« Phase 23 README · Warmup · Hitchhiker's · Deep Dive · Principal Deep Dive · Staff Notes

Phase 23 — Core Contributor Notes: AutoGen & the Microsoft Agent Framework

Written from inside the codebase. The labs are faithful miniatures; this is where they diverge from the real thing, why the real thing is shaped that way, and which simplifications are load-bearing versus incidental. If a specific detail is not something I can state with certainty, I describe the pattern rather than invent an API.

The v0.2 → v0.4 rearchitecture: why the loop had to die

Early AutoGen (v0.2) was a monolithic conversation loop. The GroupChat, the chat manager, the agents, and the orchestration were tightly coupled, largely synchronous, single-process. It was superb for a notebook demo and structurally hostile to a production service: you could not cleanly distribute agents, you could not observe the run except by printing, you could not extend the orchestration without forking the manager, and error handling was "the loop throws and dies."

v0.4 (early 2025) was a ground-up rewrite around an actor model / event-driven runtime, split into layers that a maintainer keeps rigorously separate:

  • autogen-core — the foundation. An asynchronous message-passing runtime where agents are actors: isolated state, no shared memory, communicate only by sending/receiving typed events. This is the Erlang/Akka lineage applied to agents. Everything reliability-shaped lives here because it is the only layer that can own it — delivery, mailboxes, subscription/routing of events, the seam for distribution.
  • autogen-agentchat — the ergonomic team layer most people actually import: AssistantAgent, UserProxyAgent, RoundRobinGroupChat, SelectorGroupChat, the termination family. This is deliberately a thin opinionated layer over core; the team loop is sugar over "actors exchanging messages under a scheduler."
  • autogen-ext — model clients (OpenAI, Azure OpenAI, others), tools, and integrations. Kept separate so a new provider is a new package, not a core change.

The maintainer's reason this layering matters: it lets the ergonomic API and the runtime evolve independently, and it makes the framework a thin layer over a message-passing substrate — which is exactly the property MSAF reuses and LangGraph independently arrives at. The lab compresses all three layers into one synchronous loop because the lab's job is to teach selection + termination as the coordination model, not to rebuild an async actor scheduler. That compression is the single biggest simplification in Lab 01, and it is honest: the coordination semantics are identical; only the substrate is collapsed.

Why Microsoft merged AutoGen and Semantic Kernel — and what each brought

Two teams inside one company were solving adjacent problems. AutoGen (Microsoft Research) had the best agent and multi-agent abstractions — simple AssistantAgent, teams, and after v0.4 a real actor runtime — but a thin enterprise story (Python-first, lighter state/telemetry/governance) and, crucially, no explicit-orchestration answer: everything was open-ended chat. Semantic Kernel (product side) had the best enterprise posture — typed plugins/functions with schemas, session/thread state, filters/middleware, OpenTelemetry, .NET-first — but a weaker multi-agent story. Customers were being asked to choose between, or glue together, two overlapping Microsoft frameworks.

The Microsoft Agent Framework (late 2025) is the merge, by the same teams. It takes:

  1. AutoGen's agent abstractions — the descendants of AssistantAgent and the message-passing runtime carry over as the agent layer.
  2. Semantic Kernel's enterprise features — typed functions, session-based thread state, middleware, and telemetry become first-class.
  3. A new graph-based Workflow engine — the thing neither parent had: explicit, typed, checkpointed, human-in-the-loop orchestration. This is the deliberate answer to "AutoGen's open-ended chat is un-auditable for the 90% of tasks with a known structure."

It ships across .NET, Python, and Go. The one-sentence maintainer summary: MSAF = AutoGen's simplicity + SK's enterprise plumbing + graph Workflows, and AutoGen/SK are now lineage — still usable, but new investment lands in MSAF.

Termination conditions are stateful and async — the lab's biggest honest simplification

This is the divergence a committer would flag first. Lab 01 models termination conditions as pure functions of the whole transcript: terminate(messages) -> reason | None, recomputed from scratch each turn. The real autogen-agentchat conditions are stateful objects, and the difference is not cosmetic:

  • Each condition is fed the delta — the new messages produced this turn — not handed the entire transcript to re-scan. It is a streaming predicate, not a batch one.
  • Each remembers whether it has fired. This is what makes & (AND) work across turns: a "reviewer approved AND at least three messages" condition has to accumulate the fact that approval happened on turn two while the message count crosses three on turn four. A pure function of the current transcript can approximate this, but the real object is genuinely stateful.
  • Each is async and needs an explicit reset() between runs, because a condition instance is reused across run calls and must clear its fired-state or the second run terminates on stale memory.

The lab drops all three because a pure transcript function keeps the composition algebra (| / & / reason strings) exact while staying deterministic and trivially testable — and the algebra is the thing worth teaching. But if you claim to know AutoGen and someone asks "why does a termination condition need reset()?", the answer is "because it's a stateful, delta-fed object that remembers firing, not a pure function" — and that is the tell that you have read the real code, not just the lab.

Type-safe edges are Semantic Kernel's DNA in the Workflow layer

SK's whole posture is "capabilities are strongly-typed functions with schemas, not stringly-typed blobs." MSAF's Workflow edges are that idea applied to orchestration: an edge declares a message_type, and a payload of the wrong type raises a WorkflowTypeError at the wiring. The maintainer distinction the lab enforces, and the one interviewers probe: the type is a contract that errors, entirely separate from the edge's condition, which is control-flow routing. Real MSAF keeps these as different concepts for a reason — if you conflate them (use the type as the router), a type mismatch degrades from a loud error into a silently non-firing edge, i.e. a dropped message. The lab implements them as two methods (Edge.check_type and Edge.fires) precisely to make the separation unmissable.

The super-step runtime, checkpoint store, and RequestInfoExecutor

MSAF's Workflow runtime is Pregel / Bulk-Synchronous-Parallel: a queue of (executor, payload) messages, a super-step that delivers the whole batch then routes outputs into the next batch, bounded by a super-step limit. This is the same design LangGraph uses, because graph-of-actors orchestration converges on it — it is what makes concurrency and checkpointing clean at once.

The checkpoint is the queue plus shared state plus pending requests captured at a super-step boundary; the boundary is atomic, so the snapshot is consistent (no half-run executor). In production this serializes to a checkpoint store (a database / blob storage / an Azure-native layer) so a different process can resume later. The RequestInfoExecutor (HITL) is not a special persistence path — reaching it emits a request event and checkpoints, the run returns paused, and resume(checkpoint, {node: response}) injects the human's answer as that node's output and routes it downstream. The load-bearing implementation fact: HITL and durability are one code path. In the lab, _drive returns a checkpoint whether it paused for a RequestInfoExecutor or for a pause_after crash simulation; that unification is faithful to how the real engine treats "pause for a human" and "pause for a crash" as the same suspend.

Magentic-One's progress ledger

The magentic pattern is named after Microsoft Research's Magentic-One, a generalist multi-agent system. Its defining structure is an orchestrator that maintains a progress ledger — a working memory of facts/plan plus a record of what has been attempted and observed. Each round the manager reasons over the ledger, assigns a subtask to the best specialist, appends the result, and either re-plans or synthesizes. The mechanism the lab reproduces is the explicit progress tracking: the manager observes progress rather than hoping a chat converges, which is what lets it detect being stuck and reassign or reset. Magentic subsumes the simpler patterns — a fixed plan is sequential, parallel assignments are concurrent, a reassignment is a handoff — which is why MSAF ships it as the flagship orchestrator, bounded by max_rounds.

What the stdlib miniatures deliberately simplify

To be precise about the seams, so you can name them:

  • Deterministic "concurrency." The concurrent pattern runs branches in list order, not on real workers — legitimate because the branches are independent, so order cannot change the result. The lab teaches independence-permits-parallelism without a thread pool.
  • Injected policies instead of a model. Every agent is a pure policy(messages) -> str. This is exactly how you unit-test a real AutoGen/MSAF system (record/replay the model), so the orchestration is provably correct independent of what the model says.
  • In-memory checkpoint. The Checkpoint is the right shape; the only missing piece is serialization to a durable store, which the README's Extensions points at.
  • Collapsed async actor runtime. One synchronous loop stands in for autogen-core's asynchronous message passing. Same semantics, no scheduler.

None of these change the concepts; each is chosen so the mechanism stays honest while the incidental machinery (async, distribution, a real DB) is out of scope. Knowing which is which — the algebra is exact, the substrate is compressed — is the core-contributor read.