L3 · 03 · Multi-Agent Systems & Orchestration
One agent with the right tools solves most tasks. Multi-agent systems help when work is long-running, splits into independent subtasks, or benefits from a critic that isn't the author. This page covers the patterns that work in production, the coordination costs nobody mentions in demos, and agent-to-agent protocols.
By the end of this page you can
- Pick the right pattern: pipeline, orchestrator–worker, planner–executor, critic loops, or just one agent.
- Pass artefacts between agents without polluting each one's context.
- Estimate the real costs: latency, tokens, compounded error rates.
- Describe agent-to-agent protocols (A2A, ACP) and when they matter.
Start with the honest question
The patterns that work
| Pattern | Shape | Best for | Watch out |
|---|---|---|---|
| Pipeline | A → B → C, deterministic order | Document workflows: extract → draft → review | Doesn't need agents at every stage — code is fine |
| Orchestrator–worker | One agent spawns N parallel sub-agents, merges results | Independent subtasks: search 5 sources, check 10 files | Merging; workers reporting partial results as complete |
| Planner–executor | Planner emits steps; executor runs them; planner re-plans on failure | Long tasks where early steps decide later ones | Re-planning loops need hard caps |
| Author–critic | Author produces, a separate agent reviews, iterate until pass or cap | Code, copy, anything with a checkable quality bar | Politeness drift — critics go soft; give them a checklist |
| Handoffs | One agent transfers the conversation to a specialist | Triage → specialist in chat products | State transfer; the specialist needs context, not a cold start |
Artefacts, not conversations
The biggest practical rule: agents pass artefacts (files, JSON, summaries), not their full message histories. A worker returns "here are the 3 findings with citations", not 40 messages of exploration. This keeps each agent's context clean, makes results testable, and lets you swap agents without rewiring anything.
from concurrent.futures import ThreadPoolExecutor
def make_worker(model, role: str):
def work(task: str) -> dict:
# In production this is a real model call with the role as system prompt.
# Returns an artefact (dict), not a conversation.
return model(f"[{role}] {task}")
return work
def orchestrate(task: str, subtasks: list[str], workers, merge) -> dict:
with ThreadPoolExecutor(max_workers=len(subtasks)) as pool:
artefacts = list(pool.map(lambda t: workers[t[0]](t[1]),
enumerate(subtasks)))
return merge(task, artefacts)
if __name__ == "__main__":
# Scripted stand-ins: each "worker" returns a fake finding.
scripted = lambda model: make_worker(model, "researcher")
workers = [scripted(lambda p: {"finding": p[:40], "source": "doc-A"}),
scripted(lambda p: {"finding": p[:40], "source": "doc-B"}),
scripted(lambda p: {"finding": p[:40], "source": "doc-C"})]
def merge(task, artefacts):
return {"task": task, "n_findings": len(artefacts),
"sources": sorted({a["source"] for a in artefacts})}
result = orchestrate(
"Why did pickup cancellations spike in June?",
["check order logs", "check staffing rota", "check promotion calendar"],
workers, merge)
print(result)
Three workers run in parallel, each returns a small artefact, and merge is plain code you can unit-test.
That's the skeleton of every orchestrator–worker system.
The costs nobody mentions
- Tokens multiply. An orchestrator that replays each worker's output pays for every token twice. Artefacts keep this bounded.
- Errors compound. Three stages at 90% accuracy each → ~73% end-to-end. Add checks between stages, not just at the end.
- Latency adds up serially in pipelines; parallel workers pay the max, not the sum.
- Debugging gets hard fast. Log every artefact passed between agents with run IDs — you'll need them (L3·07).
- Determinism where possible. If a step is just "format these results", write code, not a prompt.
Agents talking to agents: A2A and ACP
MCP connects a model to tools. Agent-to-agent protocols connect agents to agents — one agent delegating a task to another, possibly across organisations or runtimes.
| Protocol | Focus |
|---|---|
| A2A (Agent2Agent) | Agent cards describing capabilities, task delegation with lifecycle (submitted → working → completed), streaming updates |
| ACP and similar | Similar goals: discovery, structured task exchange between independent agents |
These matter when agents live in different systems (your support agent calls a vendor's logistics agent). Inside one codebase, a function call is still simpler and better. The field is young — expect the spec names and versions to keep moving; the patterns above are the durable knowledge.
Permissions differ per agent — that's a feature
Separation of duties is a security pattern, not just an org chart: the drafting agent can read the ticket queue; the sending agent can send replies but not read the whole queue; the critic can only read. Each agent gets least-privilege tools (L2·09), and a compromise of one doesn't compromise the rest. L3·08 goes deeper.
- Take a real multi-step task (e.g. "summarise these 3 documents, then compare them"). Do it with one agent and log the token use.
- Now do it as orchestrator–worker: one agent per document, merge in code. Compare tokens and answer quality.
- Add an author–critic loop to the merge step with a 4-item checklist. How many iterations until the critic passes?
- Break a worker (return an error artefact). Does your merge handle partial results honestly?
Key takeaways
- Default to one agent. Multi-agent earns its cost for independent subtasks, different permissions, or critic separation.
- Pass artefacts, not conversations. Merge in code you can test.
- Errors compound multiplicatively — put checks between stages.
- A2A-style protocols are for cross-system agents; inside one codebase, call a function.