Learn Claude Code
s04

子 Agent

规划与协调

Process Isolation = Context Isolation

146 LOC5 个工具Subagent spawn with isolated messages[]
Process isolation gives context isolation for free

子智能体使用全新的消息列表运行, 与父智能体共享文件系统, 仅返回摘要 -- 保持父上下文的整洁。

问题

随着智能体工作, 它的消息数组不断增长。每次工具调用、每次文件读取、每次 bash 输出都在累积。20-30 次工具调用后, 上下文窗口充满了无关的历史。为了回答一个简单问题而读取的 500 行文件, 会永久占据上下文中的 500 行空间。

这对探索性任务尤其糟糕。"这个项目用了什么测试框架?" 可能需要读取 5 个文件, 但父智能体的历史中并不需要这 5 个文件的全部内容 -- 它只需要答案: "pytest, 使用 conftest.py 配置。"

在本课程里, 一个实用解法是 fresh messages[] 隔离: 以 messages=[] 启动一个子智能体。子智能体进行探索、读取文件、运行命令。完成后, 只有最终的文本响应返回给父智能体。子智能体的全部消息历史被丢弃。

解决方案

Parent agent                     Subagent
+------------------+             +------------------+
| messages=[...]   |             | messages=[]      | <-- fresh
|                  |  dispatch   |                  |
| tool: task       | ---------->| while tool_use:  |
|   prompt="..."   |            |   call tools     |
|                  |  summary   |   append results |
|   result = "..." | <--------- | return last text |
+------------------+             +------------------+
          |
Parent context stays clean.
Subagent context is discarded.

工作原理

  1. 父智能体拥有一个 task 工具用于触发子智能体的生成。子智能体获得除 task 外的所有基础工具 (不允许递归生成)。
PARENT_TOOLS = CHILD_TOOLS + [
    {"name": "task",
     "description": "Spawn a subagent with fresh context.",
     "input_schema": {
         "type": "object",
         "properties": {
             "prompt": {"type": "string"},
             "description": {"type": "string"},
         },
         "required": ["prompt"],
     }},
]
  1. 子智能体以全新的消息列表启动, 仅包含委派的 prompt。它共享相同的文件系统。
def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]
    for _ in range(30):  # safety limit
        response = client.messages.create(
            model=MODEL, system=SUBAGENT_SYSTEM,
            messages=sub_messages,
            tools=CHILD_TOOLS, max_tokens=8000,
        )
        sub_messages.append({
            "role": "assistant", "content": response.content
        })
        if response.stop_reason != "tool_use":
            break
        # execute tools, append results...
  1. 只有最终文本返回给父智能体。子智能体 30+ 次工具调用的历史被丢弃。
    return "".join(
        b.text for b in response.content if hasattr(b, "text")
    ) or "(no summary)"
  1. 父智能体将此摘要作为普通的 tool_result 接收。
if block.name == "task":
    output = run_subagent(block.input["prompt"])
results.append({
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": str(output),
})

核心代码

子智能体函数 (来自 agents/s04_subagent.py, 第 110-128 行):

def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]
    for _ in range(30):
        response = client.messages.create(
            model=MODEL, system=SUBAGENT_SYSTEM,
            messages=sub_messages,
            tools=CHILD_TOOLS, max_tokens=8000,
        )
        sub_messages.append({"role": "assistant",
                             "content": response.content})
        if response.stop_reason != "tool_use":
            break
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                output = handler(**block.input)
                results.append({"type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(output)[:50000]})
        sub_messages.append({"role": "user", "content": results})
    return "".join(
        b.text for b in response.content if hasattr(b, "text")
    ) or "(no summary)"

相对 s03 的变更

组件之前 (s03)之后 (s04)
Tools55 (基础) + task (仅父端)
上下文单一共享父 + 子隔离
Subagentrun_subagent() 函数
返回值不适用仅摘要文本

设计原理

在本节中, fresh messages[] 隔离是一个近似实现上下文隔离的实用办法。全新的 messages[] 意味着子智能体从不携带父级历史开始。代价是通信开销 -- 结果必须压缩回父级, 丢失细节。这是消息历史隔离策略, 不是操作系统进程隔离本身。限制子智能体深度 (不允许递归生成) 防止无限资源消耗, 最大迭代次数确保失控的子任务能终止。

试一试

cd learn-claude-code
python agents/s04_subagent.py

可以尝试的提示:

  1. Use a subtask to find what testing framework this project uses
  2. Delegate: read all .py files and summarize what each one does
  3. Use a task to create a new module, then verify it from here