Index Ai Academy
Phase 6 · Modern LLM engineering · Systems & safety

Multi-agent systems

Coordinating many agents on one large job.

About 35 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

In Phase 1 you built single agents: one model calling tools in a loop. Bigger jobs are now often split across many agents that work at the same time. Think of researching a market from dozens of sources, checking every page of a large website, or working through a quarter's campaign data. Done well, a team of agents is faster and more thorough. Done badly, it multiplies cost and mistakes. This lesson shows you how to tell the two apart.

What you'll be able to do

  • Describe the main multi-agent patterns: orchestrator-workers, parallel sections, pipelines, evaluator loops
  • Estimate the speedup from parallel subagents, and see how dependencies cap it
  • Calculate how errors compound across agent hand-offs, and what a verifier buys
  • Decide when several agents beat one, given their extra cost and coordination problems

What it covers

  1. A team instead of one worker
  2. How much faster?
  3. Errors add up
  4. Worked example: a three-step chain
  5. A checker at each hand-off
  6. When one agent is better
  7. What the code looks like

A team instead of one worker

Picture a big house move. One person can do it alone, but it takes all weekend. With friends, one person packs the kitchen while another packs the books. Someone has to say who does what. Someone also has to check that nothing got left behind.

An AI agent is a model that calls tools in a loop until a task is done. A multi-agent system puts several agents on one job. Most fit one of four patterns:

  • Orchestrator-workers: a lead agent, the orchestrator, splits the job into smaller tasks. It hands each one to a subagent, a helper agent with its own context window and tools. Then it combines what they find. This suits open-ended research.
  • Parallel sections: the same instructions run on separate pieces at the same time. That might be one agent per competitor, per web page or per region. The results are merged at the end.
  • Pipelines: specialist agents work in a line, like a production line. Research, then draft, then edit, then fact-check. Each one hands its output to the next.
  • Evaluator loops: one agent writes and a second one critiques the work against a checklist. Then the first one revises. It works like the critique-and-revise step in the constitutional AI lesson.

Why go to the trouble? There are three main gains:

  1. Speed. Agents that work in parallel, meaning at the same time, finish sooner by the clock.
  2. Separate context windows. A context window is everything a model can read in one request. Each subagent can read deeply without filling up the lead agent's window.
  3. Specialization. Each role can have its own prompt, its own tools, or even its own model.

It can pay off. Anthropic built a research system in which a lead agent directs subagents working in parallel. The company reported that it did about 90% better than a single agent on an internal research test. It also used roughly 15 times as many tokens as a chat conversation. You pay for tokens, so that matters.

Quick check
You run one agent per competitor, each with the same instructions, and merge the results at the end. Which pattern is that?
  1. Parallel sections
  2. A single agent
  3. A pipeline
  4. An evaluator loop
Show the answer

Parallel sections

The same instructions run on separate pieces at the same time. A pipeline would pass the work along a line, one agent after another.

How much faster?

Think about cooking a big dinner with friends. Extra cooks can chop vegetables and set the table at the same time. But the roast still needs its full time in the oven. No number of helpers changes that.

Agent work behaves the same way. Here's a research job as a list of tasks. Each task has a length in minutes. It also has prerequisites, the tasks that must finish before it can start:

  • plan comes first and takes 3 minutes.
  • Six research threads each need only the plan: pricing, reviews, social, ads, SEO and partners.
  • compare needs pricing and reviews to be done.
  • synthesis, the final write-up, needs everything else.

The code below simulates kk agents, where kk is the number of agents working at once. Whenever an agent is free, it picks up any task whose prerequisites are done. With more than one agent, each task also costs an extra half minute for handing the work over.

tasks = {                       # name: (minutes, prerequisites)
    "plan":         (3, []),
    "pricing":      (9, ["plan"]),      "reviews":     (12, ["plan"]),
    "social":       (8, ["plan"]),      "ads":         (10, ["plan"]),
    "seo":          (11, ["plan"]),     "partners":    (7, ["plan"]),
    "compare":      (6, ["pricing", "reviews"]),
    "synthesis":    (5, ["compare", "social", "ads", "seo", "partners"]),
}

def finish_time(agents, overhead_per_task=0.5):
    done, running, clock = {}, [], 0.0                      # done: name -> finish time
    while len(done) < len(tasks):
        ready = [t for t, (_, pre) in tasks.items()
                 if t not in done and t not in [r for r, _ in running] and all(p in done for p in pre)]
        while ready and len(running) < agents:              # start ready tasks on free agents
            t = ready.pop(0)
            extra = overhead_per_task if agents > 1 else 0  # handing work over costs a little
            running.append((t, clock + tasks[t][0] + extra))
        running.sort(key=lambda r: r[1])
        t, clock = running.pop(0)                           # jump to the next task to finish
        done[t] = clock
    return clock

total = sum(m for m, _ in tasks.values())
for agents in [1, 2, 4, 8]:
    t = finish_time(agents)
    label = f"{agents} agent{'s' if agents > 1 else ''}:"
    print(f"{label:<10}{t:5.1f} minutes   speedup {total / t:.1f}x")
print(f"\nthe critical path (plan -> reviews -> compare -> synthesis) alone takes {3 + 12 + 6 + 5} minutes")
It runs right here in your browser, with nothing to install.

Speedup is how many times faster the job gets. It's the one-agent time divided by the new time. With 4 agents, that's 71 ÷ 29, about 2.4.

Going from 1 agent to 4 cuts the job from 71 minutes to 29. But 8 agents save only one more minute. Look at the chain plan → reviews → compare → synthesis. Each step has to wait for the one before it. That chain takes 3 + 12 + 6 + 5 = 26 minutes, however many agents you add. Add the half-minute hand-off for each of its four tasks, and you get the 28 minutes that 8 agents need.

The longest chain of tasks that depend on each other is called the critical path. It can't be split up. The same holds for any team, human or AI. Split work only where the pieces are truly independent, and expect the longest chain to set the pace.

Quick check
Adding more subagents stops speeding up a job. What's the most likely reason?
  1. The remaining time is a chain of dependent steps (the critical path) that can't run in parallel
  2. Parallel agents always produce the same output
  3. Subagents get slower as more are added
  4. The model runs out of parameters
Show the answer

The remaining time is a chain of dependent steps (the critical path) that can't run in parallel

Once the independent work is spread out, the longest chain of dependent steps sets the finish time.

Quick check
In the research job, suppose the reviews thread took 20 minutes instead of 12. Nothing else changes. How long is the critical path now?
  1. 34 minutes
  2. 79 minutes
  3. 26 minutes
  4. 20 minutes
Show the answer

34 minutes

The chain plan → reviews → compare → synthesis now takes 3 + 20 + 6 + 5 = 34 minutes. The other chains are shorter: plan → pricing → compare → synthesis is 23, and plan → SEO → synthesis is 19.

Errors add up

Think of the party game telephone. A message passes from person to person. Each person usually gets it right, but small slips pile up. By the end, the message is often garbled.

Agent hand-offs work the same way. Each one is a chance to drop or bend information. Say each step in a chain works with probability pp. That's a number between 0 and 1, so 0.95 means 95% of the time. Now say the chain has nn steps, and each one succeeds or fails on its own. Then the chance that the whole chain works is:

pnp^n

In plain words: write down the step's success rate once for each step, then multiply them all together. Two steps at 90% give 0.9 × 0.9 = 0.81. So the pair works only 81% of the time, even though each step works 90% of the time.

This is a simplification. Real steps don't always succeed or fail on their own, so treat it as a rough guide.

Try it now

Three 90% steps only give 73%. Long chains need a much better step, or a checker at each hand-off.

Worked example

Worked example: a three-step chain

Each step in a chain gets it right 90% of the time. How often does a chain of one, two or three steps get everything right?

p = 0.9                      # each step succeeds 90% of the time
for n in [1, 2, 3]:
    print("steps:", n, " chance all succeed:", round(p ** n, 3))
In the lesson you can change this and run it.

One step works 0.9 of the time. Two steps: 0.9 × 0.9 = 0.81. Three steps: 0.81 × 0.9 = 0.729.

In Python, p ** n means pp to the power nn. So 0.9 ** 3 is 0.9 × 0.9 × 0.9. Each extra step takes away another 10% of whatever chance was left.

Quick check
Each step in a three-step chain works half the time. What does this print?
print(0.5 ** 3)
  1. 0.125
  2. 1.5
  3. 0.5
  4. 0.167
Show the answer

0.125

0.5 × 0.5 × 0.5 = 0.125. The whole chain works only 1 time in 8.

A checker at each hand-off

A verifier is a checker placed at a hand-off. It might be another agent or a set of tests. It might also check that the output fits a fixed format, called a schema. When it spots a mistake, the step runs again. That second try is a retry.

Here's how much one checker helps. Take a step that's right 95% of the time. The checker catches 80% of the mistakes, and the step gets one retry. Now the step can work in two ways:

  • right the first time: 95%;
  • wrong, caught, then right on the retry: 5% × 80% × 95% = 3.8%.

Together that's 98.8%, up from 95%. The code below works this out for chains of different lengths. Its catch setting is the share of mistakes the checker spots. Its retries setting is how many retries a step gets.

def chain_success(p, steps, catch=0.0, retries=0):
    # one step: right first time, or wrong-but-caught and retried (each retry has the same chances)
    step = p
    miss = 1 - p
    for _ in range(retries):
        step += miss * catch * p
        miss = miss * catch * (1 - p)
    return step ** steps

for steps in [1, 3, 6, 10]:
    plain = chain_success(0.95, steps)
    checked = chain_success(0.95, steps, catch=0.8, retries=1)
    print(f"{steps:>2} steps at 95% each: {plain:.0%} end to end   with a verifier (catches 80%, one retry): {checked:.0%}")

Ten steps at 95% each work together only 60% of the time. With a checker and one retry, that rises to 89%. So well-built multi-agent systems put checks at their hand-offs. They use schemas for structured outputs and tests for code. They use citations that can be checked. And an evaluator agent reviews the work before anything reaches a person.

Go deeper The formula behind the checker

With one retry, the chance that a single step works is:

p+(1−p) c pp + (1 - p)\,c\,p

Here cc is the catch rate, the share of mistakes the checker spots.

In plain words: the step works if it's right the first time (pp). It also works if it's wrong (1−p1 - p), the mistake is caught (cc), and the retry is right (pp again).

With p=0.95p = 0.95 and c=0.8c = 0.8, that's 0.95 + 0.05 × 0.8 × 0.95 = 0.988. For ten steps, multiply 0.988 by itself ten times. You get about 0.886, the 89% in the table.

p, c = 0.95, 0.8
step = p + (1 - p) * c * p
print(round(step, 3), round(step ** 10, 3))

In the chain_success code, the line step += miss * catch * p adds this second route once per retry. After each retry, miss becomes the chance of needing yet another one: wrong, caught, and wrong again.

Quick check
A step is right 90% of the time. A checker catches half of its mistakes and allows one retry. How often does the step work now?
  1. 90%
  2. 94.5%
  3. 95%
  4. 99%
Show the answer

94.5%

Right the first time is 90%. Wrong, caught, then right on the retry is 10% × 50% × 90% = 4.5%. Together that's 94.5%.

When one agent is better

More agents aren't always better. Watch for three warning signs:

  • Tightly coupled work. This is work where every decision depends on every other one, like most coding and writing. Split it up, and each agent works from only part of the picture, so the pieces clash. Cognition, the company behind the Devin coding agent, argued in 2025 for single-threaded agents for this reason. Single-threaded means one agent doing the steps in order.
  • Cost. Each subagent re-reads its instructions and context. So token use, and the bill, rises sharply. Use several agents only when the job is worth the extra spend.
  • Debugging. When something fails, it's harder to trace across many agents. Log every agent's inputs, tool calls and outputs.
Quick check
Two setups use the same model. Why does the one with five subagents cost much more than a single agent?
  1. Each call is charged a fixed fee, whatever its length
  2. Each subagent re-reads its own instructions and context, so token use rises sharply
  3. Running calls at the same time raises the price of every token
  4. It doesn't: cost depends only on the length of the final answer
Show the answer

Each subagent re-reads its own instructions and context, so token use rises sharply

You pay for every token each agent reads and writes. Five agents reading their own instructions and context means many more tokens than one agent.

What the code looks like

A typical orchestrator gives the lead model a tool for starting subagents. Each subagent is a separate model call with its own instructions. The sketch below shows the idea. run_agent makes one model call. A thread pool runs three research subagents at the same time, each with a fresh context. A final call combines their findings into one briefing.

import anthropic
from concurrent.futures import ThreadPoolExecutor

client = anthropic.Anthropic()

def run_agent(instructions, task):
    reply = client.messages.create(model="claude-opus-5-5", max_tokens=4000, system=instructions,
                                   messages=[{"role": "user", "content": task}])
    return next(b.text for b in reply.content if b.type == "text")

subtasks = ["pricing pages of our 5 main competitors", "their review-site ratings", "their ad messaging"]
with ThreadPoolExecutor() as pool:             # subagents run in parallel, each with a fresh context
    findings = list(pool.map(lambda t: run_agent("Research thoroughly. Cite sources.", t), subtasks))
report = run_agent("Combine the findings into a competitor briefing; flag contradictions.", "\n\n".join(findings))

This sketch is for reading, not running, because it calls a real model API. In practice the subagents would also get tools, such as web search or the MCP servers from Phase 1. And the lead would choose the subtasks itself.

For marketers: some jobs are a good fit. Think of competitive research across many brands, auditing hundreds of landing pages or tagging a big archive. Each splits into independent pieces that you merge at the end. A single campaign concept is not a good fit. One agent, or one person, holding the whole picture does better.

Practice what you just read

  • 5 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

Which task is the best fit for an orchestrator with parallel subagents?

  1. Summarizing reviews of 40 competitor products, one subagent per competitor, then a combined briefing
  2. Writing a single 300-word brand manifesto, with each subagent taking a sentence or two of it
  3. Fixing one bug in a small script, with one subagent reading it and another one running the tests
  4. Choosing between two taglines, with a subagent arguing for each of them and a third one deciding

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson