AI & ML / Agents & orchestration / 04_multi_agent_patterns.md

Multi-agent patterns

Updated 7 interview angles 10 min read source
On this page12
  1. What the evidence says
  2. The mechanism: a sub-agent is a tool
  3. Legitimate reasons
  4. The topologies
  5. The arithmetic that should decide it
  6. Handoffs are where information dies
  7. Where it breaks in production
  8. One budget, and cancellation that reaches every child
  9. Evaluating and tracing it
  10. The trade not taken
  11. Related
  12. Interview angle

Multi-agent patterns

Article.

Fashionable, and usually the wrong first answer. The strongest response to “design a multi-agent system” often starts by asking whether one agent with good tools would do — and the arithmetic below is why.

What the evidence says

The two most-cited engineering write-ups from June 2025 disagree, and the disagreement is the interview answer:

  • Anthropic’s research system (a Claude Opus 4 lead agent directing Claude Sonnet 4 sub-agents) outperformed a single Opus 4 agent by 90.2% on Anthropic’s internal research eval. The cost: multi-agent runs used about 15x the tokens of a chat (single agents about 4x), and on BrowseComp token usage alone explained 80% of the variance in performance. They name where it does not fit: tasks where agents must share the same context or depend on each other heavily, and most coding.
  • Cognition’s “Don’t Build Multi-Agents” argues for one agent by default: “share context, and share full agent traces, not just individual messages”, because “actions carry implicit decisions, and conflicting decisions carry bad results”. Two parallel sub-agents that each pick a different style or interpretation produce parts that do not fit.

Read together: multiple agents pay off for breadth-first work that splits into independent parts (research across many sources, reviewing a change along separate dimensions), and they pay for it in tokens. They hurt when the parts must agree on decisions nobody wrote down, which describes most building tasks.

The mechanism: a sub-agent is a tool

Strip away the vocabulary and there is almost nothing new here.

From the parent’s point of view, delegating to a sub-agent is indistinguishable from calling any other tool. It emits a call, something happens, a result comes back in the transcript.

python
async def research_topic(question: str, max_sources: int = 5) -> dict:
    """Research a question and return findings with sources."""
    sub = Agent(system=RESEARCH_PROMPT, tools=[search, fetch_page])
    result = await sub.run(question, max_steps=15)      # a whole loop, inside
    return ResearchResult.model_validate(result).model_dump()

TOOLS = [search_orders, get_tracking, research_topic]   # just another entry

The parent sees one tool call and one result. It has no idea whether that took one model call or fifteen.

Every property people attribute to multi-agent systems falls out of that:

  • Context isolation is the tool returning a summary instead of a transcript.
  • Specialisation is the tool constructing its own agent with its own prompt, tools and model.
  • Privilege separation is the tool running with different credentials.
  • Handoff loss is the return type being narrower than what the sub-agent saw.

Which reframes the design question usefully: not “how do agents talk to each other” but “what should this tool return”.

What isolation actually buys

The parent never sees the sub-agent’s work, and that is the point:

text
without isolation
    parent = 2k prompt + 15 calls + 15 results (~30k)
           = 32k

with isolation
    parent = 2k prompt + 1 call + 1 result (~800)
           = 2.8k

Because agent cost grows with the square of the steps in one context, moving fifteen steps into a child is not a 15× saving — it is considerably more. That is the strongest, most defensible reason to use a second agent at all.

Legitimate reasons

  • Context isolation. One prompt cannot hold the task. The strongest reason.
  • Specialisation. A cheap model for retrieval, a reasoning model for synthesis, different tools per role.
  • Parallelism. Genuinely independent subtasks running concurrently.
  • Privilege separation. The agent reading untrusted content holds no write tools. A real security argument, not an organisational one.

Bad reasons: it sounds sophisticated, a blog post said so, or the org chart has three teams. The task having several conceptual parts is not a reason — one agent calls several tools.

The topologies

Supervisor

text
        supervisor
       /     |     \
  research  code   write

The default. Control flow is centralised so it is traceable, and — the property that matters most, see below — the supervisor can notice a specialist failed and re-plan. It becomes a bottleneck and its own context still grows, but it is the pattern that works most often.

Pipeline

text
extract -> analyse -> summarise -> format

A fixed sequence. This is a workflow, not really a multi-agent system, and that is a compliment: deterministic, testable, cheap. If your multi-agent system is a pipeline, call it a pipeline and keep the determinism.

Parallel with a reducer

python
async with asyncio.TaskGroup() as tg:
    tasks = [tg.create_task(agent.run(sub)) for sub in subtasks]
merged = await synthesiser.run([t.result() for t in tasks])

Excellent when subtasks are genuinely independent — researching five topics, reviewing a diff along several dimensions. Latency is the slowest branch rather than the sum.

Network

Any agent may call any other. Maximum flexibility, minimum predictability: non-terminating conversations, circular delegation, runaway cost. Rare in production, and worth pushing back on in a design discussion.

The arithmetic that should decide it

The reliability calculation is the thing to do out loud, and the naive version is only half right.

A chain, where every stage must succeed:

text
5 agents, each 90% reliable:  0.9^5 = 0.59

Fifty-nine percent. Multiplicative decay is brutal and it is why deep hierarchies rarely survive contact with production.

A supervisor that can detect failure and retry is a different system. If the supervisor can tell a specialist failed — which is exactly what a gaps field in the contract gives it — each stage gets more than one attempt:

text
per stage with one retry:  1 - (1 - 0.9)^2 = 0.99
five stages:               0.99^5 ≈ 0.95

This is the real argument for the supervisor topology, and it is stronger than “centralised control flow is tidier”. It only holds if failure is detectable, which is a contract design problem, not an orchestration one.

Parallel fan-out with a reducer is different again: if the synthesiser can work with four of five results, one branch failing degrades quality rather than failing the run.

Handoffs are where information dies

Style Trade
Full context expensive; defeats the isolation
Summary lossy; the receiver lacks nuance
Typed payload precise; you must design the contract
Shared state no loss; coupling

A typed contract is the engineering answer. Define what a sub-agent receives and returns as a schema, exactly as you would an internal API.

python
class ResearchResult(BaseModel):
    findings: list[Finding]
    sources: list[HttpUrl]
    confidence: Literal["high", "medium", "low"]
    gaps: list[str] = Field(
        description="What you could NOT determine. Empty only if nothing failed."
    )

That gaps field is what makes the whole pattern work. A sub-agent reporting what it failed to find lets the supervisor re-plan; a sub-agent that returns only findings forces the supervisor to synthesise confidently over a hole it cannot see. It is also what turns 0.59 into 0.95 above.

Where it breaks in production

Circular delegation. A hands to B, B hands back to A, and neither is wrong locally. Bound the total agent invocations for a run, not just steps within each agent — the per-agent limit does not compose.

Budget fragmentation. Five agents each with a 50,000-token budget is a 250,000-token run nobody approved. Hold one budget centrally and pass the remainder down (shown below).

Inter-agent prompt injection. A sub-agent’s output is model-generated text that may carry instructions it read from a web page or document. The parent must treat it as untrusted input, like a tool result: parse it into the typed contract, never paste it into its own instructions, and never let it choose tools the parent would not have chosen.

Conflicting parallel writes. Two sub-agents editing the same file, record or plan make incompatible changes, each locally reasonable. Give each parallel agent a disjoint piece of the output, and let one agent (or code) do the merge; shared mutable state between parallel agents is a race condition with a language model in it.

Diffuse responsibility. The output is wrong and no single agent is at fault. Propagate one trace id through every agent and tool call, or attributing a failure is guesswork — see LLM observability.

Lost constraints. The supervisor knew the customer is in the EU; the specialist did not, because that was not in the contract. Constraints that affect correctness belong in the schema, not in the supervisor’s head.

Privilege leakage. The agent that reads untrusted documents also holds the tool that sends email, so a prompt injection in a document has an exfiltration path. Scope tools per agent, and treat the read-untrusted agent as compromised by default — see Building and securing MCP servers.

One budget, and cancellation that reaches every child

The supervisor owns the budget and the lifetime of every sub-agent. Run the children in a TaskGroup, charge one shared budget, and let one failure cancel the rest:

python
async def supervisor(total: int):
    budget = Budget(total)                      # one budget for the whole run
    try:
        async with asyncio.TaskGroup() as tg:   # a failure cancels the siblings
            for name, steps, cost in [("research", 10, 3_000),
                                      ("pricing", 12, 3_000),
                                      ("runaway", 40, 9_000)]:
                tg.create_task(sub_agent(name, steps, cost, budget))
    except* BudgetExceeded as eg:
        print(f"  stopped: {eg.exceptions[0]}")
    print(f"  tokens left: {budget.left:,} of {total:,}")
text
one run budget of 100k tokens, shared by three parallel sub-agents:
  research: cancelled at step 8
  pricing: cancelled at step 8
  stopped: runaway needed 9000, 4000 left
  tokens left: 4,000 of 100,000

The runaway agent exhausted the shared budget and the other two stopped mid-work instead of spending more. Per-agent limits would have let all three run to their own caps. The task group is what stops the siblings: asyncio.gather propagates the first exception but leaves the other children running and spending. Cancelling the supervisor’s task (the user pressed Stop) cancels every child either way.

Evaluating and tracing it

  • Trace the tree. One trace id across the supervisor, every sub-agent and every tool call; OpenTelemetry’s GenAI conventions (still marked Development) name agent spans invoke_agent with gen_ai.agent.name, so each child appears as a subtree you can cost and time.
  • Evaluate the run, then the parts. Score the final answer, then the trajectory: which agents ran, how many tokens each spent, which returned non-empty gaps. A regression usually lives in one sub-agent’s contract, and per-agent token and failure counts are how you find which (Why evaluating LLM systems is hard).
  • Compare against one agent. Keep a single-agent baseline in the eval set. If the multi-agent version is not clearly better per dollar, ship the single agent.

The trade not taken

One agent with a well-designed tool set very often beats a multi-agent system: no handoff loss, one trace, cheaper, faster, and evaluable as a single unit.

The honest heuristic: reach for a second agent when the context genuinely does not fit, when subtasks are truly parallel, or when privilege separation demands it. Those are the three, and they are all structural. “The problem has several parts” is not one of them.

And if the answer is a fixed sequence of specialists, you have designed a pipeline. Ship the pipeline.

Interview angle 7

  • “When would you use multiple agents?” — context isolation when one prompt cannot hold the task, genuine parallelism, model specialisation, or privilege separation. Not because the task has several conceptual parts; one agent calls several tools.
    • Follow-up: “What is a sub-agent, mechanically?” — a tool. The parent emits a call and gets a result, with no visibility into whether that took one model call or fifteen. Every property of multi-agent systems follows from what the tool returns.
  • “What is the main risk?” — compounding unreliability. Five agents at 90% each in a chain is 0.9^5, about 59% end-to-end.
    • Follow-up: “So how does anyone ship one?” — by making failure detectable and retryable. A supervisor that can see a specialist failed gets a second attempt per stage, which takes each stage to about 99% and the run to roughly 95%. That is the real argument for the supervisor topology.
  • “Which topology would you start with?” — supervisor with specialists. Centralised control flow is traceable, and the supervisor can re-plan on failure. Network topologies are flexible and rarely worth their unpredictability.
  • “How do you pass information between agents?” — a typed schema treated as an internal API contract, including a field for what the sub-agent could not determine. Without that field the supervisor synthesises confidently over a gap it cannot see.
  • “How much does isolation actually save?” — more than the step count suggests, because cost within one context grows with the square of the steps. Moving fifteen steps into a child and returning an 800-token summary is not a 15× saving.
  • “Anthropic’s multi-agent system beat a single agent by 90%. Why not always use one?” — it cost about 15x the tokens of a chat, and it won on breadth-first research, where sub-tasks are independent. For tasks whose parts must agree on unstated decisions, most coding included, parallel agents produce parts that do not fit.
    • Follow-up: how do you keep three parallel agents from overspending? One budget owned by the supervisor, children in a task group, so exhausting it cancels the siblings.
  • “Is a fixed sequence of agents a multi-agent system?” — it is a pipeline, and calling it that is a compliment: deterministic, testable, cheap. Do not add model-driven delegation where a fixed sequence already works.