Semantic Kernel Sample Agent - C#/.NET

This sample demonstrates how to build an agent using Semantic Kernel in C#/.NET with the Microsoft Agent 365 SDK. It covers:

  • Observability: End-to-end tracing, caching, and monitoring for agent applications
  • Notifications: Services and models for managing user notifications
  • Tools: Model Context Protocol tools for building advanced agent solutions
  • Hosting Patterns: Hosting with Microsoft 365 Agents SDK

This sample uses the Microsoft Agent 365 SDK for .NET.

For comprehensive documentation and guidance on building agents with the Microsoft Agent 365 SDK, including how to add tooling, observability, and notifications, visit the Microsoft Agent 365 Developer Documentation.

Prerequisites

  • .NET 8.0 or higher
  • Microsoft Agent 365 SDK
  • Semantic Kernel 1.66.0 or higher
  • Azure/OpenAI API credentials

Launch Profiles

This sample includes two launch profiles in Properties/launchSettings.json:

Sample Agent

Uses Agentic Users with Client Credentials or Managed Identity. Use this for production or when testing with full Azure Bot Service configuration.

Sample Agent with Bearer Token Support

Simplified profile for early local development using bearer token authentication.

Quick setup:

  1. Add required permissions using the a365 CLI:

    a365 develop add-permissions
    

    This grants the necessary scopes for MCP tool access.

  2. Get a bearer token:

    a365 develop get-token
    

    The CLI will either automatically add the token to your launchSettings.json or provide it for you to copy/paste.

  3. Select the "Sample Agent with Bearer Token Support" launch profile in Visual Studio

  4. Run the agent

Note: Bearer tokens are for development only and expire regularly. Refresh with a365 develop get-token.

Observability

A365 export is disabled by default. To send traces to Agent 365, set EnableAgent365Exporter=true and configure the separate Agent365Observability values: TenantId (agent home tenant GUID), AgentId (actual agent instance client ID), and BlueprintClientId. For Azure, set UseManagedIdentity=true; optional ManagedIdentityClientId selects a user-assigned identity, otherwise the system-assigned identity is used. The blueprint federation must already exist. For local development, set UseManagedIdentity=false and supply BlueprintClientSecret through user secrets or the Agent365Observability__BlueprintClientSecret environment variable.

The provider in Observability/ obtains a blueprint T1 using client_credentials, fmi_path=AgentId, and api://AzureADTokenExchange/.default, then uses T1 as the agent's client assertion for api://9b975845-388f-4429-889e-eab1ef63949c/.default. This is the documented app-only protocol, not OBO or user_fic. Existing business MCP/Graph tokens, auth handlers, and original turn baggage remain unchanged; a developer bearer token cannot authenticate S2S OBS.

This sample provider is single-instance: one configured tenant and agent instance. Multi-instance or multi-tenant deployments should cache per agent/tenant and reuse the hosting connection credential. The provider requests tokens from login.microsoftonline.com; sovereign clouds need provider changes.

An app-only OBS token with idtyp=app, or without idtyp but with oid equal to sub, may omit roles or have roles: []. Valid nonempty application roles also remain supported when idtyp is absent. Any scp claim is rejected. For registered blueprint agents, do not add an Agent365.Observability.OtelWrite grant solely to populate roles. For AI Teammates, complete the OtelWrite application-role step printed by a365 setup all --aiteammate; AI Teammate S2S without that step has not been validated.

Missing/placeholder settings or using the blueprint as AgentId fail when export is enabled; with export disabled, placeholders do not block local/Playground startup. Export tenant/agent mismatches, delegated scp, explicit non-app/null idtyp, malformed roles, invalid responses, and expired tokens fail closed. The isolated cache refreshes 60 seconds before the earliest expiry.

Microsoft.OpenTelemetry 1.0.1 is configured with o.Agent365.Exporter.UseS2SEndpoint = true and the dedicated provider's TokenResolver only when export is enabled. See Agent 365 observability S2S export for the route finding and token contract.

Working with User Identity

On every incoming message, the A365 platform populates Activity.From with basic user information — always available with no API calls or token acquisition:

Field Description
Activity.From.Id Channel-specific user ID (e.g., 29:1AbcXyz... in Teams)
Activity.From.Name Display name as known to the channel
Activity.From.AadObjectId Azure AD Object ID — use this to call Microsoft Graph

The sample logs these fields at the start of every message turn and injects the display name into the LLM system instructions for personalized responses.

Handling Agent Install and Uninstall

When a user installs (hires) or uninstalls (removes) the agent, the A365 platform sends an InstallationUpdate activity. The sample handles this in OnHireMessageAsync (Agents/MyAgent.cs):

Action Description
add Agent was installed — send a welcome message
remove Agent was uninstalled — send a farewell message
if (turnContext.Activity.Action == InstallationUpdateActionTypes.Add)
{
    await turnContext.SendActivityAsync(MessageFactory.Text("Thank you for hiring me! Looking forward to assisting you in your professional journey!"), cancellationToken);
}
else if (turnContext.Activity.Action == InstallationUpdateActionTypes.Remove)
{
    await turnContext.SendActivityAsync(MessageFactory.Text("Thank you for your time, I enjoyed working with you."), cancellationToken);
}

To test with Agents Playground, use Mock an Activity → Install application to send a simulated installationUpdate activity.

Sending Multiple Messages in Teams

Agent365 agents can send multiple discrete messages in response to a single user prompt in Teams. This is achieved by calling SendActivityAsync multiple times within a single turn.

Important: Streaming responses are buffered by the SDK for Teams agentic identities and delivered as a single message. Use SendActivityAsync directly to send immediate, discrete messages to the user.

The ack and typing indicator are sent in MessageActivityAsync before agent initialization (Agents/MyAgent.cs). This guarantees they arrive as discrete messages before the streaming connection opens:

// Message 1: immediate ack
await turnContext.SendActivityAsync(MessageFactory.Text("Got it — working on it…"), cancellationToken);

// Typing indicator — visible while agent initializes, before the streaming response opens.
// Only visible in 1:1 and small group chats, not in channels.
await turnContext.SendActivityAsync(Activity.CreateTypingActivity(), cancellationToken);

Once agent initialization completes, the streaming response opens and takes over as the visual indicator:

// Streaming response — arrives as message 2, buffered by the SDK for Teams agentic identities.
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Working on a response for you", cancellationToken);
// LLM call streams response chunks via QueueTextChunk...
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken);

Typing Indicators

Typing indicators show a ... animation in Teams while the agent is working. They have a ~5-second visual timeout and must be re-sent to stay visible for long-running operations. For this sample, a single typing indicator is sent before the streaming response opens — once streaming starts, it takes over as the progress indicator.

Note: Typing indicators are only visible in 1:1 chats and small group chats — not in channels.

Running the Agent

To set up and test this agent, refer to the Configure Agent Testing guide for complete instructions.

For a detailed explanation of the agent code and implementation, see the Agent Code Walkthrough.

Support

For issues, questions, or feedback:

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.

Additional Resources

Trademarks

Microsoft, Windows, Microsoft Azure and/or other Microsoft products and services referenced in the documentation may be either trademarks or registered trademarks of Microsoft in the United States and/or other countries. The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks. Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653.

License

Copyright (c) Microsoft Corporation. All rights reserved.

Licensed under the MIT License - see the LICENSE file for details.