Microsoft used to hand you two agent frameworks and let you guess which one to bet on. Semantic Kernel gave you enterprise plumbing (sessions, telemetry, filters) for a single agent. AutoGen gave you multi-agent orchestration patterns, born out of research, that were fun to prototype and painful to run in production. In October 2025 Microsoft merged them into one thing: Microsoft Agent Framework (MAF). It hit 1.0 GA in April 2026, and this month the Agent Harness and Foundry Hosted Agents (the pieces that turn an agent into a real deployed service) reached GA too. That’s a fast maturity curve for something built from two frameworks’ worth of legacy decisions, so I spent time going through the architecture end to end to see if the plumbing actually holds together.
It does, more than I expected. Here’s the full picture — three layers, one repeated design pattern, and where the seams still show.
The shape: three layers, one repeated pattern
MAF ships in both Python and .NET, with intentionally mirrored (not identical) APIs, and stacks cleanly into three layers:
┌─────────────────────────────────────────────────────────────┐
│ Layer 3 — Multi-Agent Orchestration ("Workflows") │
│ Graph of Executors + Edges, superstep execution, │
│ checkpointing, human-in-the-loop request ports │
└─────────────────────────────────────────────────────────────┘
▲ wraps
┌─────────────────────────────────────────────────────────────┐
│ Layer 2 — Single Agent │
│ Agent → Session (durable state) → ChatClient → │
│ Tools/Approval, Middleware, Context Providers, Compaction │
└─────────────────────────────────────────────────────────────┘
▲ wraps
┌─────────────────────────────────────────────────────────────┐
│ Layer 1 — Provider & Protocol Ecosystem │
│ Any LLM provider, MCP (tools), A2A (agent-to-agent), │
│ AG-UI (agent-to-frontend), memory/vector backends │
└─────────────────────────────────────────────────────────────┘
The mental model that matters more than any individual feature: everything is composition via decorators, not inheritance. A single agent and the entire orchestration engine are both built the same way — a minimal core wrapped in independently pluggable layers for middleware, telemetry, approval, and isolation.
# Python: Agent = middleware layer wrapping a telemetry layer wrapping the raw core
class Agent(AgentMiddlewareLayer, AgentTelemetryLayer, RawAgent[OptionsCoT]): ...
// .NET: the same idea, expressed as a fluent builder
var agent = client.AsAIAgent(model, instructions)
.AsBuilder()
.Use(FunctionCallMiddleware)
.Use(PIIMiddleware)
.Build();
Almost every cross-cutting production concern — auth, PII redaction, guardrails, approval, compliance logging — gets added by wrapping, never by subclassing or editing core code. Once that clicks, the rest of the architecture reads as variations on it.
Layer 1: the single agent core
An Agent (Python) or AIAgent (.NET) orchestrates a ChatClient — the provider-agnostic interface every LLM implements — plus tools, middleware, and context providers. Session state is deliberately dumb: AgentSession is a plain, JSON-serializable state bag with no behavior of its own. Behaviors live on the agent; the session just carries data forward, which is what makes it trivial to serialize, hand to another process, and resume later.
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
agent = Agent(
client=FoundryChatClient(credential=AzureCliCredential()),
name="HaikuAgent",
instructions="You are an upbeat assistant that writes beautifully.",
)
result = await agent.run("Write a haiku about Microsoft Agent Framework.")
Tool approval is the most interesting reliability decision in this layer. Instead of an in-process callback, an approval request is a typed content object (FunctionApprovalRequestContent) that flows through the normal message channel. The rationale, straight from the design doc: a callback leaves the agent “deep in the call stack” where it can’t be suspended and resumed — which breaks the moment the agent is hosted remotely or the process restarts mid-approval. Typed content survives a process boundary; a callback doesn’t.
result = await agent.run("Weather in Seattle?")
while result.user_input_requests:
responses = [Message("user", [req.to_function_approval_response(
input("Approve? (y/n): ").lower() == "y")]) for req in result.user_input_requests]
result = await agent.run(responses)
Three middleware types wrap different scopes — agent middleware around the whole run, function middleware around each tool call, chat middleware around each model call — and this is genuinely where you hook in production concerns rather than scattering them through business logic.
Two things worth flagging before you ship: structured output keeps both a raw schema knob and a typed convenience method, but explicitly does not ship a second-LLM-call coercion step for non-conforming output — that’s a sample pattern, not a supported feature, so you own it if you need it. And chat-history persistence defaults to atomic, once-per-run — meaning a crash mid-tool-call-loop loses in-flight progress. There’s an opt-in flag (RequirePerServiceCallChatHistoryPersistence) that persists after every model round-trip instead, at the cost of extra write overhead. It’s not the default, and it should be a deliberate choice for any agent that makes multiple tool calls per turn.
Layer 2: the Workflows engine — where MAF actually earns its name
This is the layer AutoGen users are here for, and it’s the most technically distinct thing in the framework. Multi-agent orchestration is a graph of message-passing nodes executed in synchronized supersteps, not a bolted-on runtime: the same Pregel/bulk-synchronous-parallel model behind systems like Google Pregel and Azure Durable Task. Every high-level pattern is just a pre-wired topology over the same primitives; there’s no separate code path per pattern.
| Pattern | Mechanism | Reach for it when |
|---|---|---|
| Sequential | Chain of agents linked by direct edges | A fixed pipeline (draft → edit → publish) |
| Concurrent | Fan-out to N agents, true barrier fan-in with a pluggable aggregator | Independent sub-tasks that don’t depend on each other |
| Handoff | Each agent gets a handoff_to_<agent> tool; a switch edge routes on request |
Support-style triage needing specialist transfer |
| Group chat | Hub/star topology; a manager picks next speaker and decides termination | Peer review/debate loops with a moderator |
| Magentic | An LLM manager builds a plan, then a progress ledger decides who speaks next each round | Open-ended tasks where the plan isn’t known up front |
Workflow workflow = new WorkflowBuilder(start)
.AddFanOutEdge(start, [physicist, chemist])
.AddFanInBarrierEdge([physicist, chemist], aggregate)
.WithOutputFrom(aggregate)
.Build();
Complexity and cost climb down that table, and so does autonomy — which is why Magentic ships with a human plan-approval gate on by default. That default is worth keeping unless you have a specific reason not to.
Checkpointing isn’t bolted on either — after every superstep the runner exports the full topology, executor state, and in-flight edge state (so fan-in barriers can resume holding partial input correctly). Because every superstep produces its own addressable checkpoint, you can rehydrate a brand-new workflow instance from any arbitrary past checkpoint — genuine deterministic replay, not just crash recovery. Human-in-the-loop rides the same mechanism: a RequestPort halts the superstep loop until an external response arrives, so a host can end its turn at that halt (a web request/response cycle, say) instead of blocking a thread.
Layer 3: providers and protocols
The provider abstraction is the literal mechanism behind “swap the model vendor, keep the agent code”:
from agent_framework import Agent
from agent_framework.anthropic import AnthropicClient # swap this line...
# from agent_framework.openai import OpenAIChatClient
# from agent_framework.bedrock import BedrockChatClient
agent = Agent(client=AnthropicClient(), instructions="You are a helpful assistant.")
OpenAI, Azure OpenAI, Microsoft Foundry, Anthropic, Bedrock, Gemini, Mistral, Ollama, and GitHub Copilot all implement the same chat-client interface, composed through the same middleware/telemetry stack regardless of vendor.
Above the model layer, three protocols solve three distinct integration problems, and MAF speaks all of them rather than picking one:
| Protocol | Solves | MAF role |
|---|---|---|
| MCP | Agent ↔ its own tools | Both client (built into core) and server |
| A2A | Agent ↔ another agent, cross-vendor | Client (A2AAgent) and server bridge |
| AG-UI | Agent ↔ frontend, streaming events | Python hosting glue; .NET moved the protocol layer to a third-party SDK |
If you’ve read my A2A write-up or the A2UI piece, this is where those protocols plug in — MAF doesn’t reinvent agent-to-agent interop, it consumes the existing open standards.
Memory splits into a clean three-tier taxonomy worth keeping straight: short-term session/thread state (Redis, Cosmos), long-term semantic memory that generalizes across threads (mem0, Cosmos Memory Toolkit), and RAG over external corpora (Azure AI Search). Conflating memory with RAG is a real footgun — one is “what the agent learned about a user,” the other is “grounding in a document corpus at query time,” and using the wrong one produces either bloated stale context or missing recall. Worth noting: a generic vector-store abstraction with 13+ planned connectors is documented but not yet built — only OpenAI/Azure embeddings ship today, so real RAG right now means the Azure AI Search context provider, not the aspirational generic layer.
Security, governance, and multi-tenancy
Three things stood out enough to flag for anyone evaluating this seriously:
- FIDES, MAF’s prompt-injection defense, labels content by trust/confidentiality and routes untrusted data through variable indirection — the model sees a reference, not the raw payload — instead of relying on “ignore instructions in the following content” prompt engineering, which the framework’s own authors call out as bypassable and unverifiable. It’s a real, tested implementation, but its design doc is still status “proposed,” not accepted. Treat it as preview.
- Multi-tenant isolation is hardened specifically for Foundry-hosted deployments — physical per-tenant storage partitioning, identity sourced only from platform-injected context, fail-closed outside the hosted environment. If you self-host, none of that is automatic: the framework hands you an explicit checklist (authenticate before trusting any protocol-supplied id, bind ids to the authenticated principal, treat wire-sourced session ids as untrusted) and expects you to implement it.
- Purview integration gets you DLP/audit/eDiscovery over agent traffic, but it’s licensed (M365 E5) and preview on both language sides — a real enterprise compliance answer, not a free capability.
Deployment and scaling
Turning an agent into a Foundry-hosted service is two lines in .NET, and Foundry manages compute, identity, and session storage for you. Self-hosting is where the two languages genuinely diverge in philosophy: .NET ships batteries-included ASP.NET Core hosting; Python ships small conversion-only packages and leaves routing, auth, and storage to your app. That’s a deliberate choice tied to the framework’s “shared responsibility” security model, not an oversight — but it means the Python self-hosting samples ship with explicit no-auth warnings you have to close yourself.
One thing worth knowing before you plan around it: durable, crash-resilient execution isn’t in this repo at all. Azure Functions and Durable Task integrations were pulled out into a separate agent-framework-durable-extension repo to keep heavyweight dependencies off the core release cadence. If exactly-once or crash-resilient long-running orchestration is a hard requirement, that extension is a separate evaluation, not something you get by default.
My Take on Microsoft Agent Framework
The composition-over-inheritance mental model is the right call, and it’s consistent enough that once you internalize it, unfamiliar parts of the API stop feeling unfamiliar — that’s a rarer property in frameworks than it should be. The Workflows engine is the real substance here: building multi-agent orchestration on a Pregel-style superstep model instead of a bespoke actor runtime gets you genuine time-travel replay almost for free, and that’s not something I’ve seen executed this cleanly elsewhere.
Two things I’d push back on before betting production infrastructure on this today. First, durable execution living in a separate, differently-paced repo is a real gap for anyone who assumed “production-ready framework” meant crash-resilient by default — it doesn’t, and you need to evaluate that extension on its own maturity timeline. Second, the framework is unusually honest about what’s preview versus shipped (FIDES, the generic vector-store layer, CodeAct sandboxing), which I respect, but it also means the marketing-friendly feature list and the actually-safe-to-build-on feature list are two different lists, and you have to read the ADRs to know which is which.
Given the pace from October 2025 announcement to April 2026 GA to Harness/Hosted Agents GA this month, the gaps are closing fast. If you’re currently on Semantic Kernel or AutoGen, this is the maintained path forward and the migration guides exist for a reason — don’t keep building on a framework Microsoft has already put into maintenance mode. If you’re evaluating fresh, the core agent model and the Workflows engine are solid enough to build on now; just don’t assume durability, FIDES, or the vector-store layer are as finished as the rest.
Further Reading and Sources
- github.com/microsoft/agent-framework — the source repo, ADRs, and samples referenced throughout this post
- Microsoft Agent Framework overview — Microsoft Learn — official docs
- Semantic Kernel and Microsoft Agent Framework — devblogs.microsoft.com — the framing on why the merger happened
- Migrate your Semantic Kernel and AutoGen projects — devblogs.microsoft.com — the migration path if you’re on either predecessor
- Microsoft Agent Framework at Build 2026 — devblogs.microsoft.com — the Harness, hosted agents, and orchestration announcements
- Microsoft Agent Framework Harness and Hosted Agents reach GA — InfoQ — this month’s GA milestone
- /posts/a2a-agent-to-agent-protocol/ — my deep dive on A2A, which MAF consumes for cross-agent interop
- /posts/a2ui-agent-to-user-interface-protocol/ — my write-up on A2UI, the other protocol MAF plugs into
If you’re building on MAF in production — not just the Foundry quickstart — I want to know what broke first: the durable-execution gap, the self-hosting isolation checklist, or something the ADRs didn’t warn you about. Find me on X: @mikezupper, or email [email protected].
Two frameworks merged into one, and the seams are mostly in the places they told you to expect them.
