When I first started building AI agents with Semantic Kernel in C#, I quickly realized there's a fundamental difference between a chatbot and an agent. A chatbot responds to prompts and returns text. An AI agent, on the other hand, is autonomous -- it can reason about tasks, call tools and functions, maintain conversation context across multiple turns, and work toward goals. In Semantic Kernel, an agent isn't just a wrapper around a language model. It's a fully-configured entity with its own identity, instructions, access to plugins, and the ability to manage stateful conversations through threads. Building AI agents Semantic Kernel C# enables you to create sophisticated, tool-using assistants that can handle complex workflows in your .NET applications.
In this practical guide, I'll walk you through every step of building AI agents with Semantic Kernel in C#, from basic setup to production-ready implementations with plugins and multi-turn conversations.
Step 1: Set Up the Kernel
Note: The Semantic Kernel agent functionality is currently experimental -- as an experimental feature, breaking changes may occur between versions without prior notice. Install the agent package with the --prerelease flag:
dotnet add package Microsoft.SemanticKernel.Agents.Core --prerelease
Before you can create an agent, you need a kernel.The kernel is the foundation of every Semantic Kernel application -- it holds your AI service configuration, registered plugins, and execution settings. When building AI agents Semantic Kernel C#, you configure the kernel with a chat completion service and register any plugins your agent will need to call.
Here's how I set up the kernel for an agent:
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
"gpt-4o",
Environment.GetEnvironmentVariable("OPENAI_API_KEY")!
);
var kernel = builder.Build();
This creates a kernel connected to OpenAI's GPT-4o model (model availability and defaults are subject to change). You can also use Azure OpenAI, Anthropic, or other providers. The key point is that you register your chat completion service on the kernel before creating your agent. If your agent needs plugins, you add them to the kernel at this stage too -- I'll show you that in detail later in the article.
For more background on how kernels work, check out my complete Semantic Kernel guide.
Step 2: Define Agent Instructions
Instructions are the most critical part of building AI agents Semantic Kernel C#. They define who your agent is, what it can do, and how it should behave. Poor instructions lead to unreliable agents that ignore constraints or produce low-quality output. Great instructions create agents that consistently deliver value.
Good agent instructions have three components: persona, capabilities, and constraints. Here's an example of weak instructions:
You are a helpful assistant.
That's too generic. The agent has no clear identity, no understanding of its capabilities, and no constraints to guide its behavior. Here's a strong version:
You are an expert C# code reviewer with 10 years of .NET experience.
When reviewing code:
- Use the analyze_code tool to get automated analysis
- Then provide your own expert commentary
- Be constructive and specific with feedback
- Always suggest concrete improvements, not just problems
- Follow C# best practices and SOLID principles
Do not:
- Provide generic advice like "write clean code"
- Review code in languages other than C#
- Make changes to the code directly (only suggest improvements)
This version tells the agent exactly who it is, what tools it has, how to use them, and what boundaries it must respect. I've found that spending 10 extra minutes crafting clear instructions saves hours of debugging agent behavior later.
Step 3: Create the ChatCompletionAgent
Once you have a kernel and well-defined instructions, creating the agent is straightforward. The ChatCompletionAgent class is the core agent type in Semantic Kernel. It wraps a chat completion model with identity, instructions, and execution settings.
Here's the full creation code:
using Microsoft.SemanticKernel.Agents;
var agent = new ChatCompletionAgent
{
Name = "CodeReviewer",
Instructions = """
You are an expert C# code reviewer with 10 years of .NET experience.
When reviewing code:
- Use the analyze_code tool to get automated analysis
- Then provide your own expert commentary
- Be constructive and specific with feedback
- Always suggest concrete improvements, not just problems
- Follow C# best practices and SOLID principles
""",
Kernel = kernel,
Arguments = new KernelArguments(new OpenAIPromptExecutionSettings
{
Temperature = 0.7,
MaxTokens = 1000
})
};
The Name property gives your agent an identity that appears in conversation history. The Instructions are the system prompt that guides the agent's behavior. The Kernel connects the agent to your AI service and plugins. And ExecutionSettings lets you tune model behavior like temperature and token limits.
I typically set lower temperatures (0.3-0.5) for agents that need consistent, deterministic outputs like code reviewers or data validators. I use higher temperatures (0.7-0.9) for agents that need creativity, like content generators or brainstorming assistants.
Step 4: Start a Conversation Thread
Agents don't exist in isolation -- they participate in conversations. A conversation thread maintains the full history of messages between you and the agent. This is what makes agents stateful and context-aware, unlike stateless API calls.
In Semantic Kernel, you use ChatHistoryAgentThread to manage conversation state:
var thread = new ChatHistoryAgentThread();
Creating a new thread gives you a clean slate -- the agent has no prior context. This is perfect for starting a new conversation or handling a new user session. If you want to resume an existing conversation, you can rebuild the thread from stored chat history. I'll cover that pattern in detail in my upcoming article on agent persistence.
Why do threads matter? Without threads, your agent would forget everything after each interaction. With threads, the agent remembers earlier messages, can reference previous decisions, and maintains conversational coherence. This is essential for building AI agents Semantic Kernel C# that feel natural and intelligent to users.
Step 5: Send Messages and Receive Responses
Now comes the interaction -- sending messages to your agent and receiving responses. Semantic Kernel uses an async enumerable pattern that lets you stream responses as they're generated, rather than waiting for the complete response.
Here's how I invoke the agent:
await foreach (var response in agent.InvokeAsync("Review this code: public void Process() { }", thread))
{
Console.WriteLine(response.Content);
}
The InvokeAsync method returns an IAsyncEnumerable<ChatMessageContent>. For most responses, you'll get a single item containing the agent's complete message. However, when the agent calls tools or functions, you might receive multiple content items -- one for each function call result and then the final synthesized response.
I always use await foreach rather than trying to collect all responses at once. This gives you real-time feedback and keeps your application responsive. If you need the full response text, you can accumulate it:
var fullResponse = new StringBuilder();
await foreach (var response in agent.InvokeAsync(thread, userMessage))
{
fullResponse.Append(response.Content);
}
var completeMessage = fullResponse.ToString();
The thread automatically captures both your input message and the agent's response, maintaining the conversation history for subsequent turns. For more on handling async/await patterns in C#, I've written an in-depth guide.
Adding Tools: Agents with Plugins
The real power of building AI agents Semantic Kernel C# comes from giving agents tools to work with. Plugins let agents call C# methods, query databases, fetch APIs, and interact with external systems. An agent with plugins can do more than chat -- it can take action.
To add tools to your agent, you register plugins on the kernel and configure ToolCallBehavior to auto-invoke functions. Here's a complete example with a code review plugin:
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.ComponentModel;
// Define a plugin for the agent to use
public class CodeReviewPlugin
{
[KernelFunction("analyze_code")]
[Description("Analyzes C# code for common issues and suggests improvements")]
public string AnalyzeCode(
[Description("The C# code snippet to analyze")] string code)
{
// Simplified analysis -- in production, use Roslyn
var issues = new List<string>();
if (code.Contains("var ") && code.Split('
').Length > 50)
issues.Add("Consider using explicit types in longer methods for clarity");
if (!code.Contains("async") && code.Contains("Task"))
issues.Add("Consider making this method async for better scalability");
return issues.Count > 0
? $"Found {issues.Count} suggestion(s):
" + string.Join("
", issues.Select(i => $"- {i}"))
: "No issues found. Code looks clean!";
}
}
// Build kernel with plugin
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
builder.Plugins.AddFromType<CodeReviewPlugin>();
var kernel = builder.Build();
// Create the agent
var agent = new ChatCompletionAgent
{
Name = "CodeReviewer",
Instructions = """
You are an expert C# code reviewer with 10 years of .NET experience.
When reviewing code:
- Use the analyze_code tool to get automated analysis
- Then provide your own expert commentary
- Be constructive and specific with feedback
- Always suggest concrete improvements, not just problems
- Follow C# best practices and SOLID principles
""",
Kernel = kernel,
Arguments = new KernelArguments(new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
})
};
// Create conversation thread and chat
var thread = new ChatHistoryAgentThread();
await foreach (var response in agent.InvokeAsync("Please review this code:
public var result = GetData();", thread))
{
Console.WriteLine(response.Content);
}
The ToolCallBehavior.AutoInvokeKernelFunctions setting is crucial. It tells Semantic Kernel to automatically execute any functions the agent decides to call, then pass the results back to the agent to formulate a final response. Without this, the agent would generate function calls but never execute them.
For more detailed guidance on creating and registering plugins, see my guides on Semantic Kernel plugins and how to create custom plugins.
Multi-Turn Conversation Example
One of the most impressive capabilities when building AI agents Semantic Kernel C# is multi-turn conversations where the agent remembers context across many interactions. Here's a complete example showing a five-turn conversation about building a REST API:
var thread = new ChatHistoryAgentThread();
string[] turns =
[
"My name is Alex and I'm building a REST API in .NET 8.",
"What authentication approach would you recommend for it?",
"Can you show me how to implement JWT with the approach you suggested?",
"How would I test the authentication in my integration tests?"
];
foreach (var message in turns)
{
Console.WriteLine($"
User: {message}");
Console.Write("Agent: ");
await foreach (var response in agent.InvokeAsync(message, thread))
{
Console.Write(response.Content);
}
Console.WriteLine();
}
// Agent retains context: knows user's name is Alex, knows it's a .NET 8 REST API, etc.
In this conversation, the agent remembers that the user's name is Alex, that they're working on a .NET 8 REST API, and what authentication approach was discussed. By turn three, the agent provides implementation code that's contextually relevant to the earlier recommendation. By turn four, it understands the full technical context and can give specific testing advice.
This is the difference between stateless chat completions and true agents. The thread maintains the full conversation, enabling the agent to build understanding over time and provide increasingly relevant responses.
Agent Identity and Personas
The Name property on your agent isn't just metadata -- it shapes how the agent communicates and how users perceive it. I've found that giving agents specific, role-appropriate names significantly improves interaction quality.
Compare these two agents:
// Generic
var agent1 = new ChatCompletionAgent
{
Name = "Assistant",
Instructions = "You help users with tasks."
};
// Specific
var agent2 = new ChatCompletionAgent
{
Name = "SecurityAuditor",
Instructions = """
You are SecurityAuditor, a specialist in .NET application security.
You conduct code reviews focused exclusively on security vulnerabilities.
You provide severity ratings (Critical, High, Medium, Low) for each finding.
"""
};
The second agent has a clear identity that reinforces its specialized capabilities. Users understand its expertise boundaries, and the agent itself tends to stay in character better because the name reinforces the instructions.
I use role-specific instructions paired with descriptive names for production agents. For general-purpose assistants, I keep names neutral but still meaningful -- "TaskCoordinator" rather than "Agent1". The persona you establish through naming and instructions directly impacts the quality and consistency of responses your agent produces when building AI agents Semantic Kernel C#.
Error Handling and Safety
Agents can fail in ways that simple chat completions don't. The most common issue is tool-calling loops, where an agent repeatedly calls functions without making progress. Semantic Kernel includes built-in protections, but you should understand what happens when things go wrong.
By default, Semantic Kernel limits function call iterations to prevent infinite loops. If your agent reaches the iteration limit, InvokeAsync will throw a KernelException. Here's how I handle it:
try
{
await foreach (var response in agent.InvokeAsync(userMessage, thread))
{
Console.WriteLine(response.Content);
}
}
catch (KernelException ex)
{
Console.WriteLine($"Agent failed: {ex.Message}");
// Log the error, potentially reset the thread, inform the user
}
You can also configure custom iteration limits in your execution settings:
Arguments = new KernelArguments(new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
})
Content filtering is another safety consideration. Most AI providers filter harmful inputs and outputs automatically, but you should implement application-level filtering too. I validate user inputs before passing them to agents and sanitize agent outputs before displaying them to users, especially in production systems.
When building AI agents Semantic Kernel C#, always assume agents can produce unexpected outputs. Wrap invocations in try-catch blocks, log agent behavior for debugging, and implement timeouts for long-running operations.
FAQ
Can I use multiple agents in the same conversation thread?
Yes. Semantic Kernel supports multi-agent conversations where different agents participate in the same thread. You can invoke different agents sequentially on the same ChatHistoryAgentThread, and each agent sees the full conversation history including messages from other agents. This enables powerful agent-to-agent collaboration patterns. For architectural guidance on multi-agent systems, see my Semantic Kernel agents guide.
How do I persist conversation threads across application restarts?
The ChatHistoryAgentThread stores history in memory by default. To persist conversations, serialize the underlying ChatHistory to a database or file. You can access the chat history from the thread, save it as JSON, and reload it when the user returns. I typically store conversation state in a database keyed by user ID and session ID.
What's the difference between ChatCompletionAgent and other agent types?
ChatCompletionAgent is the primary agent type in Semantic Kernel, designed for conversational AI with tool-calling capabilities. Semantic Kernel also supports specialized agent types like OpenAIAssistantAgent which uses OpenAI's Assistants API with built-in code interpreter and file access. For most .NET scenarios, ChatCompletionAgent gives you the most control and flexibility when building AI agents Semantic Kernel C#.
How do I test agents in automated tests?
I test agents by mocking the kernel's AI service and providing deterministic responses. You can also test plugins independently from agents by calling plugin methods directly. For integration tests, I use test API keys with rate limits and verify that agents correctly invoke functions and maintain conversation state. Testing agent behavior requires both unit tests for plugins and integration tests for full agent workflows.
Conclusion
Building AI agents Semantic Kernel C# can significantly influence how you integrate AI into .NET applications. Unlike simple chatbots, agents maintain context, call tools, and work toward goals autonomously. By following the steps in this guide -- setting up the kernel, crafting clear instructions, creating the ChatCompletionAgent, managing threads, and adding plugins -- you can build production-ready agents that handle complex workflows.
The key to success is starting simple and iterating. Create a basic agent without plugins first. Test its responses. Refine the instructions. Then gradually add tools and complexity. Every agent I've built in production started as a minimal prototype and evolved through real-world usage.
Whether you're building code reviewers, customer support assistants, or data analysis agents, Semantic Kernel provides the foundation you need to create intelligent, autonomous AI agents in C#.

