# Semantic Kernel to Microsoft Agent Framework: A Real Migration

*Published 2026-05-12 · Dmitry Shirokov · shirokoff.ca/blog/semantic-kernel-to-maf-migration*

For seven months the honest advice was "don't." Semantic Kernel went into maintenance mode on 1 October 2025 alongside AutoGen, MAF arrived in public preview the same day, and rewriting production onto a preview SDK trades known risk for unknown risk on someone else's schedule. **MAF 1.0 in April 2026 — stable APIs, long-term support — changed the calculus.** Here's what one migration actually cost.

## Sort the pieces before estimating

| Semantic Kernel piece | MAF equivalent | Effort |
|---|---|---|
| Native functions | Tools (plain functions + descriptions) | **Trivial** |
| Prompt functions | Prompts + tools, or a workflow step | Low |
| Plugins | Tool collections, or an MCP server | Low |
| Chat completion + connectors | `AzureOpenAIChatClient` etc. | Low |
| Filters | Middleware | Moderate — different hook points and ordering |
| Agents & group chat | Agents + orchestration/workflows | Moderate — MAF inherited AutoGen's model, not SK's |
| Memory / vector connectors | Your store, behind your own port | **Don't port it. Rewrite it as yours.** |
| Planners | No like-for-like | High if you leaned on them |
| Telemetry | OpenTelemetry + GenAI semconv | Low, and an upgrade |

Memory and planners decide your timeline. Everything else you can estimate by counting functions.

## Decision one: extract the domain before touching the framework

Days 1–2: pull every native function's body into a plain module with **no framework imports**, leave a thin SK wrapper calling it, and ship that. Still on SK, still in production, still passing tests — but the business logic is now yours. It's independently valuable (a testable domain layer is an improvement even if you never migrate) and it converts "rewrite the agent" into "write a new adapter." Later, the MAF adapter sits next to the SK one; both compile, both are tested, and cutover is a flag.

## Decision two: do not port the memory layer

Conversation history and durable facts are product data, not framework artifacts ([agent state you own](https://shirokoff.ca/blog/agent-state-portability)). Porting SK's memory connectors into MAF's equivalents pays a conversion cost to end up in the same place: your most valuable state inside a component you don't control, awaiting the next migration. Three days moved conversations and learned facts into our own tables behind two ports. Immediate payoff: both agents could run against the *same* conversations during cutover, compare on real traffic, and roll back with a flag — impossible when each framework owns its own truth.

## Filters → middleware

Same concept, mechanical port. The afternoon of confusion was **ordering and short-circuit semantics**: which hook sees arguments pre-validation, which sees results post-retry, what happens when one refuses. Write framework-agnostic behaviour tests *first* ("an unauthorized tenant never reaches the tool", "a redacted field never appears in the trace") — they become the specification, and they caught two behaviour changes we'd have shipped.

## The eval suite is what makes it provable

You cannot diff an agent: two implementations phrase things differently, order tools differently, and both are correct. The only answer to "did behaviour change?" was the same [eval suite](https://shirokoff.ca/blog/evaluating-llm-agent-systems) against both agents on the same dataset, plus per-case tool-call accuracy. Build it before, not during. Two cases caught real regressions — a middleware ordering change and a subtly reworded tool description that changed when the model reached for it.

## What has no equivalent

**Planners** — replace with plain function calling (usually better) or an explicit workflow graph (usually clearer); budget design time, not porting time. **Group-chat behaviour** — MAF inherited AutoGen's patterns, so port it last and test it hardest. **Bespoke connectors** written against SK interfaces have no home. And scheduling: **don't migrate and upgrade your model in the same change** — we did once by accident and spent a day arguing about whether MAF had made the agent worse.

## Was it worth it?

Yes, for three reasons unrelated to novelty: **observability improved** (OTel + GenAI semconv out of the box, so traces landed in the same backend and the eval harness read them without an adapter), **the workflow model gave us real checkpointing** where we'd hand-rolled it badly, turning an approval step from a scheduled-job kludge into a paused run, and **.NET and Python converged**, removing a second architecture. Costs: docs lagging samples even post-1.0, multi-agent logic needing redesign, a week on state we'd have spent eventually. ~Three weeks for a moderate system, most of it producing artifacts that outlive both frameworks.

On SK today? Not urgent, not blind. Do the two decoupling steps now; migrate when you have a reason. But **don't start a new agent on Semantic Kernel in 2026** — that's a migration scheduled for later at full price.

## Carry-away

Sort the pieces, then do the two things that pay off regardless: extract the domain layer, and move history and facts into tables you own. After that the migration is adapters plus middleware, written beside the originals. Cut over per tenant behind a flag with the eval suite as arbiter. Write filter behaviour as framework-agnostic tests first. Change one variable at a time.

*Related: [Agent frameworks compared](https://shirokoff.ca/blog/agent-frameworks-compared) · [Agent state you own](https://shirokoff.ca/blog/agent-state-portability) · [Azure AI Foundry classic vs new](https://shirokoff.ca/blog/azure-ai-foundry-classic-vs-new) · [Evaluating LLM and agent systems](https://shirokoff.ca/blog/evaluating-llm-agent-systems) · [What MCP standardizes](https://shirokoff.ca/blog/model-context-protocol)*
