Multi-agent Patterns
When several coordinated agents beat one: the supervisor/worker, sequential-pipeline, and handoff topologies, how agents communicate, an overview of AutoGen/CrewAI/LangGraph/Semantic Kernel, and the discipline to know that a single well-designed agent usually wins.
- Explain when multiple coordinated agents beat a single agent — and why that's rarer than it looks.
- Build the three core topologies: supervisor/worker, sequential pipeline, and handoff/routing.
- Understand how agents communicate (shared state vs. message passing) and where coordination fails.
- Place AutoGen, CrewAI, LangGraph, and Semantic Kernel on the map.
Python for JavaScript developers: the code here is orchestration glue you already read — functions calling
run_agent(...) (each a fresh agent from earlier topics), lists, and dicts. A "specialist agent" is just an
agent with a focused system prompt and its own tools. No new syntax.
When (and when not) to use multiple agents
A multi-agent system is several LLM agents, each with its own role/prompt/tools, coordinating on a task. It's the natural extension of the sub-agents from the last topic — but coordination has real costs (more LLM calls, more latency, more ways to fail), so the bar is high. Reach for multiple agents when:
- The task decomposes into distinct specialties — research, writing, code, review — where a focused prompt and toolset per role beats one generalist juggling all of them.
- Context isolation genuinely helps — each agent keeps a clean, on-task context (the poisoning problem from Topic 6).
- Roles need different tools/permissions — e.g. a "writer" with no DB access and a "DBA" agent that has it.
Default to a single agent with good tools. Most tasks that look like they need a team are handled better by one well-designed agent, or one orchestrator with sub-agents (Topic 6). Multi-agent adds coordination overhead, non-determinism, and debugging pain — and agents talking to agents can compound errors. Add roles only when a single agent demonstrably can't keep the task coherent.
Topology 1: supervisor / worker
The most common and robust pattern: a supervisor agent owns the task, decides which worker (specialist) to invoke, passes it a scoped subtask, and integrates the results. Workers don't talk to each other — all coordination flows through the supervisor (a star topology).
# Each worker is a focused agent: its own system prompt + tools. It returns a concise result.
def researcher(task): return run_agent(sys="Research; return a 3-line summary.", user=task)
def writer(task): return run_agent(sys="Write clear marketing copy from the brief.", user=task)
def editor(task): return run_agent(sys="Tighten and fix the copy; return final text.", user=task)
WORKERS = {"researcher": researcher, "writer": writer, "editor": editor}
def supervisor(goal: str) -> str:
plan = llm(f"Break this goal into steps, each assigned to one of {list(WORKERS)}:\n{goal}") # who does what
state = goal
for step in parse_steps(plan): # supervisor drives the sequence…
worker = WORKERS[step["worker"]] # pick the specialist for this step
state = worker(step["task"] + "\nContext: " + state) # run it; feed its output forward
return state # the integrated final result
The supervisor holds the plan and the only "full picture"; each worker sees just its scoped task. This maps cleanly onto LangGraph (supervisor = a routing node, workers = nodes) with the durability and HITL from Topic 4.
↳ Exercise 1 has you build a supervisor over three workers (researcher → writer → editor) and confirm each worker only ever sees its own scoped subtask, not the others' internals.
Topology 2: sequential pipeline
When the steps are a fixed, known order, skip the dynamic supervisor and wire the agents in a line — each agent's output is the next's input. This is a chain of agents (and, being fixed, is closer to a chain than a true agent system):
def pipeline(brief: str) -> str:
draft = writer(brief) # agent 1: write
edited = editor(draft) # agent 2: edit the draft
checked = fact_checker(edited) # agent 3: verify claims
return checked # fixed order, each stage a specialist agent
Predictable and easy to debug. If the order never changes, a pipeline beats a supervisor — no routing LLM call, no non-determinism. (Same "known steps → don't over-orchestrate" logic as chain-vs-agent in Topic 1.)
↳ Exercise 2 has you build a writer → editor → fact-checker pipeline and compare its predictability to the supervisor version.
Topology 3: handoff / routing
Sometimes one agent should hand off the whole conversation to a specialist — a triage agent routing a support chat to billing, technical, or sales. Unlike supervisor/worker (which integrates results), a handoff transfers control:
def triage(message: str) -> str:
dest = llm(f"Route to one of [billing, technical, sales]:\n{message}").strip() # classify intent
SPECIALISTS = {"billing": billing_agent, "technical": tech_agent, "sales": sales_agent}
return SPECIALISTS.get(dest, general_agent)(message) # hand the conversation to that specialist
This is the pattern behind customer-service agent swarms (and OpenAI's Swarm/Agents SDK "handoff" idiom). Keep routing decisions cheap and auditable — a mis-route sends the user to the wrong specialist.
↳ Exercise 3 has you build a triage router over three specialists and verify each message reaches the right one (including a fallback for ambiguous input).
Other topologies (briefly)
- Hierarchical — supervisors of supervisors; teams of teams for very large tasks. Powerful, hard to debug.
- Group chat / network — agents share one conversation and take turns (AutoGen's classic mode). Flexible but prone to rambling and runaway cost without a strong stop rule.
- Debate / critic — agents argue or critique each other's output to improve quality (e.g. a solver + a verifier). Useful for hard reasoning; expensive.
The framework landscape
| Framework | Flavor | Notes |
|---|---|---|
| LangGraph | graph-based orchestration | multi-agent as nodes over shared state; durable, controllable (Topic 4) |
| AutoGen (Microsoft) | conversational agents | group-chat/network patterns, code execution; research-heritage |
| CrewAI | role-based "crews" | ergonomic roles/tasks/process abstractions; fast to prototype |
| Semantic Kernel (Microsoft) | enterprise/.NET-first | planners + plugins, strong C#/.NET and Azure integration |
They overlap heavily; the choice is usually ecosystem fit (your language, your cloud, LangGraph if you're already there) rather than raw capability. All of them implement the same topologies above.
↳ Exercise 4 has you decide, for four tasks, single-agent vs. multi-agent (and which topology) — the judgment that keeps systems simple.
Pitfalls
- Multi-agent by default. Coordination overhead usually isn't worth it — start single-agent (or orchestrator
- sub-agents) and add roles only when needed.
- Compounding errors. Agents feeding agents propagate mistakes; add verification/critic steps for high-stakes chains.
- Runaway group chats. Free-for-all conversations ramble and burn tokens — enforce turn limits and a clear stop condition.
- Over-orchestrating fixed order. If steps never change, a pipeline beats a dynamic supervisor.
- Opaque routing. Handoff/routing decisions must be cheap and auditable, or mis-routes are hard to diagnose.
Recap
- Multi-agent systems coordinate role-specialized agents — an extension of sub-agents, justified only when a task truly needs distinct specialties, context isolation, or per-role tools/permissions.
- Supervisor/worker (star, dynamic routing + integration), sequential pipeline (fixed order), and handoff/routing (transfer control) are the three core topologies; group-chat, hierarchical, and debate extend them.
- Agents coordinate via shared state or message passing; watch for compounding errors and runaway chats.
- LangGraph, AutoGen, CrewAI, Semantic Kernel all implement these patterns — pick by ecosystem fit.
- Default to a single well-designed agent; add agents only when it demonstrably can't stay coherent.
- Worked examples map to the exercises: supervisor/worker (Ex 1), sequential pipeline (Ex 2), handoff routing (Ex 3), single-vs-multi decision (Ex 4).
Multi-agent Patterns — Exercises
Reuse your run_agent/agent helper from earlier topics — a "specialist agent" is just an agent with a focused
system prompt. No new library required; you can do all four with plain functions.
Exercise 1 — Supervisor / worker
Build a supervisor over three workers (researcher, writer, editor). The supervisor plans the order, runs each worker with a scoped subtask, and feeds output forward. Confirm each worker only sees its own subtask.
Reference solution
def researcher(t): return run_agent(sys="Research; return 3-line summary.", user=t)
def writer(t): return run_agent(sys="Write copy from the brief.", user=t)
def editor(t): return run_agent(sys="Tighten the copy; return final.", user=t)
def supervisor(goal):
state = goal
for worker in (researcher, writer, editor): # (a real supervisor picks order via an LLM plan)
state = worker(f"{worker.__name__} task.\nContext: {state}")
return state
print(supervisor("Announce our new Pro plan"))
Each worker receives only its scoped task plus the prior output as context — never the other workers' internal reasoning. The supervisor holds the only full picture. This is the star topology; in LangGraph the supervisor is a routing node and workers are nodes.
Exercise 2 — Sequential pipeline
Rewrite the same task as a fixed pipeline: writer → editor → fact-checker. Note why a fixed order doesn't need the supervisor's routing LLM call.
Reference solution
def fact_checker(t): return run_agent(sys="Verify claims; flag anything unsupported.", user=t)
def pipeline(brief):
return fact_checker(editor(writer(brief))) # fixed order, each stage a specialist
print(pipeline("Announce our new Pro plan"))
Because the order never changes, there's no routing decision to make — no supervisor LLM call, no non-determinism, trivially debuggable. Same lesson as chain-vs-agent (Topic 1): if the steps are fixed, don't add dynamic orchestration.
Exercise 3 — Handoff / routing (triage)
Build a triage agent that routes a support message to one of billing, technical, or sales, with a
fallback for ambiguous input. Test with one message per category plus one ambiguous one.
Reference solution
def triage(msg):
dest = run_agent(sys="Reply with exactly one word: billing, technical, or sales.", user=msg).strip().lower()
specialists = {"billing": billing_agent, "technical": tech_agent, "sales": sales_agent}
return specialists.get(dest, general_agent)(msg) # fallback = general_agent for anything unexpected
for m in ["My card was charged twice", "The API returns 500", "Do you offer volume discounts?", "hello"]:
print(m, "->", triage(m))
Handoff transfers control to a specialist (unlike supervisor/worker, which integrates results). The
.get(dest, general_agent) fallback catches ambiguous or unexpected routes — critical, because a mis-route
sends the user to the wrong place. Keep the routing prompt cheap and its output auditable.
Exercise 4 — Single agent or multi-agent?
For each task, decide single agent, pipeline, supervisor/worker, or handoff, and justify in one line.
- Answer a customer's question from your docs.
- A support inbox where each message needs billing, technical, or sales expertise.
- Produce a blog post: research the topic, draft it, edit, and fact-check.
- Generate marketing copy where a "creative" and a "compliance reviewer" must both weigh in before publishing.
Reference solution
- Single agent. One focused RAG task — no specialties to split; multi-agent adds cost for nothing.
- Handoff / routing. Distinct specialties selected per message; triage transfers the conversation to the right specialist.
- Pipeline. Fixed order (research → draft → edit → fact-check) — a sequential chain of specialists, no dynamic routing needed.
- Supervisor/worker (or a debate/critic pattern). Two roles with different objectives must both contribute and be integrated before publishing.
The reflex: start single-agent; add topology only when the task has real distinct specialties, fixed multi-stage structure, or per-role tools/permissions.