TAI BUI
Back to curriculum
LearnPython

The Multi-Agent Primitive Model

Multi-agent frameworks like AutoGen, LangGraph, CrewAI, the OpenAI Agents SDK, and Microsoft Agent Framework look different, but they sit in the same four-dimensional design space: the agent, the handoff, the shared state, and the orchestrator. You build those four from scratch, run a toy system on them, and map each major framework onto the same axes so a new release takes one paragraph to read.

TIME: ~60 minutesPREREQUISITES: Phase 14 (Agent Engineering), Phase 16 · 01 (Why Multi-Agent)

Learning Objectives

  • Name the four primitives (agent, handoff, shared state, orchestrator) and say which one is stateful.
  • Map OpenAI Swarm, AutoGen, CrewAI, LangGraph, Microsoft Agent Framework, and Google ADK onto the four primitives.
  • Compare static, handoff-driven, LLM-selected, and queue-driven orchestrators and pick one for a given routing requirement.
  • Run a stdlib toy system and point to where in the code the next-speaker decision lives.

Problem

A new multi-agent framework ships every six months. AutoGen in 2023. CrewAI in 2024. LangGraph and OpenAI Swarm in 2024. Google ADK in April 2025. Microsoft Agent Framework RC in February 2026. Each press release claims to be "the right abstraction."

If you try to learn them one at a time you will burn out. The APIs look different, and the docs disagree about what an "agent" is. One framework calls its shared memory a "blackboard," another a "message pool," a third a "StateGraph." It starts to feel like the field is churning.

It isn't. Under the marketing, the same four primitives keep showing up. Learn them once and a new framework takes one paragraph to read.

Warm-up0/3
1. In the Phase 14 ReAct loop, what happens right after the model emits an Action?Warm-up
A Thought box and an Action box joined by an arrow, then a dashed box marked with a question mark, with a dotted arrow looping back toward Thought.
2. Which situation from Phase 16 · 01 is a good reason to split one agent into several?Warm-up
One agent box with one system prompt, dotted lines fanning out to five unrelated job tags (book travel, answer billing, write code, schedule calls, triage tickets) stacked past a dashed context-window line, with a note asking whether to split the agent.
3. Which orchestration pattern from Phase 16 · 01 has one agent routing work to specialists?Warm-up
A one-column list of five pattern names from Phase 16, 01: single agent, pipeline, parallel fan-out, supervisor, hierarchical, with a note asking which one routes work to specialists.
Check: Problem0/1
1. A new multi-agent framework ships every few months, each with different APIs and vocabulary. Why does learning them one at a time waste effort?Explain
A timeline of framework releases with the lesson's own dates, AutoGen 2023, CrewAI 2024, LangGraph and OpenAI Swarm 2024, Google ADK April 2025, Microsoft Agent Framework RC February 2026, above a card asking whether studying each one separately pays off.

Concept

The four primitives

  1. Agent: a system prompt plus a tool list. It is stateless, so each run starts from its system prompt and the current message history.
  2. Handoff: a structured transfer of control from one agent to another. Mechanically it is a tool call that returns a new agent, or a graph edge that follows a condition.
  3. Shared state: any data structure that more than one agent can read (and sometimes write). A message pool, a blackboard, a key-value store, or vector memory.
  4. Orchestrator: whatever decides who speaks next. The options are an explicit graph (deterministic), an LLM speaker-selector (soft), the last speaker's handoff call (OpenAI Swarm), or a scheduler over a queue (swarm architecture).

That is the design space. Each framework picks defaults for the four axes, and the rest is surface syntax.

How every 2026 framework maps to it

FrameworkAgentHandoffShared stateOrchestrator
OpenAI Swarm / Agents SDKAgent(instructions, tools)tool returns Agentcaller's problemthe LLM's next handoff call
AutoGen v0.4 / AG2ConversableAgentspeaker-selector on GroupChatmessage poolselector function (LLM or round-robin)
CrewAIAgent(role, goal, backstory)Process.Sequential / HierarchicalTask outputs chainedmanager LLM or static order
LangGraphnode functiongraph edge + conditionStateGraph reducerthe graph, deterministic
Microsoft Agent Frameworkagent + orchestration patternspattern-specificthread / contextpattern-specific
Google ADKagent + A2A cardA2A taskA2A artifactshost decides

The surface differences look big. Underneath, they are the same four knobs.

Why this matters

Once you see the primitives, framework comparison becomes a short checklist:

  • Does the orchestrator trust the LLM to route (Swarm) or does it pin routing in code (LangGraph)?
  • Is shared state full-history (GroupChat) or projected (StateGraph reducer)?
  • Can agents modify each other's prompts (CrewAI manager) or only hand off (Swarm)?

Those three questions answer 80% of which framework fits a given problem. You stop shopping for "the best multi-agent framework" and start designing around the axis you care about.

The stateless insight

Apart from shared state, the primitives are stateless. An agent is a function of (prompt, tools), a handoff is a function call, and an orchestrator is a scheduler. The only stateful part of the system is shared state. That is where the interesting bugs live: memory poisoning (Lesson 15), message ordering, versioning, write contention.

Frameworks that hide shared state (Swarm) push the problem to the caller. Frameworks that centralize it (LangGraph checkpoint, AutoGen pool) make it inspectable, but the coordination cost moves onto the shared-state implementation.

Anatomy of a single primitive

Agent

Agent = (system_prompt, tools, model, optional_name)

No memory and no state of its own. Two agents with the same system prompt and tools are interchangeable. What looks like per-agent state actually lives in shared state or the handoff protocol.

Handoff

Handoff = (from_agent, to_agent, reason, payload)

Three implementations dominate:

  • Function return: the tool returns the next agent. This is the OpenAI Swarm pattern, and agents carry routing in their tool schemas.
  • Graph edge: the LangGraph way. Edges are declarative. The LLM produces a value and a condition selects the next node.
  • Speaker selection: AutoGen GroupChat. A selector function (sometimes itself an LLM call) reads the pool and picks who speaks next.

Shared state

SharedState = { messages: [], artifacts: {}, context: {} }

At minimum it is a list of messages. Often it is more: structured artifacts (CrewAI Task outputs), typed context (LangGraph reducers), external memory (MCP, vector DB).

Two topologies: full pool (every agent sees every message) and projected (agents see a role-scoped view). Full pools are simple and scale badly. Projected pools scale, but you have to design the schema up front.

Orchestrator

Orchestrator = ({state, last_speaker}) -> next_agent

Four flavors:

  • Static: the graph is fixed at build time (LangGraph deterministic, CrewAI Sequential).
  • LLM-selected: an LLM reads the pool and picks the next speaker (AutoGen, CrewAI Hierarchical).
  • Handoff-driven: the current agent decides by calling a handoff tool (Swarm).
  • Queue-driven: workers pull from a shared queue, with no explicit next speaker (swarm architectures, Matrix).

What changes between frameworks

With the primitives fixed, the remaining design decisions are:

  • Memory strategy: ephemeral or durable checkpointing (LangGraph checkpointer).
  • Safety boundary: who can approve a handoff (human-in-the-loop).
  • Cost accounting: per-agent token budgets.
  • Observability: tracing handoffs and persisting state for replay.

You can implement each of these on top of the primitives. None of them is a new primitive.

Check: Concept0/23
1. How many primitives underlie every 2026 multi-agent framework in the primitive model?Define
Six framework name tags (AutoGen, CrewAI, LangGraph, Swarm, MS Agent Framework, Google ADK) above a card asking how many design axes they all actually share, with a numbered blank list that trails off with an ellipsis.
2. What is an Agent reduced to in the primitive model?Define
The concrete Agent object for the researcher from main.py, with fields name, system_prompt and policy, and a dashed box asking whether this object remembers anything between separate runs.
Code is from main.py's make_team().
3. What four fields does the Handoff formula contain?Define
A dict returned by researcher_policy holding content and a handoff key naming the writer, above a card showing the Concept section's Handoff formula with four blank fields.
Code dict is from main.py; the formula's field names are left blank.
4. What is the only stateful part of a multi-agent system in the primitive model?Define
Three agent boxes, researcher, writer and reviewer, each with an arrow down to an orchestrator box, which feeds a message pool box below it, and a note asking which of the four pieces still holds data once a run ends.
5. What is the orchestrator in the primitive model?Define
Three agent boxes, researcher, writer and reviewer, each with a dotted line down to one dashed box asking what decides who runs next.
6. What is a message pool?Define
Three agent boxes over a box labelled message pool, connected by dotted lines, with a dashed box asking what each agent sees of the pool.
7. What is projected state?Define
Three role-named agent boxes, billing agent, support agent and sales agent, over a box labelled projected state, connected by dotted lines, with a dashed box asking whether each role sees the same slice or a different one.
8. What is speaker selection?Define
A message-pool box feeding a dashed selector box, which feeds a next-agent box, with a note that AutoGen GroupChat works this way.
9. Which of these is NOT one of the three handoff implementations?Explain
A researcher box with an arrow to a writer box, with a dashed box under the arrow asking for the handoff mechanism, and a note that three real mechanisms implement a handoff.
10. In the framework-mapping table, what does CrewAI use as its orchestrator?Explain
The framework-mapping table skeleton with every cell blank, the CrewAI row highlighted and its Orchestrator cell marked with a dashed question mark.
11. In the framework-mapping table, what is Google ADK's shared-state column?Explain
The framework-mapping table skeleton with every cell blank, the Google ADK row highlighted and its Shared state cell marked with a dashed question mark.
12. In the framework-mapping table, what does AutoGen v0.4 / AG2 use for its Agent and Shared state columns?Explain
The framework-mapping table skeleton with every cell blank, the AutoGen v0.4/AG2 row highlighted and its Agent and Shared state cells each marked with a dashed question mark.
13. In the framework-mapping table, how does LangGraph implement handoff and orchestrator?Explain
The framework-mapping table skeleton with every cell blank, the LangGraph row highlighted and its Handoff and Orchestrator cells each marked with a dashed question mark.
14. In the framework-mapping table, what does Microsoft Agent Framework use for handoff and shared state?Explain
The framework-mapping table skeleton with every cell blank, the Microsoft Agent Framework row highlighted and its Handoff and Shared state cells each marked with a dashed question mark.
15. In the framework-mapping table, what does OpenAI Swarm / Agents SDK use for its Agent definition and orchestrator?Explain
The framework-mapping table skeleton with every cell blank, the OpenAI Swarm row highlighted and its Agent and Orchestrator cells each marked with a dashed question mark.
16. Which of these is one of the three questions that answer 80% of "which framework fits"?Explain
A card titled three questions the lesson says answer 80 percent of it, with three blank numbered lines, and a note that none of them are about streaming, vector databases or cloud hosting.
17. What happens when a framework like OpenAI Swarm hides shared state entirely?Explain
Two agent boxes inside a box labelled framework boundary, with a dashed red box below it outside the boundary labelled shared state kept outside the framework's own boundary, and a dashed question box asking what happens to coordination.
18. What is the queue-driven orchestrator flavor?Explain
A shared queue box holding three task cards, with two worker boxes connected to it by dotted lines, and a dashed box asking who decides which worker goes next.
19. Beyond the four primitives, which four decisions still vary between frameworks?Explain
A box labelled core design fixed by the primitive model, with an arrow up to four dashed boxes each marked with a question mark, and a note asking what the four decisions above the core design are.
20. In a Swarm-style system, a billing agent asks the customer for details that the previous agent already collected. Which primitive's design explains this?Analyze
A support agent box that collects details handing off to a billing agent box, with a note that the billing agent asks for the same details again, and a dashed box asking which primitive's design explains the repeat question.
21. A team of twelve agents shares one full message pool. Prompts and token cost keep growing, yet each role needs only a small slice of the data. What is the diagnosis?Analyze
Twelve small agent circles in two rows, each dotted-lined to one message-pool box, above a dashed line marked token cost per prompt rising with every added agent, and a note that each role only needs a small slice, asking for the diagnosis.
22. Two agents share a prompt and tool list, yet the writer sometimes sees stale facts after a handoff. Where should you look first?Analyze
Two boxes, agent A which writes a fact and agent B the writer which reads it later and finds it stale, both with arrows into a dashed box about writes landing out of order with no version check, and a question asking where to look first.
23. You must require human approval before any handoff to a payments agent. How does this fit the primitive model?Design
An agent box with an arrow through a dashed box asking human approval, before reaching a payments agent box, and a note asking whether requiring approval is a new fifth primitive or something else.

Build It

code/main.py implements the primitives in about 170 lines of stdlib Python. There is no real LLM. Each agent is a scripted policy, so the focus stays on the coordination structure.

The file defines:

  • Agent: a dataclass of name, system prompt, and a policy function that reads the shared state and returns a message. The tool list from the model above is left out because the policies never call tools.
  • SharedState: a message pool behind a lock, with append, snapshot, and last_by.
  • Handoff: not a class here. Each message may carry a handoff key naming the next agent, or "done".
  • Three orchestrators: StaticOrchestrator, HandoffOrchestrator, and LLMSelectorOrchestrator. The selector in the last one is a scripted round-robin function standing in for an LLM call.

The demo runs the same three-agent pipeline (researcher, writer, reviewer) through each orchestrator and prints the message pool after each run. The agents and the shared state are the same across runs. Only the code that picks the next speaker changes.

Run it:

python3 code/main.py

Expected output: three message pools, one per orchestrator. With these scripted policies the three pools come out the same, because the researcher hands off to the writer on its first turn and the writer hands off to the reviewer. That is the point of the demo. Swap in a policy that hands off differently (say a researcher that returns "done" immediately) and the handoff-driven run stops early while the static run still visits every agent. That gap is the LLM-routing tradeoff in miniature.

Check: Build It0/8
1. What does `main.py` use in place of a real LLM for each agent, and why?Explain
The Agent dataclass from main.py with fields name, system_prompt and policy, and a dashed box noting that no field calls a hosted model and asking what plays that role.
Code is from main.py.
2. Which three orchestrators does `main.py` implement, and what do they share?Explain
Three boxes labelled Orchestrator A, B and C, each holding the same team of researcher, writer and reviewer and the same one message pool, each with a dashed picker slot marked with a question mark.
3. In the handoff-driven orchestrator in `main.py`, where does the next-speaker decision come from?Explain
The body of HandoffOrchestrator.run from main.py with every line shown except the line that computes nxt, which is replaced with a question mark.
Code is from main.py; one line is redacted.
4. When you run `main.py` as shipped, how do the three message pools compare?Apply
Three orchestrator calls from the shipped main.py, StaticOrchestrator, HandoffOrchestrator and LLMSelectorOrchestrator, each run on a fresh team and state, with a dashed box asking how the three resulting pools compare.
Code is from main.py's main().
5. `researcher_policy` is changed to return handoff "done" on its first turn. How many messages are in the pool afterwards under `StaticOrchestrator(["researcher", "writer", "reviewer"])` and under `HandoffOrchestrator("researcher")`?Apply
An edited researcher_policy that always hands off done, above two dashed boxes asking how many messages end up in the pool under StaticOrchestrator with the full order and under HandoffOrchestrator starting at researcher.
Edited policy is a hypothetical change to main.py.
6. `StaticOrchestrator(["researcher", "writer", "reviewer"]).run(team, state, max_steps=2)` is called. What does the pool hold afterwards?Apply
A call to StaticOrchestrator with the full three-name order but max_steps set to 2, and a dashed box asking what the pool holds afterwards.
Code calls main.py's real classes.
7. `StaticOrchestrator(["reviewer", "writer"])` runs on an empty pool. What does the reviewer's message say?Apply
A call to StaticOrchestrator with the order reviewer then writer on a fresh, empty pool, and a dashed box asking what the reviewer's message says.
Code calls main.py's real classes.
8. With the same three agents, a Swarm-style run ends after the researcher's first message while a static run visits all three. What explains the difference?Analyze
Two code panels side by side, StaticOrchestrator.run which never reads the handoff key, and HandoffOrchestrator.run which reads nxt from the handoff key and returns on done, with a dashed box asking why static visits all three agents while the handoff-driven run stops after one.
Code is from main.py.

Use It

outputs/skill-primitive-mapper.md is a skill that reads a multi-agent codebase or framework doc and returns the four-primitive mapping. Run it on a new framework release to get a one-paragraph understanding before you read the docs in depth.

Ship It

Before adopting a new framework, write its primitive mapping. If you can't, either the docs are incomplete or the framework is inventing a fifth primitive (rare, so check for a shared-state flavor you haven't seen).

Pin the mapping in your architecture doc. When someone new joins the team, send them the mapping before the API docs. When framework versions change, diff the mapping instead of the changelog.

Check: Ship It0/2
1. What does Ship It say to do before adopting a new multi-agent framework?Design
A box labelled new release above a card with two blank, unchecked checklist lines for what to do before adopting it, and a note asking what it means if the first box can't be checked.
2. A support bot must route refund requests to a billing agent, and compliance requires the same route on every run. Which orchestrator fits best?Design
A support bot handing a refund request to a billing agent, a note that compliance requires the same route every single run, four neutral chips naming the candidates static, LLM-selected, handoff-driven and queue-driven, and a note asking which fits best.
Author's Note

I'd read the classes in main.py before the framework table. SharedState is the only one that holds anything between turns: a list of messages behind a lock. The agents are plain objects with a policy function, and the orchestrators hold only their routing config (an order, a start agent, a selector). The step limit is just an argument to run. That matches the stateless point in the Concept section, and it shows up in the code.

My bet is that the three orchestrators are worth comparing line by line. Each one is a short run method, and the only difference is where the next speaker comes from: a fixed list, the handoff key on the last message, or a selector function. Everything else stays the same, so you can see that routing is a swappable piece.

I'd also keep the toy in proportion. Handoff is just a dictionary key here and the selector is a round-robin function, so the output tells you nothing about how an LLM would route. I'd treat the demo as a map of where the decision lives, then try the exercises with policies that disagree with each other.

Exercises

  1. Run code/main.py three times with different agent policies. Observe how the orchestrator choice changes which agents run.
  2. Implement a fourth orchestrator type: a queue-driven one where agents poll shared state for work. What deadlock can happen, and how do you detect it?
  3. Take the LangGraph quickstart (https://docs.langchain.com/oss/python/langgraph/workflows-agents) and rewrite it as the four primitives. Which of LangGraph's abstractions map 1:1 and which are convenience wrappers?
  4. Read the OpenAI Swarm cookbook (https://developers.openai.com/cookbook/examples/orchestrating_agents). Identify which of the four primitives Swarm makes most ergonomic, and which one it pushes to the caller.
  5. Find one framework in this table that hides shared state entirely. Explain what breaks when agents need to coordinate across handoffs without re-reading history.

Key Terms

TermWhat people sayWhat it actually means
Agent"An LLM with tools"A (system_prompt, tools, model) triple. Stateless.
Handoff"Transfer of control"A structured call that names the next agent and optional payload. Three implementations: function return, graph edge, speaker selection.
Shared state"Memory" / "context"The only stateful part of a multi-agent system. Message pool or blackboard.
Orchestrator"Coordinator"Whoever decides who runs next. Static graph, LLM selector, handoff-driven, or queue-driven.
Primitive"Abstraction"One of the four axes every framework parameterizes. Not a framework feature.
Message pool"Shared chat history"Full-history shared state. Easy to reason about, scales badly.
Projected state"Scoped view"Role-specific view into shared state. Scales, requires schema design.
Speaker selection"Who talks next"Orchestrator pattern where a function (often an LLM) picks the next agent from a group.

Further Reading

Code

Ships With