Skip to content
ML to LLMs

Module 19: Agents and tool use 40 min13 min quick readLLMs

Multi-agent systems

Recognize the single-agent ceiling, choose between pipeline, fan-out, orchestrator-worker, and peer designs, compute their token and latency cost against a single agent, and build an orchestrator that hands out briefs and merges summaries.

Before this: Tool use and agents, Building reliable agents

Read on GitHub

Quick read: the intuition, interactive visualization, summary, and interview questions. The math, implementation, engineering, and knowledge-check sections are hidden.

Mental model. A multi-agent system is a manager and a team. The manager splits the job, hands each person a written brief, and later reads their one-page reports, not their notebooks. Each person works with a clear desk. It finishes faster when the pieces are independent, costs more in total hours, and fails in the handoffs: a vague brief, two people doing the same work, or reports that don't fit together.

You will learn to

  • Name the three single-agent limits that motivate splitting: context saturation, role confusion, and sequential execution.
  • Choose between pipeline, fan-out/fan-in, orchestrator-worker, and peer (swarm) designs for a given task shape.
  • Compute total input tokens, peak context, and critical-path length for a single agent and for an orchestrator with workers.
  • Build an orchestrator that sends structured briefs to parallel workers and merges only their summaries.
  • Decide when a single agent with good context management beats a multi-agent design.

Why it matters. "Should this be multi-agent?" is one of the most common agent design questions, and one of the most commonly answered wrong. Splitting helps broad, parallel work like research across many sources. It hurts tightly coupled work like most code edits, where every handoff loses context. You need numbers, not intuitions, to tell the two apart.

1. Intuition

The single-agent ceiling. One agent is one loop, one context window, and one system prompt. Three things eventually break:

  1. Context saturation. Every tool result stays in the context. By step 30, the window holds pages of file contents and search results, the early details get lost, and every call re-reads all of it.
  2. Role confusion. One prompt that says "research, then code, then review, then test" produces an agent that does each job half-well. Reviewing your own work in the same context also inherits your own blind spots.
  3. Sequential execution. Reading three independent sources takes three rounds, one after another, even though nothing about the second depends on the first.

Splitting fixes each one, at a price. Give each agent one job, its own fresh context, and a prompt tuned for that job. Workers read a lot and return a little, so the coordinator's context stays small. Independent workers run at the same time. The price: every boundary is lossy (a worker's 20,000 tokens of reading become a 400-token summary), coordination is new code that can fail, and total tokens can go up because every worker re-reads its own instructions and may re-discover what another already found.

A spectrum, not a switch.

DesignShapeGood forMain risk
Single agentone loopmost tasks; tightly coupled stepscontext overflow on long runs
Subagentsparent spawns a child for a side task, gets a summary backkeeping the parent's context clean (e.g. "search the codebase for X")child lacks context the parent had
PipelineA → B → C, fixed stagesstaged work: research → draft → reviewone stage's error flows downstream
Fan-out / fan-insplit into parallel workers, mergeindependent subtasks known in advancemerge step must reconcile conflicts
Orchestrator-workera model decides the subtasks at run time and delegatesbreadth-first research, subtasks not known in advancevague briefs, duplicated work
Peer / swarmagents share a queue or message bus, no fixed leaderhigh-throughput queues of similar itemshard to debug; emergent loops

Pipeline and fan-out are workflows: your code fixes the control flow. Orchestrator-worker and swarms are agentic: a model decides it. The rule from tool use and agents still applies: if you can write the steps down, write them down.

The handoff is the product. A worker only knows what its brief says. A good brief has the objective, what is already known, the boundaries (what not to do, which sources to skip), the tools to use, and the exact output format and length. A good report is short, structured, and cites what it found, so the orchestrator can merge and check it without re-doing the work.

Key distinction

Multi-agent pays when work is broad and independent. It buys parallel speed and clean contexts. It does not make a model smarter at a single coupled problem, and it usually costs more tokens than a single agent that manages its context well.

2. Visualization

Two panels for synthetic token sizes. Left: total input tokens versus number of independent subtasks k from 1 to 10. A single agent that keeps everything rises quadratically to about 712 thousand at k=10. A single agent that compacts each finished subtask rises to about 189 thousand. An orchestrator with workers doing 3 steps each rises linearly to about 99 thousand. Workers that need 6 steps each reach about 323 thousand. Right: sequential model calls on the critical path: a single agent needs 3k calls, rising to 30, while orchestrator plus workers stays at 5.

Synthetic sizes. Left: the big token saving is against a single agent that never compacts; against one that does, the gap is small, and if workers must re-discover context they cost more. Right: the robust win is latency, because parallel workers keep the critical path flat.

Try it (run multi_agent.py, predict first, then check):

  1. Before running, predict which design wins on total tokens at k = 1. Check the left panel.
  2. Set M (summary length) to 4,000 tokens. Predict what happens to the orchestrator's synthesis call and its peak context.
  3. Make workers need 6 steps (the dashed line). At what k does multi-agent become cheaper than the non-compacting single agent?
  4. Suppose the subtasks are not independent: worker 2 needs worker 1's result. What happens to the critical path?

3. The math

Symbols

SymbolMeaningUnit
kkindependent subtaskscount
sstool steps per subtaskcount
BBsingle agent's base context (prompt, tools, task)tokens
DDtokens appended per step (model output + tool result)tokens
BOB_O, BWB_Worchestrator's and each worker's base contexttokens
PP, MMplan length; each worker's summary lengthtokens
CCtotal input tokens billedtokens

One agent loop of TT calls bills TB+D T(T−1)/2TB + D\,T(T-1)/2 input tokens, because each call re-reads everything before it (derived in tool use and agents).

Single agent doing all kk subtasks in one loop, T=ksT = ks:

Csingle=ksB+D ks(ks−1)2,critical path=ks callsC_{\text{single}} = ksB + D\,\frac{ks(ks-1)}{2}, \qquad \text{critical path} = ks \text{ calls}

Orchestrator with workers: one planning call, kk parallel worker loops, one synthesis call that reads the plan and the kk summaries:

Cmulti=BO+k(sBW+D s(s−1)2)+(BO+P+kM),critical path=s+2 callsC_{\text{multi}} = B_O + k\left(sB_W + D\,\frac{s(s-1)}{2}\right) + (B_O + P + kM), \qquad \text{critical path} = s + 2 \text{ calls}

The single agent's quadratic term grows with (ks)2(ks)^2; the workers' grows with ks2k s^2. Splitting removes the cross-subtask re-reading.

Worked example

Synthetic sizes: k=4k = 4, s=3s = 3, B=2,000B = 2{,}000, D=1,500D = 1{,}500, BO=2,000B_O = 2{,}000, BW=1,500B_W = 1{,}500, P=300P = 300, M=400M = 400.

Single agent.

  1. T=4×3=12T = 4 \times 3 = 12 calls.
  2. C=12×2,000+1,500×12×112=24,000+1,500×66=24,000+99,000=123,000C = 12 \times 2{,}000 + 1{,}500 \times \frac{12 \times 11}{2} = 24{,}000 + 1{,}500 \times 66 = 24{,}000 + 99{,}000 = 123{,}000.
  3. Peak context (last call): 2,000+11×1,500=18,5002{,}000 + 11 \times 1{,}500 = 18{,}500 tokens.

Orchestrator with workers.

  1. Plan: 2,0002{,}000.
  2. One worker: 3×1,500+1,500×3×22=4,500+4,500=9,0003 \times 1{,}500 + 1{,}500 \times \frac{3 \times 2}{2} = 4{,}500 + 4{,}500 = 9{,}000. Four workers: 36,00036{,}000.
  3. Synthesis: 2,000+300+4×400=3,9002{,}000 + 300 + 4 \times 400 = 3{,}900.
  4. Total: 2,000+36,000+3,900=41,9002{,}000 + 36{,}000 + 3{,}900 = 41{,}900.
  5. Peak context: a worker's last call, 1,500+2×1,500=4,5001{,}500 + 2 \times 1{,}500 = 4{,}500, versus synthesis 3,9003{,}900, so 4,5004{,}500.
  6. Critical path: 1+3+1=51 + 3 + 1 = 5 sequential calls instead of 12.

The fair baseline. A single agent that replaces each finished subtask's history with a 400-token summary pays, for subtask j=0,1,2,3j = 0, 1, 2, 3, s(B+jM)+D s(s−1)/2s(B + jM) + D\,s(s-1)/2:

  1. Base reads: 3×2,000×4=24,0003 \times 2{,}000 \times 4 = 24{,}000.
  2. Summary reads: 3×400×(0+1+2+3)=7,2003 \times 400 \times (0 + 1 + 2 + 3) = 7{,}200.
  3. Within-subtask growth: 4×1,500×3=18,0004 \times 1{,}500 \times 3 = 18{,}000.
  4. Total: 49,20049{,}200, close to the multi-agent 41,90041{,}900, with no coordination code.

When workers duplicate work. If each worker needs 6 steps because it must re-discover context the orchestrator already had, one worker costs 6×1,500+1,500×15=9,000+22,500=31,5006 \times 1{,}500 + 1{,}500 \times 15 = 9{,}000 + 22{,}500 = 31{,}500; four cost 126,000126{,}000; the total is 2,000+126,000+3,900=131,9002{,}000 + 126{,}000 + 3{,}900 = 131{,}900, more than the naive single agent.

Real systems that explore broadly spend far more. Anthropic reported that its multi-agent research system used roughly 15 times the tokens of a chat interaction, and that this paid off only on tasks valuable enough and parallel enough to justify it. The model above shows why: the token bill depends on how much each worker explores, while the latency win depends only on independence.

4. Implementation

Build it from scratch

The full script is code/19-agents/multi_agent.py. Agents are mocks (no API calls) so the run is offline and deterministic in tokens; "latency" is a short sleep per model call.

The cost model. One helper bills an agent loop; the two designs are built from it, so the arithmetic above is exactly what the code computes.

python
def loop_tokens(base, d, steps):
    """Input tokens billed by one agent loop: each call re-reads base + everything appended so far."""
    return steps * base + d * steps * (steps - 1) // 2

def single_agent(k, s):
    t = k * s
    return {"input": loop_tokens(B, D, t), "critical_calls": t, "peak": B + (t - 1) * D}

Predict: doubling kk roughly multiplies the single agent's tokens by what factor, once kk is large?

The orchestrator design. Plan, kk independent worker loops, then one synthesis call that reads only the summaries.

python
def orchestrator_workers(k, s):
    plan = B_O
    workers = k * loop_tokens(B_W, D, s)
    synth = B_O + P + k * M
    return {"input": plan + workers + synth, "critical_calls": 1 + s + 1,
            "peak": max(B_W + (s - 1) * D, synth), "parts": (plan, workers, synth)}

Predict: with k=50k = 50 subtasks, which call has the largest context, a worker or the synthesis?

A worker gets a brief, not the orchestrator's context. It works in its own context and returns a short, addressed message. Nothing else crosses the boundary.

python
def worker(brief, steps):
    """A mock worker: its own fresh context (the brief only), a few tool steps, then a short summary."""
    for _ in range(steps):
        time.sleep(CALL_SECONDS)
    return {"to": "orchestrator", "from": brief["id"], "summary": f"findings on {brief['topic']} ({M} tokens)",
            "tokens": loop_tokens(B_W, D, steps)}

Predict: the brief has no field saying which sources other workers cover. What failure does that invite?

Fan out, fan in. Briefs are structured (goal, format, length cap). Workers run concurrently in a thread pool; the orchestrator waits for all of them, then synthesizes.

python
def run_fanout(topics, steps):
    briefs = [{"id": f"w{i + 1}", "topic": t, "goal": "3 key facts with sources", "format": "bullets, max 400 tokens"}
              for i, t in enumerate(topics)]
    t0 = time.perf_counter()
    time.sleep(CALL_SECONDS)                                          # orchestrator plans
    with ThreadPoolExecutor(max_workers=len(briefs)) as pool:         # workers run concurrently
        results = list(pool.map(lambda b: worker(b, steps), briefs))
    time.sleep(CALL_SECONDS)                                          # orchestrator synthesizes summaries only
    return results, time.perf_counter() - t0

Predict: one worker hangs. What does pool.map do, and what should a production orchestrator do instead?

Use the library

Agent frameworks package these patterns: the OpenAI Agents SDK (handoffs and agents-as-tools), LangGraph (graphs of nodes with shared state), Microsoft's AutoGen (conversations between agents), CrewAI (role-based crews), and Anthropic's Claude Agent SDK (subagents that run with their own context and return a summary). What they add over the script: real model calls, tool routing, shared state or message passing, retries, timeouts, and tracing across agents. What they do not add is judgment about whether to split. Start with one agent, measure where it fails, and split only the part that is broad and parallel.

Run it

bash
uv run code/19-agents/multi_agent.py

Key lines of output (wall-clock times vary slightly by machine):

text
single agent: T = 4*3 = 12 calls, input = 12*2000 + 1500*12*11/2 = 123,000, peak context 18,500
orchestrator+workers: plan 2,000 + workers 4*9,000 = 36,000 + synthesis 3,900 = 41,900, peak context 4,500
critical path: 12 sequential calls vs 5
single agent that compacts each finished subtask to 400 tokens: 49,200
if each worker needs 6 steps: 131,900 input tokens (single agent: 123,000)
wall clock 0.28s vs 0.70s if the same calls ran one after another

5. Engineering

Decide with three questions. Are the subtasks independent (no worker needs another's result)? Does a single context overflow or slow down on this task? Is the task valuable enough to pay for parallel exploration? Three yeses: consider orchestrator-worker. Otherwise, one agent with good tools and context management usually wins.

Write briefs like tickets. Objective, known facts, scope boundaries, tools and sources, and an output contract (format, length cap, citations). Most multi-agent failures trace back to a brief that assumed context the worker didn't have.

Keep reports small and checkable. Workers return structured summaries, not transcripts. The orchestrator checks them (are claims cited, do they conflict) before merging, and can send a worker back with a sharper brief.

Budget and stop. Give every worker a step and token cap, and the orchestrator a cap on how many workers it may spawn. Scale effort to the task: a simple question does not need ten workers.

Observe across agents. Trace every agent with a shared run ID so you can follow a request from plan to briefs to worker steps to synthesis. Without it, debugging means reading several transcripts and guessing how they relate. Evaluate the whole system end to end, as in LLM evaluation, and also check each handoff.

Prefer read-only parallelism. Parallel workers that only read and report are safe to retry and easy to merge. Parallel workers that write (edit the same files, place orders) create conflicts and side effects that are hard to undo; keep writes in one agent, or serialize them.

Watch out

Failure modes. Vague briefs; workers duplicating each other; lost context at handoffs; conflicting reports merged without reconciliation; a spawning loop where the orchestrator keeps adding workers; one slow worker holding up the whole run; and agents echoing each other's mistakes (peer designs can amplify a wrong claim rather than catch it).

Common mistakes

  • Splitting a tightly coupled task (most code edits) across agents, then losing the decisions each made.
  • Comparing multi-agent to a single agent that never compacts its context, and calling the token saving a win.
  • Passing workers the orchestrator's full transcript "for context", which removes the clean-context benefit.
  • No output contract, so the orchestrator receives ten differently shaped essays.
  • No per-worker budget or timeout.
  • Measuring only final quality, not total tokens and latency, so the design's cost is invisible.

6. Knowledge check

Quiz: Multi-agent systems

Question 1 of 9
Select all that applyeasy

Which are limits of a single agent that motivate splitting work across agents? Select all that apply.

Practice exercise. Use the lesson's sizes (B=BO=2,000B = B_O = 2{,}000, BW=1,500B_W = 1{,}500, D=1,500D = 1{,}500, P=300P = 300, M=400M = 400) with k=6k = 6 subtasks of s=2s = 2 steps each. Compute total input tokens and critical-path length for the single agent and for orchestrator-workers.

Solution

Single agent: T=12T = 12. C=12×2,000+1,500×66=24,000+99,000=123,000C = 12 \times 2{,}000 + 1{,}500 \times 66 = 24{,}000 + 99{,}000 = 123{,}000. Critical path 12 calls. (Same TT as the worked example, so the same total.)

Orchestrator-workers: one worker =2×1,500+1,500×2×12=3,000+1,500=4,500= 2 \times 1{,}500 + 1{,}500 \times \frac{2 \times 1}{2} = 3{,}000 + 1{,}500 = 4{,}500; six workers =27,000= 27{,}000. Synthesis =2,000+300+6×400=4,700= 2{,}000 + 300 + 6 \times 400 = 4{,}700. Total =2,000+27,000+4,700=33,700= 2{,}000 + 27{,}000 + 4{,}700 = 33{,}700. Critical path =1+2+1=4= 1 + 2 + 1 = 4 calls.

Shorter subtasks favour splitting: each worker's quadratic term is tiny, and the synthesis call stays small because summaries are capped.

Implementation challenge. Add a timeout and a retry to run_fanout: if a worker takes longer than 3 model-call times, cancel waiting for it, re-issue its brief once with steps=1, and mark its report as partial. Show the wall clock when one worker is slow.

Solution sketch

Submit with futures = {pool.submit(worker, b, steps): b for b in briefs} and collect with concurrent.futures.wait(futures, timeout=3 * CALL_SECONDS * steps). For each brief whose future is not done, submit worker(b, 1) and add "partial": True to its result. Threads can't be killed, so the slow one keeps running in the background; in production you'd pass a cancellation flag or use async tasks with cancellation. Wall clock becomes roughly plan + timeout + one retry step + synthesis, instead of waiting for the slowest worker.

Summary

  • One agent hits three limits: context saturation, role confusion, and sequential execution.
  • Designs range from subagents and pipelines (fixed flow) to orchestrator-worker and swarms (model-chosen flow); choose by task shape.
  • Splitting turns the (ks)2(ks)^2 re-reading term into ks2k s^2 and the critical path from ksks to s+2s + 2 calls, but against a single agent that compacts its context the token saving is small, and duplicated exploration erases it.
  • The reliable win is latency and clean contexts on broad, independent work; the reliable cost is lossy handoffs and coordination code.
  • Briefs and reports are the interface: write briefs like tickets, cap and structure reports, budget every worker, and trace across agents.

Next: Production architecture

Related: Building reliable agents · Agent memory · Model Context Protocol · Reliability, cost, and observability

Go deeper, code-first (AI Engineering from Scratch): A2A — Agent-to-Agent Protocol · Multi-Agent Debate and Collaboration · Orchestration Patterns: Supervisor, Swarm, Hierarchical · Supervisor / Orchestrator-Worker Pattern · Role Specialization — Planner, Critic, Executor, Verifier · Handoffs and Routines — Stateless Orchestration

Parts of this lesson are adapted from AI Engineering from Scratch by Rohit Ghumare (MIT license, notice): Why Multi-Agent?.

Interview angle

Why would you split an agent into several agents, and what does it cost?

Three reasons. Context: a single agent accumulates every tool result, so on long, broad tasks the window fills, early details get lost, and every call re-reads all of it; workers each start clean and return a short summary. Specialization: a reviewer prompt in its own context catches things the author's context would rationalize. Parallelism: independent subtasks like researching five sources can run at once, so the critical path drops from many sequential calls to a few. The costs: every handoff compresses a worker's full context into a summary and loses detail; coordination is new code that fails in new ways, like vague briefs, duplicated work, or conflicting reports; and total tokens often go up because workers re-read instructions and re-explore. I split when work is broad and independent; I keep one agent when steps are tightly coupled.

A team says their multi-agent coder beats their single agent. What would you check before believing it?

First the baseline: was the single agent given the same tools, budget, and context management? A single agent that never compacts its history is a weak comparison, because most of the multi-agent token and quality gain can come from clean contexts that a compacting single agent also gets. Second, cost: total tokens and wall-clock time per task, not just success rate, since multi-agent designs often spend several times more tokens. Third, the task mix: coding edits are usually tightly coupled, so I'd look at which tasks improved; gains often come from the broad, read-heavy parts like searching the codebase, which subagents handle well, while the edits themselves are better kept in one agent. Fourth, variance: run each design several times and report pass^k, not one lucky run. Then I'd decide whether the gain is worth the extra cost.

Your research orchestrator's answers often contain duplicated findings and miss obvious sources. How do you debug it?

Read the briefs first, because that's where these failures usually start. Duplicates mean the workers' scopes overlap: briefs like "research the market" with no boundaries, so every worker searches the same top results. Missed sources mean nobody owned them. I'd check the traces with a shared run ID: what each worker was told, which queries it ran, and what it returned. Fixes: briefs that partition the space explicitly (this worker covers regulation, from these source types, and skips news coverage), list what's already known, and set an output contract with citations. Then have the orchestrator check coverage and overlap before synthesizing, and send a worker back with a sharper brief when a gap shows up. Finally, add eval cases for coverage and duplication so the fix holds as prompts change.

Four independent subtasks, three tool steps each, 2,000-token base context, 1,500 tokens added per step. Single agent or orchestrator with workers?

Single agent first: 12 calls, so input tokens are 12 times 2,000 plus 1,500 times 12 times 11 over 2, which is 24,000 plus 99,000, or 123,000, with a peak context of 18,500. With an orchestrator, each worker with a 1,500-token brief costs 4,500 plus 4,500, or 9,000 tokens; four is 36,000, plus a 2,000-token plan and a 3,900-token synthesis, about 42,000 in total, with a peak of 4,500 and 5 sequential calls instead of 12. But a single agent that compacts each finished subtask to a 400-token summary costs about 49,000, so the token gap is small. I'd choose workers here for latency, since the subtasks are independent, and watch that workers don't re-explore, because at 6 steps each the multi-agent bill rises to about 132,000.