Semantic Kernel has been the default .NET agent toolkit for years. In April 2026 Microsoft shipped Microsoft Agent Framework (MAF) 1.0 as the unification of Semantic Kernel’s enterprise features and AutoGen’s orchestration patterns. As of October 2026 the package sits at 1.26, Semantic Kernel is in maintenance mode (critical fixes only), and every new capability—graph workflows, native MCP/A2A, durable execution, better middleware—lands only in MAF.
Teams that already have working Semantic Kernel agents are not forced to rewrite overnight, but the longer you wait the larger the gap becomes. This article shows a concrete, incremental migration of a tool-using agent into an ASP.NET Core application using current package names and APIs.
What Actually Changes
The biggest shift is architectural. Semantic Kernel centered everything on a Kernel object that owned the chat service, plugins, filters, and memory. MAF splits those responsibilities:
- Model access lives on an
IChatClient(from Microsoft.Extensions.AI). - Agent behavior lives on
AIAgent(most commonly aChatClientAgent). - Callable capabilities are plain
AIFunctions created withAIFunctionFactory.Create. - Conversation continuity is an
AgentSessioncreated by the agent itself. - Cross-cutting concerns move to middleware.
There is no longer a mandatory Kernel. You create a chat client for your provider, then call the extension method .AsAIAgent(...).
Packages
For a typical ASP.NET Core migration:
dotnet add package Microsoft.Agents.AI
dotnet add package Microsoft.Extensions.AI
# Provider-specific (examples)
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI.OpenAI # AsIChatClient() for OpenAI/Azure OpenAI clients
dotnet add package Azure.Identity
# or for local
dotnet add package OllamaSharp
Microsoft.Agents.AI 1.26 is the current stable release. Prefer the non-prerelease package unless you need a preview feature.
Before: Semantic Kernel Agent with a Tool
A typical SK agent that can look up weather:
using System.ComponentModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
// Values from configuration
string endpoint = "https://<your-resource>.openai.azure.com/";
string deploymentName = "gpt-4o-mini";
string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!;
public class WeatherPlugin
{
[KernelFunction, Description("Gets the current weather for a city")]
public string GetWeather(string city) =>
$"Weather in {city}: 18°C, partly cloudy";
}
// In Program.cs or a factory
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(deploymentName, endpoint, apiKey)
.Build();
kernel.Plugins.AddFromType<WeatherPlugin>();
var agent = new ChatCompletionAgent
{
Name = "WeatherAgent",
Instructions = "You are a helpful weather assistant. Use the available tools.",
Kernel = kernel
};
// Invocation
AgentThread thread = new ChatHistoryAgentThread();
await foreach (var item in agent.InvokeAsync("What's the weather in Seattle?", thread))
{
Console.WriteLine(item.Message);
}
The Kernel is required, the plugin must be registered on it, the thread type is caller-created, and invocation returns an IAsyncEnumerable.
After: Microsoft Agent Framework
The same agent becomes:
using System.ComponentModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
string endpoint = "https://<your-resource>.openai.azure.com/";
string deploymentName = "gpt-4o-mini";
[Description("Gets the current weather for a city")]
static string GetWeather(string city) =>
$"Weather in {city}: 18°C, partly cloudy";
// Create the chat client (Azure OpenAI example)
var azureClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential());
IChatClient chatClient = azureClient.GetChatClient(deploymentName).AsIChatClient();
// Turn it into an agent and register the tool in one call
AIAgent agent = chatClient.AsAIAgent(
name: "WeatherAgent",
instructions: "You are a helpful weather assistant. Use the available tools when needed.",
tools: [AIFunctionFactory.Create(GetWeather)]);
// Session is created by the agent
AgentSession session = await agent.CreateSessionAsync();
// Non-streaming invocation returns a single AgentResponse
AgentResponse response = await agent.RunAsync("What's the weather in Seattle?", session);
Console.WriteLine(response.Text);
No Kernel, no plugin class required (a static method or instance method works), no [KernelFunction] attribute, and the session is owned by the agent. Streaming uses RunStreamingAsync and yields AgentResponseUpdate objects.
For a local model the client creation changes but the rest of the API stays identical:
using OllamaSharp;
var ollama = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2");
AIAgent localAgent = ollama.AsAIAgent(
instructions: "You are a helpful local assistant.",
tools: [AIFunctionFactory.Create(GetWeather)]);
Use a model that supports tool calling (llama3.2, qwen2.5, etc.).
Dependency Injection in ASP.NET Core
Register the chat client and the agent as services. Prefer a keyed singleton when the agent configuration is static:
// Program.cs
builder.Services.AddSingleton<IChatClient>(sp =>
{
var azureClient = new AzureOpenAIClient(
new Uri(builder.Configuration["AzureOpenAI:Endpoint"]!),
new DefaultAzureCredential());
return azureClient.GetChatClient(
builder.Configuration["AzureOpenAI:Deployment"]!).AsIChatClient();
});
builder.Services.AddKeyedSingleton<AIAgent>("WeatherAgent", (sp, key) =>
{
var chatClient = sp.GetRequiredService<IChatClient>();
return chatClient.AsAIAgent(
name: "WeatherAgent",
instructions: "You are a helpful weather assistant. Use tools when appropriate.",
tools: [AIFunctionFactory.Create(GetWeather)]);
});
Inject it where needed:
public class WeatherController : ControllerBase
{
private readonly AIAgent _agent;
public WeatherController([FromKeyedServices("WeatherAgent")] AIAgent agent)
{
_agent = agent;
}
[HttpPost("ask")]
public async Task<IActionResult> Ask([FromBody] string question)
{
var session = await _agent.CreateSessionAsync();
var response = await _agent.RunAsync(question, session);
return Ok(new { answer = response.Text });
}
}
If you need per-request state or different tool instances (for example a tool that depends on a scoped DbContext), register the agent as scoped instead.
Sessions, Streaming, and Options
Create a session once per conversation and reuse it for multi-turn chats. For hosted providers that support conversation deletion you still manage cleanup through the underlying client when required.
Streaming is straightforward:
await foreach (var update in agent.RunStreamingAsync(question, session))
{
if (!string.IsNullOrEmpty(update.Text))
Console.Write(update.Text);
}
Token limits and other inference options move to ChatClientAgentRunOptions:
var options = new ChatClientAgentRunOptions(
new ChatOptions { MaxOutputTokens = 800 });
var response = await agent.RunAsync(question, session, options);
Incremental Migration Checklist
- Add the MAF packages and the Microsoft.Extensions.AI client for your provider.
- Extract pure functions that were previously
[KernelFunction]methods. They usually need only a[Description]attribute. - Replace Kernel + ChatCompletionAgent construction with
IChatClient.AsAIAgent(...)and pass the functions viaAIFunctionFactory.Create. - Change thread creation to
await agent.CreateSessionAsync(). - Switch
InvokeAsync/InvokeStreamingAsynctoRunAsync/RunStreamingAsync. - Update DI registration from
AddKernel+ keyed Agent to keyedAIAgent. - Run your existing evaluation suite (or add one) against the new agent before removing the SK code path.
- Only after the agent layer is stable, consider adopting MAF workflows for multi-agent orchestration if you need them.
You can keep non-agent Semantic Kernel usage (prompt templates, pure function calling via Microsoft.Extensions.AI) running in parallel during the transition. The model-access layer is already shared. If you are weighing which AI layer to standardize on for an existing system, this is the kind of decision I cover in my AI integration work for .NET teams.
When to Stay on Semantic Kernel a Little Longer
If the existing SK agent is stable, has no need for new orchestration features, and the team is mid-release, there is no emergency. Semantic Kernel still receives critical fixes and security patches, so check Microsoft's current support statement and plan around it. Plan the migration for the next feature cycle rather than forcing it into a hotfix window.
Conclusion
Migrating a Semantic Kernel agent to Microsoft Agent Framework removes the Kernel as the center of gravity, simplifies tool registration, and puts you on the package that receives all new investment. The surface area is smaller than many teams expect: most of the work is mechanical once the chat client is in place. Start with one agent (a support-reply agent like the one in my open-source SupportAgent.NET project is a good candidate), keep the old path behind a feature flag if needed, and move the rest when the first migration is proven in production.
Rahmat Afridi is an AI Integration Developer & Senior .NET Developer based in Islamabad, Pakistan. He has been building production .NET systems since 2016, focusing on practical AI integration with ASP.NET Core, Azure, and local models.
Top comments (0)