Subagent - 干净上下文的委托执行

2857 字
14 分钟
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"] # 无 task
PARENT_TOOLS = ["bash", "read_file", "write_file", "edit_file", "todo", "task"] # 有 task

task 工具只在父端注册,子端没有。这是 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(...)
→ ???

两个问题:

  1. 目标偏移:层层委托后,Subagent 1.1.1.1 可能忘记原始任务是”找测试框架”
  2. 成本失控:无法预测总共多少次 API 调用

设计决策:禁止递归是架构选择,防止目标偏移和成本失控。

验证你的理解#

Q2:为什么禁止递归(子端无 task 工具)?

:::details 答案 不是 messages 增长问题,是设计决策。

递归会导致:

  1. 目标偏移——层层委托后原始目标可能丢失
  2. 成本失控——无法预测总调用次数

扁平设计保持顶层控制:所有 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 LoadingSubagent 加载 Skill子端注入知识
L06 Context CompactSubagent 摘要策略返回值优化
L07 Task SystemSubagent + 持久化状态保存
L08 BackgroundSubagent 异步执行并行委托
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 层级回答 两个风险:

  1. 目标偏移:Subagent 1.1.1.1.1 可能忘记原始任务。每层委托都会重新解读任务,层层传递后语义可能失真。

  2. 成本失控:无法预测总 API 调用次数。每层 Subagent 都可能跑 30 轮,5 层就是 150+ 轮,且无法提前知道会派多少层。

扁平设计的选择:禁止递归,保持顶层控制。所有 Subagent 由父端派发,目标清晰,成本可预测。

这不是技术限制(技术上完全可以允许递归),是架构选择——权衡顶层控制 vs 层层灵活。 :::

本章目标:让每个核心概念都从 L0 升级到 L2。理解 L04,不只是记住代码,而是掌握”如何在循环上委托执行”的设计原则。

六、复习可视化#

Subagent 流程#

flowchart TB subgraph Parent["父 Agent"] P1["messages = [...]<br/>(可能很长的历史)"] P2["tool: task<br/>prompt='找测试框架'"] P3["收到 tool_result<br/>(摘要文本)"] P4["继续主对话"] end subgraph Subagent["子 Agent"] S1["messages = []<br/>(干净上下文)"] S2["LLM API Call"] S3["执行工具 1"] S4["执行工具 2"] S5["..."] S6["执行工具 N"] S7["stop_reason ≠ tool_use"] S8["返回最后 text<br/>丢弃中间过程"] end subgraph Isolation["上下文隔离"] I1["父端 messages 不受污染"] I2["子端 messages 完全独立"] I3["中间过程全部丢弃"] end P1 --> P2 P2 -->|"dispatch"| S1 S1 --> S2 S2 -->|"tool_use"| S3 S3 --> S4 S4 --> S5 S5 --> S6 S6 --> S2 S2 -->|"end_turn"| S7 S7 --> S8 S8 -->|"summary"| P3 P3 --> P4 I1 -.-> P1 I2 -.-> S1 I3 -.-> S8 style S1 fill:#9f9,stroke:#333,stroke-width:2px style S8 fill:#ff9,stroke:#333,stroke-width:2px

扁平 vs 层级对比#

flowchart LR subgraph Flat["扁平设计(禁止递归)"] F1["Parent"] F2["Subagent 1"] F3["Subagent 2"] F1 --> F2 F1 --> F3 end subgraph Hierarchical["层级设计(允许递归)"] H1["Parent"] H2["Subagent 1"] H3["Subagent 1.1"] H4["Subagent 1.1.1"] H1 --> H2 H2 --> H3 H3 --> H4 end Flat -->|"控制成本"| R["顶层控制"] Hierarchical -->|"风险"| R2["目标偏移<br/>成本失控"] style F1 fill:#9f9,stroke:#333 style H4 fill:#f99,stroke:#333

返回值机制流程#

flowchart TB subgraph Execution["执行过程"] E1["工具调用 1"] E2["工具调用 2"] E3["..."] E4["工具调用 N"] E5["最后一次响应"] end subgraph Discard["丢弃"] D1["所有 tool_use blocks"] D2["所有 tool_result"] D3["中间 reasoning"] end subgraph Return["返回"] R1["text 部分<br/>(LLM 总结)"] R2["父端收到 tool_result"] end E1 --> E2 --> E3 --> E4 --> E5 E1 -.-> D1 E2 -.-> D1 E4 -.-> D1 E5 --> R1 D1 --> D2 --> D3 R1 --> R2 style D1 fill:#f99,stroke:#333 style D2 fill:#f99,stroke:#333 style D3 fill:#f99,stroke:#333 style R1 fill:#9f9,stroke:#333,stroke-width:2px

Unix 管道 vs LLM 上下文#

flowchart LR subgraph Unix["Unix 管道"] U1["进程 A"] U2["完整数据"] U3["进程 B"] U4["完整数据"] U1 --> U2 --> U3 --> U4 end subgraph LLM["LLM 上下文"] L1["父 Agent"] L2["语义摘要<br/>(有损压缩)"] L3["子 Agent<br/>(丢弃一切)"] L4["原始数据<br/>(全部丢弃)"] L1 --> L2 L3 --> L4 L4 -.->|"丢弃"| X["❌"] end style U2 fill:#9f9,stroke:#333 style U4 fill:#9f9,stroke:#333 style L2 fill:#ff9,stroke:#333 style L4 fill:#f99,stroke:#333

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Subagent - 干净上下文的委托执行
https://firefly.cuteleaf.cn/posts/learn-claude-code/04-subagent/
作者
AltumSisy
发布于
2026-04-07
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
AltumSisy
Hello, I'm AltumSisy.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
3
标签
25
总字数
68,347
运行时长
0
最后活动
0 天前

文章目录