Start Debugging

Migrate a Semantic Kernel App to Microsoft Agent Framework 1.0

A step-by-step checklist for moving an existing Semantic Kernel 1.77 .NET app to Microsoft Agent Framework 1.13. Covers the Kernel-to-AIAgent rewrite, plugins-to-tools, thread-to-session, the KernelFunction compatibility bridge, DI changes, and the gotchas that bite mid-cutover.

If you shipped a .NET agent on Semantic Kernel in 2025, you now own a codebase built around the Kernel object, [KernelFunction] plugins, and ChatCompletionAgent. Microsoft has put that stack into maintenance mode: Microsoft.SemanticKernel 1.77.0 (released May 28, 2026) still gets critical bug and security fixes for at least a year past Agent Framework GA, but every new feature now lands in Microsoft.Agents.AI, which is at 1.13.0 as of July 3, 2026. This post is the migration checklist for moving an existing SK app to Agent Framework 1.x on .NET 10 / C# 14: what breaks, the exact API mapping, how to do it incrementally with the KernelFunction compatibility bridge, and how to verify each step. If you are deciding whether to move at all rather than how, read the greenfield comparison first; this post assumes you have already decided to migrate.

The short version: a small SK agent (one Kernel, a handful of plugins, one ChatCompletionAgent) is a half-day job because the concepts map one-to-one. The pain is not conceptual, it is mechanical: message and content types change from SK’s ChatMessageContent to Microsoft.Extensions.AI, thread creation moves from the caller to the agent, and every DI registration that hung off Kernel needs to be re-pointed at AIAgent. None of it is a rewrite. Most of it is find-and-replace with a compiler at your back.

Why move off Semantic Kernel now

Four concrete reasons, not marketing ones:

What breaks

Here is the full breaking-change surface for a typical SK agent. Severity is how much hand-editing each area needs, not how hard it is to understand.

AreaSemantic Kernel 1.77Agent Framework 1.13Severity
NamespacesMicrosoft.SemanticKernel, .AgentsMicrosoft.Agents.AI, Microsoft.Extensions.AIlow
Core abstractionKernel + ChatCompletionAgentAIAgent over IChatClienthigh
Agent typesChatCompletionAgent, AzureAIAgent, OpenAIAssistantAgentsingle ChatClientAgentmedium
Tool registration[KernelFunction] + plugin + add to KernelAIFunctionFactory.Create(method)medium
Message / content typesChatMessageContent, StreamingChatMessageContentChatMessage, AIContent (M.E.AI)high
Thread creationcaller news up the thread subclassawait agent.CreateSessionAsync()medium
InvocationInvokeAsync returns IAsyncEnumerable<...>RunAsync returns one AgentResponsemedium
StreamingInvokeStreamingAsync -> StreamingChatMessageContentRunStreamingAsync -> AgentResponseUpdatemedium
OptionsOpenAIPromptExecutionSettings + KernelArgumentsChatClientAgentRunOptionslow
Dependency injectionAddKernel(), agent needs KernelAddKeyedSingleton<AIAgent>(...)medium

The two high rows (core abstraction and message types) are where the real work is. Everything else is mechanical.

Pre-flight checklist

Before you touch a line of agent code:

  1. Target .NET 10. Agent Framework 1.x targets .NET 10 (and .NET Standard 2.0 for the core library). Confirm your <TargetFramework> is net10.0 and dotnet --version reports a 10.x SDK.
  2. Pin your current SK version in a branch so rollback is a git checkout away. Note the exact Microsoft.SemanticKernel version in your .csproj (this guide assumes 1.77.0).
  3. Bump Semantic Kernel to 1.38 or higher if you plan to use the compatibility bridge. The KernelFunction-as-tool path needs a version where KernelFunction derives from Microsoft.Extensions.AI.AIFunction; 1.77 is well past that line.
  4. Inventory your plugins. List every [KernelFunction] method. Each one is either a straight port or a bridge candidate. This list is your migration work queue.
  5. Inventory hosted threads. If you use OpenAIAssistantAgent or AzureAIAgent with server-side threads, note that Agent Framework has no thread.DeleteAsync(); you clean up via the provider SDK. Flag those call sites now.

Migration steps

Do these in order. Each step ends with a verification you can actually run.

  1. Add the Agent Framework packages and namespaces. Install Microsoft.Agents.AI (1.13.0) and the provider package you need, for example the OpenAI or Azure client that exposes an IChatClient. Swap the using directives at the top of your agent files.

    // Before: Semantic Kernel 1.77
    using Microsoft.SemanticKernel;
    using Microsoft.SemanticKernel.Agents;
    using Microsoft.SemanticKernel.ChatCompletion;
    
    // After: Microsoft Agent Framework 1.13, .NET 10
    using Microsoft.Agents.AI;
    using Microsoft.Extensions.AI;

    Verify: dotnet build fails only on the SK-specific symbols you are about to replace, not on missing packages.

  2. Replace Kernel + ChatCompletionAgent with a single AsAIAgent call. The Kernel builder, the plugin registration, and the agent constructor collapse into one extension call off your chat client.

    // Before: Semantic Kernel 1.77
    Kernel kernel = Kernel.CreateBuilder()
        .AddOpenAIChatCompletion("gpt-4o-mini", apiKey)
        .Build();
    kernel.Plugins.AddFromType<WeatherTools>();
    
    ChatCompletionAgent agent = new()
    {
        Name = "Concierge",
        Instructions = "You are a concise travel concierge.",
        Kernel = kernel,
        Arguments = new(new OpenAIPromptExecutionSettings
        {
            FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
        })
    };
    
    // After: Microsoft Agent Framework 1.13
    AIAgent agent = new OpenAIClient(apiKey)
        .GetChatClient("gpt-4o-mini")
        .AsIChatClient()
        .AsAIAgent(
            instructions: "You are a concise travel concierge.",
            tools: [AIFunctionFactory.Create(WeatherTools.GetWeather)]);

    Note that FunctionChoiceBehavior.Auto() is gone: automatic tool calling is the default when you pass tools. Verify: the agent constructs without a Kernel reference anywhere in the file.

  3. Convert plugins to plain methods (or bridge them). For a full rewrite, drop the [KernelFunction] attribute; a plain method with an optional [Description] is enough. This is the same tool-authoring model covered in function tools in Agent Framework.

    // Before
    public class WeatherTools
    {
        [KernelFunction, Description("Get the weather for a city")]
        public static string GetWeather(string city) => $"It is 21C and clear in {city}.";
    }
    
    // After: attribute optional, description optional
    public class WeatherTools
    {
        [Description("Get the weather for a city")]
        public static string GetWeather(string city) => $"It is 21C and clear in {city}.";
    }

    If you have dozens of plugins and cannot rewrite them all at once, use the compatibility bridge instead (see the next section) so the migration can land incrementally. Verify: the tool shows up in agent.RunAsync tool-call traces with the right name and schema.

  4. Move thread creation to the agent. SK made the caller pick the thread subclass. Agent Framework asks the agent to create the session.

    // Before
    AgentThread thread = new ChatHistoryAgentThread();
    
    // After
    AgentSession session = await agent.CreateSessionAsync();

    Verify: multi-turn conversations retain context across RunAsync calls when you pass the same session.

  5. Rewrite invocation from InvokeAsync to RunAsync. SK’s non-streaming call is an async iterator; Agent Framework returns a single AgentResponse whose .Text is the answer and whose .Messages holds the full transcript including tool calls.

    // Before
    await foreach (AgentResponseItem<ChatMessageContent> item in
        agent.InvokeAsync("What should I pack for Lisbon?", thread))
    {
        Console.WriteLine(item.Message);
    }
    
    // After
    AgentResponse response = await agent.RunAsync("What should I pack for Lisbon?", session);
    Console.WriteLine(response.Text);

    Verify: response.Text is non-empty and response.Messages contains the tool-call and tool-result messages you expect.

  6. Rewrite streaming from InvokeStreamingAsync to RunStreamingAsync. The pattern is the same async foreach, but the update type carries more agent context.

    // Before
    await foreach (StreamingChatMessageContent update in
        agent.InvokeStreamingAsync(userInput, thread))
    {
        Console.Write(update);
    }
    
    // After
    await foreach (AgentResponseUpdate update in
        agent.RunStreamingAsync(userInput, session))
    {
        Console.Write(update); // ToString()-friendly; concatenate .Text for the full answer
    }

    Verify: tokens stream to the console and the concatenated .Text matches the non-streaming answer.

  7. Re-point dependency injection at AIAgent. Delete AddKernel() and the Kernel-keyed registration. Register the AIAgent directly.

    // Before
    services.AddKernel().AddOpenAIChatCompletion("gpt-4o-mini", apiKey);
    services.AddKeyedSingleton<Microsoft.SemanticKernel.Agents.Agent>("concierge",
        (sp, _) => new ChatCompletionAgent { Kernel = sp.GetRequiredService<Kernel>() });
    
    // After
    services.AddKeyedSingleton<AIAgent>("concierge", (sp, _) =>
        sp.GetRequiredService<IChatClient>()
          .AsAIAgent(instructions: "You are a concise travel concierge."));

    Verify: the app resolves the keyed AIAgent at startup and dotnet test passes on any DI smoke test you have.

  8. Migrate options. Replace OpenAIPromptExecutionSettings wrapped in KernelArguments with ChatClientAgentRunOptions.

    // Before
    var settings = new OpenAIPromptExecutionSettings { MaxTokens = 1000 };
    var options = new AgentInvokeOptions { KernelArguments = new(settings) };
    
    // After
    var options = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });

    Verify: token limits and other inference options take effect (check a response that would otherwise overflow the old limit).

The incremental path: bridge KernelFunction instead of rewriting

The single most useful fact for a large migration is that in current Semantic Kernel, KernelFunction derives from Microsoft.Extensions.AI.AIFunction. That means you do not have to rewrite every plugin before the app compiles on Agent Framework. You can reuse an existing KernelFunction as an Agent Framework tool directly, either by passing it straight into tools: or via the .AsAIFunction() extension.

// Microsoft Agent Framework 1.13 + Semantic Kernel 1.77 side by side.
// Keep your existing plugin class untouched during the cutover.
KernelPlugin plugin = KernelPluginFactory.CreateFromType<WeatherTools>();

AIAgent agent = chatClient.AsAIAgent(
    instructions: "You are a concise travel concierge.",
    // KernelFunction is an AIFunction, so it drops straight into tools:
    tools: [.. plugin.Select(kf => kf.AsAIFunction())]);

This is the same “keep both frameworks in the process while you move” strategy as turning an SK plugin into an MCP server, except here the tool never leaves the process. Land the framework swap first with plugins bridged, get the app green, then rewrite plugins to plain methods one at a time in follow-up commits. Each rewrite is independently shippable and independently revertible.

Verification: the smoke test after the cutover

Run this checklist once the build is green:

Rollback plan

This migration is reversible per commit if you stage it right. Because the compatibility bridge lets both frameworks coexist, the safe sequence is: (1) framework swap with plugins bridged, (2) plugin rewrites, (3) SK package removal. Roll back by reverting the relevant commit. The one one-way door is removing the Microsoft.SemanticKernel package reference in step 3: do that only after every plugin is rewritten to a plain method and the SK smoke tests are deleted. Until then, keep the SK package installed even though the bridge is the only thing using it. If you deleted SK too early and need it back, it is a dotnet add package Microsoft.SemanticKernel --version 1.77.0 away, but the point of staging is that you never have to.

Gotchas we hit

Sources

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Back