Module 05 · Lesson 6

Several agents working together

A supervisor agent splits a task, hands it to three worker agents with independent contexts running in parallel, then combines the results. Compared with a single agent, it used a third fewer tokens, and the supervisor's context was only 472 tokens. What multi-agent systems are really worth, and what they cost.

  • About 40 minutes
  • Level: Intermediate
  • Tested: 2026-09-14 deepseek-flash

Code and program output are shown exactly as they ran, so comments and printed output are in Chinese.

Work too big for one person can be shared among several. Agents are the same: a "supervisor" splits a big task into smaller ones, hands them to several "worker" agents, and combines the results when they're done. This is called a multi-agent system.

It sounds natural and appealing, and many frameworks are designed specifically for it. But before handing a task to a crowd of agents, be clear about what multi-agent systems actually solve and what they cost. This lesson runs a comparison using the task from Lesson 4.

Common ways to organise them

  • Supervisor and workers: the supervisor splits the task, assigns the pieces and combines the results; each worker completes one subtask. Best when the subtasks are independent.
  • Pipeline: the first agent's output is the second's input, for example "researcher gathers material → writer drafts → reviewer checks". This is really more like a workflow in which each step is done by an agent.
  • Debate: several agents each answer the same question and challenge one another, and a vote or a referee decides.

This lesson builds the first, because it best shows the core benefit of multiple agents.

Experiment: a supervisor and three workers

The task is still the one from Lesson 4: "Compare whether httpx's Client and AsyncClient are configured the same way for three things: timeouts, proxies and HTTP/2. Put it in a table, and cite the documentation source in every cell."

This task splits naturally: timeouts, proxies and HTTP/2 are unrelated and can be looked up separately.

The supervisor's work has three steps:

def supervisor():
    # 1. 主管拆任务:只输出 JSON,不调用工具
    plan, usage = model([{"role": "user", "content":
                          TASK + '\n\n把这个任务拆成互相独立、可以同时进行的子任务,每个子任务只查一件事。'
                                 '输出 json:{"subtasks": ["子任务1", ...]}'}], None)
    subtasks = json.loads(plan.content[plan.content.find("{"):plan.content.rfind("}") + 1])["subtasks"]

    # 2. 工人并行执行,每个工人都从空白的上下文开始,只看得到自己的子任务
    def work(subtask):
        return run_agent(model, subtask + "\n只回答这一件事,写清楚结论和文档出处(文件名和行号),不超过 150 字。",
                         max_steps=8, verbose=False)

    with ThreadPoolExecutor(len(subtasks)) as pool:
        results = list(pool.map(work, subtasks))

    # 3. 主管汇总:只看工人交回的简短结论,看不到工人查过的原文
    reports = "\n\n".join(f"子任务:{s}\n结论:{a}" for s, (a, _) in zip(subtasks, results))
    final, usage = model([{"role": "user", "content": TASK + "\n\n下面是各子任务的调查结论,据此完成任务:\n\n" + reports}], None)
    ……

The workers are simply Lesson 2's run_agent, unchanged. Each worker starts with an empty message list and knows only its own subtask. Asking them for "no more than 150 characters" keeps what they hand back to the supervisor concise.

The control is a single agent doing the whole task directly. Full code in code/05-agents/multi_agent.py.

Results

A single agent:

===== 单个智能体
[第 1 步] grep_docs({"keyword": "AsyncClient"}) → ……
[第 1 步] grep_docs({"keyword": "timeout"}) → ……
[第 1 步] grep_docs({"keyword": "proxy"}) → ……
[第 1 步] grep_docs({"keyword": "http2"}) → ……
[第 2 步] read_doc({"path": "advanced/timeouts.md", "start": 1, "end": 70}) → ……
[第 2 步] read_doc({"path": "advanced/proxies.md", "start": 1, "end": 50}) → ……
[第 2 步] read_doc({"path": "http2.md", "start": 20, "end": 75}) → ……
[第 3 步] read_doc({"path": "advanced/timeouts.md", "start": 70, "end": 120}) → ……
[第 3 步] grep_docs({"keyword": "AsyncClient(proxy"}) → 没有找到 AsyncClient(proxy
[第 3 步] grep_docs({"keyword": "AsyncClient(timeout"}) → 没有找到 AsyncClient(timeout
[第 3 步] read_doc({"path": "advanced/clients.md", "start": 1, "end": 40}) → ……
[第 4 步] read_doc({"path": "async.md", "start": 1, "end": 50}) → ……
[第 4 步] read_doc({"path": "api.md", "start": 30, "end": 50}) → ……
  共 5 次模型调用,21047 词元,9 秒

A supervisor and three workers:

===== 主管 + 工人
  主管拆出了 3 个子任务:
    - 调查 httpx.Client 和 AsyncClient 在超时(timeout)配置上的方式是否一样,并记录文档文件名和行号
    - 调查 httpx.Client 和 AsyncClient 在代理(proxy)配置上的方式是否一样,并记录文档文件名和行号
    - 调查 httpx.Client 和 AsyncClient 在 HTTP/2 配置上的方式是否一样,并记录文档文件名和行号
  工人「调查 httpx.Client 和 As…」:3 次调用,3943 词元,交回 222 字
  工人「调查 httpx.Client 和 As…」:3 次调用,4506 词元,交回 208 字
  工人「调查 httpx.Client 和 As…」:3 次调用,4094 词元,交回 253 字
  主管汇总时的输入:472 词元
  共 11 次模型调用,13720 词元,8 秒

The two final answers reach essentially the same conclusions: HTTP/2 has clear support in the docs (line 50 of http2.md, "HTTP/2 support is available on both Client and AsyncClient"), while the timeout and proxy examples are written only for Client, so AsyncClient taking the same parameters is an inference.

Reading the results

A third fewer tokens. The multi-agent setup made 11 model calls, more than twice the single agent's 5, yet used fewer tokens in total: 13,720 versus 21,047.

The reason is how context grows. By step 4, the single agent's message list holds all the search results and doc content for all three topics, timeouts, proxies and HTTP/2, and it carries that whole pile into every model call. Each worker holds only the content for its own topic, so its context stays small. Three small contexts add up to less than one that keeps swelling.

The supervisor's context is clean. When combining, the supervisor's input is only 472 tokens: three conclusions of a couple of hundred characters each. It never sees the original doc text the workers read. This is the core benefit of multiple agents: context isolation. Each agent sees only what it needs, and the supervisor isn't buried in detail. On longer, more complex tasks the benefit is even more pronounced.

About the same time. The three workers run in parallel, so despite more calls in total, the overall time (8 seconds) is about the same as the single agent (9 seconds). Run serially, it would be much slower.

The single agent's answer started in English again. The first sentence of its final answer was "I have enough evidence. Let me summarize.", and only then Chinese. When the context is full of English docs, the model's language drifts easily; it happened once in Lesson 4 too. The multi-agent supervisor's context contains almost nothing but Chinese conclusions, so it doesn't have this problem.

The costs

  • Details get lost. Workers hand back only a 150-character conclusion, and the supervisor never gets the original text. If a worker's conclusion is vague or leaves out a key citation, the supervisor can't notice, let alone correct it. The more precisely you specify the format workers return (conclusion, evidence, source, uncertainties), the smaller this problem.
  • The split itself can be wrong. This task was easy to split, with three unrelated topics. If subtasks depend on each other, such as "first find the failing function, then look at its callers", a parallel split doesn't work.
  • More calls, a more complex system. Every extra agent means another prompt to write and another trace to read. When something goes wrong, you first have to work out whether the split, one of the workers, or the combining step is at fault.

When to use it

  • The subtasks are independent and each needs to read a lot of material. Context isolation saves money and lets each agent stay focused.
  • You want to keep the main thread of a long task clean. Coding agents like Claude Code hand subtasks such as "search the codebase for something" to a subagent, which combs through dozens of files and hands back only what it found, so the main conversation isn't filled with the intermediate search steps.
  • Different subtasks need different tools or different prompts. Giving each worker only the tools it needs is less error-prone than giving one agent a big pile of tools (Lesson 3 covered the problems with many tools).

For most tasks, one agent is enough. Start with one, and consider splitting only when the context swells too much or the task naturally splits into parallel pieces.

Exercises

  1. Change the workers' "no more than 150 characters" to "no more than 50 characters" and rerun. Are the final answer's citations still complete?
  2. Have the workers hand back conclusions in a fixed format: Conclusion: … Original evidence: … Source: … Uncertain: …. Does the quality of the combined answer change?
  3. Design a question whose subtasks depend on each other (such as "find the function in httpx that raises ReadTimeout, then explain which public APIs call it"), let the supervisor split it, and see whether the split is right.

Self-check

1. The multi-agent setup makes more calls than a single agent. Why does it use fewer tokens in total?

A single agent's context keeps accumulating the search results and docs for every subtopic, and every later step carries all of it into the model call. In a multi-agent setup, each worker holds only its own subtask's content, so its context stays small, and the supervisor sees only brief conclusions. Several small contexts add up to less than one that keeps growing.

2. What does "context isolation" mean in a multi-agent system? What are its pros and cons?

Each agent sees only the information it needs: workers see only their own subtask and the material they found, and the supervisor sees only the conclusions the workers hand back. The upside is that every context is small and focused, which saves money and keeps the supervisor from drowning in detail. The downside is that information is lost at handover, and the supervisor can't check details the workers didn't pass on.

3. What kind of task is unsuitable for splitting among several workers in parallel?

Tasks whose subtasks depend on each other. If a later step builds on what an earlier one finds, it can't run in parallel; split it anyway and the later workers lack the earlier information. Also, when a task is simple enough for one agent to finish in a few steps, splitting only adds calls and complexity.