Subagent - 干净上下文的委托执行
L04 要回答一个问题:如何在 L03 的基础上让 Agent 能处理复杂任务,而不污染主对话上下文?
L03 给了我们骨架:TodoManager + 单线程约束 + nag_reminder。但真实 Agent 执行复杂任务时会积累大量上下文——读很多文件、跑很多命令、记很多中间结果。这些全部留在主对话里,会污染父 Agent 的思维。怎么做?
本章讲三个机制:
| 机制 | 解决什么问题 | 核心代码 |
|---|---|---|
| 上下文隔离 | 父端 messages 不受污染 | sub_messages = [] 独立启动 |
| 禁止递归 | 防止目标偏移和成本失控 | 子端无 task 工具 |
| 返回值机制 | 只保留摘要,丢弃过程 | return text 部分 |
这三个机制叠加在 L03 的 Dispatch Map 上,循环本身不变。理解 L04,就理解了 Agent 如何”委托执行不污染上下文”。
一、上下文隔离:独立 messages[]
“大任务拆小,每个小任务干净的上下文。”
这句话概括了 Subagent 的核心价值。看代码:
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, # 独立 messages[] tools=CHILD_TOOLS, # 子端工具集 max_tokens=8000, ) sub_messages.append({"role": "assistant", "content": response.content}) if response.stop_reason != "tool_use": break # 执行工具,append tool_result...第一行 sub_messages = [{"role": "user", "content": prompt}] 是关键——Subagent 用独立 messages[] 启动,父端的历史完全不带入。
父端 vs 子端对比
| 视角 | messages 内容 | 长度 | 价值 |
|---|---|---|---|
| 父端 | 可能几十轮对话 + 任务规划 + 中间结果 | 很长 | 宝贵资源,需保护 |
| 子端 | 只有 prompt 一句话 | 干净 | 从零开始,不受污染 |
上下文隔离的本质:认知保护——父 Agent 保持思维清晰,Subagent 的 30+ 次工具调用全部丢弃。
如果不隔离会怎样?
假设共享 messages:
父端历史: - User: 请帮我重构这个项目 - Assistant: [分析...调用20个工具...] - User: [tool_result...20个结果...] - Assistant: [规划...] ...(父端已经很长)
子端执行: - Assistant: [读文件1...] → tool_result: [500行代码] - Assistant: [读文件2...] → tool_result: [300行代码] - Assistant: [读文件3...] → tool_result: [800行代码] ...(子端又增加大量内容)
结果:父端 messages 爆炸,思维混乱,无法继续规划。设计权衡:Subagent 创建成本是一次 API 调用(不是”小”),但换来的是父端上下文的长期清洁。
验证你的理解
Q1:为什么 Subagent 用独立 messages[] 而不是共享父端 messages?
:::details 答案 上下文隔离:父 Agent 的 messages 不受污染。
Subagent 可能跑 30+ 次工具调用,读很多文件,产生大量 tool_result。如果共享 messages,这些全部留在父端,思维会混乱。
独立 messages[] 让子端从零开始,执行完只返回摘要,父端保持干净。 :::
二、禁止递归:设计决策
上下文隔离解决污染问题,但还有一个风险:递归嵌套。
如果子端也能派 Subagent,会发生什么?
# 禁止递归:子端无 task 工具CHILD_TOOLS = ["bash", "read_file", "write_file", "edit_file"] # 无 taskPARENT_TOOLS = ["bash", "read_file", "write_file", "edit_file", "todo", "task"] # 有 tasktask 工具只在父端注册,子端没有。这是 Harness 层的主动设计决策,不是技术限制。
扁平 vs 层级权衡
| 设计 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| 扁平 | 禁止递归 | 顶层控制,成本可控 | 委托深度有限 |
| 层级 | 允许递归 | 层层委托,更灵活 | 目标偏移,成本失控 |
Claude Code 选择扁平设计:父端保持全局控制,所有 Subagent 都由顶层派发。
递归的风险
假设允许递归:
Parent 派 Subagent 1(找测试框架) → Subagent 1 派 Subagent 1.1(读配置文件) → Subagent 1.1 派 Subagent 1.1.1(分析某个函数) → Subagent 1.1.1 派 Subagent 1.1.1.1(...) → ???两个问题:
- 目标偏移:层层委托后,Subagent 1.1.1.1 可能忘记原始任务是”找测试框架”
- 成本失控:无法预测总共多少次 API 调用
设计决策:禁止递归是架构选择,防止目标偏移和成本失控。
验证你的理解
Q2:为什么禁止递归(子端无 task 工具)?
:::details 答案 不是 messages 增长问题,是设计决策。
递归会导致:
- 目标偏移——层层委托后原始目标可能丢失
- 成本失控——无法预测总调用次数
扁平设计保持顶层控制:所有 Subagent 都由父端派发,成本可预测,目标不跑偏。 :::
三、返回值机制:有损压缩
上下文隔离和禁止递归解决了”怎么执行”,返回值机制解决”怎么汇报”。
return "".join( # 返回最后一次 text 部分 b.text for b in response.content if hasattr(b, "text")) or "(no summary)"Subagent 返回最后一次响应的 text 部分,不是 tool_use block。中间过程全部丢弃。
丢弃什么,保留什么
| 内容类型 | 去向 | 原因 |
|---|---|---|
| tool_use blocks | 丢弃 | 父端不需要知道具体调用了什么工具 |
| tool_result | 丢弃 | 中间结果不需要保留 |
| 中间 reasoning | 丢弃 | 过程思考只有子端需要 |
| 最后 text 部分 | 返回 | LLM 的总结,父端需要的 |
返回值机制的本质:有损压缩——只保留语义摘要,丢弃所有过程细节。
Unix 管道 vs LLM 上下文
| 特性 | Unix 管道 | LLM 上下文 |
|---|---|---|
| 驱动方式 | 数据驱动 | 语义驱动 |
| 传输方式 | 无损传输 | 有损压缩 |
| 数据完整性 | 完整流动 | 只保留摘要 |
| 设计哲学 | 数据不丢失 | 关键信息足够 |
范式对比:Unix 是数据驱动,需要完整数据流动。LLM 是语义驱动,只需要关键信息——中间过程是噪音,摘要才是信号。
验证你的理解
Q3:Subagent 返回什么?为什么不是 tool_use block?
:::details 答案 返回最后一次响应的 text 部分(LLM 的总结),不是 tool_use block。
如果返回 tool_use block:
- 父端无法理解语义——“read_file(path=‘x’)” 只是工具调用,不是结果
- 父端需要自己解读——增加认知负担
返回 text 部分:
- 父端直接得到摘要——“找到测试框架是 pytest”
- 语义清晰,可直接使用
中间过程(所有工具调用和结果)全部丢弃。父端只需要结果,不需要细节。 :::
四、回顾:L04 在循环上叠加了什么
回到开头的问题:如何在 L03 的基础上让 Agent 能处理复杂任务,而不污染主对话上下文?
答案:
| 叠加机制 | 代码位置 | 循环的变化 |
|---|---|---|
| 上下文隔离 | run_subagent() 内部 | 不变——子端独立循环 |
| 禁止递归 | CHILD_TOOLS 无 task | 不变——工具注册差异 |
| 返回值机制 | return text 部分 | 不变——只是返回值处理 |
循环本身仍是 L01 的 while True + stop_reason + messages[]。L04 只是在”委托层”叠加了上下文隔离和安全机制。
与后续课程的关系
理解 L04 后,你会发现后续课程都在 Subagent 机制上叠加——循环本身始终不变:
| 课程 | 叠加什么 | 变化在哪 |
|---|---|---|
| L05 Skill Loading | Subagent 加载 Skill | 子端注入知识 |
| L06 Context Compact | Subagent 摘要策略 | 返回值优化 |
| L07 Task System | Subagent + 持久化 | 状态保存 |
| L08 Background | Subagent 异步执行 | 并行委托 |
| L09 Agent Teams | 多 Subagent 协作 | 跨 agent 任务分配 |
核心洞察:Subagent 是”委托”思想的起点。理解 L04,就理解了 Agent 如何”委托执行不污染上下文”。后续课程都在扩展委托机制——按需知识、异步执行、多 agent 协作。
五、心智模型升级:从 L0 到 L2
读完这篇文章,你的理解经历了怎样的升级?
L04 核心概念的理解升级路径
| 概念 | L0(表面) | L1(关联) | L2(深层) |
|---|---|---|---|
| 上下文隔离 | ”独立 messages[]" | "父端不受污染,子端干净启动" | "认知保护——父 Agent 保持思维清晰。类似进程隔离:独立内存空间,互不干扰。创建成本是一次 API 调用,换来父端长期清洁” |
| 禁止递归 | ”子端无 task 工具" | "防止无限嵌套" | "设计决策——扁平 vs 层级权衡。扁平保持顶层控制、成本可控;层级风险是目标偏移、成本失控。不是技术限制,是架构选择” |
| 返回值机制 | ”返回摘要" | "返回 text 部分,丢弃中间过程" | "有损压缩——LLM 语义驱动需关键信息,Unix 数据驱动需完整数据。范式差异:LLM 只保留语义摘要,中间过程是噪音” |
如何检验你达到了 L2?
用这个问题自测:
如果允许递归,Subagent 嵌套 5 层会发生什么?
:::details L2 层级回答 两个风险:
目标偏移:Subagent 1.1.1.1.1 可能忘记原始任务。每层委托都会重新解读任务,层层传递后语义可能失真。
成本失控:无法预测总 API 调用次数。每层 Subagent 都可能跑 30 轮,5 层就是 150+ 轮,且无法提前知道会派多少层。
扁平设计的选择:禁止递归,保持顶层控制。所有 Subagent 由父端派发,目标清晰,成本可预测。
这不是技术限制(技术上完全可以允许递归),是架构选择——权衡顶层控制 vs 层层灵活。 :::
本章目标:让每个核心概念都从 L0 升级到 L2。理解 L04,不只是记住代码,而是掌握”如何在循环上委托执行”的设计原则。
六、复习可视化
Subagent 流程
扁平 vs 层级对比
返回值机制流程
Unix 管道 vs LLM 上下文
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!