cd /news/ai-agents/migrating-semantic-kernel-agents-to-… · home › topics › ai-agents › article
[ARTICLE · art-157673] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Migrating Semantic Kernel Agents to Microsoft Agent Framework in ASP.NET Core

A developer documented an incremental migration of a tool-using Semantic Kernel agent to Microsoft Agent Framework (MAF) 1.0 inside an ASP.NET Core application. The walkthrough shows how MAF replaces Semantic Kernel's mandatory Kernel object with an IChatClient, an AIAgent built via the .AsAIAgent() extension, AIFunctionFactory-created tools, and agent-created AgentSession objects. The author notes Semantic Kernel is now in maintenance mode at MAF 1.26, with new capabilities such as graph workflows, native MCP/A2A, and durable execution landing only in MAF.

by read5 min views3 publishedOct 26, 2026

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.

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:

IChatClient (from Microsoft.Extensions.AI).AIAgent (most commonly a ChatClientAgent). AIFunctions created with AIFunctionFactory.Create. AgentSession created by the agent itself. There is no longer a mandatory Kernel. You create a chat client for your provider, then call the extension method .AsAIAgent(...).

For a typical ASP.NET Core migration:

dotnet add package Microsoft.Agents.AI
dotnet add package Microsoft.Extensions.AI
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI.OpenAI  # AsIChatClient() for OpenAI/Azure OpenAI clients
dotnet add package Azure.Identity
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.

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.

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.).

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.

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);

[KernelFunction] methods. They usually need only a [Description] attribute.IChatClient.AsAIAgent(...) and pass the functions via await agent.CreateSessionAsync(). InvokeAsync / InvokeStreamingAsync to RunAsync / RunStreamingAsync. AddKernel + keyed Agent to keyed 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.

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.

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.

── more in #ai-agents 4 stories · sorted by recency
── more on @microsoft 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/migrating-semantic-k…] indexed:0 read:5min 2026-10-26 · —