第 6 章 从单兵死磕到分身协作 · Agent架构实操六 开始测验

从「单兵死磕」到「分身协作」

追深调用链 bug 时,几十轮工具结果塞满历史。更合适:把自包含子任务交给一次性 Subagent,从全新历史开始探索,父 Agent 只接收一句有证据的结论。本章新增 task 工具。
⏱ 约 12 分钟 🧬 历史隔离 🚫 禁止递归委派 +subagent capability

🎯 本章导读

读完这一章,你应该能用一句话回答下面每个问题。
读完能做到
  1. 说清「委派」里隔离的到底是什么:只有消息历史和工具注册表,其他全部共享。
  2. 写一个自包含的 task 工具:description 让子 Agent 不靠父 history 也能独立完成任务。
  3. 说出父 history 在委派后只多哪三条消息,以及为什么子 Agent 的推理过程无法审计。
  4. 解释为什么必须禁止递归委派(三层防线),以及子 Agent 跑满 30 轮返回什么。
  5. 跑通第 6 章:让父 Agent 委派一个子 Agent 干一件事,再自己给结论。
你需要先具备什么
需要说明
读完第 1—5 章会看 AgentRunner、Hook、权限、tool 注册即可
多 Agent 概念不需要。本章就是第一次引入「子 Agent」
建议的阅读路线
本章导读(你在这)
  ↓
① 先看一眼一次真实的委派    ← 子 Agent 干活的完整日志
  ↓
② 这章解决什么问题          ← 上下文隔离,不是沙箱
  ↓
③ 严格的 task 输入          ← 自包含 description
  ↓
④ SubagentTool = 执行边界    ← 复用同一个 AgentRunner
  ↓
⑤ 历史隔离 + 边界共享        ← 三、四
  ↓
⑥ 禁止递归委派              ← 三层防线
  ↓
⑦ 30 轮上限 + 脱敏错误       ← 六
  ↓
⑧ 组合根 + 离线证明          ← 七、八
  ↓
⑨ 运行 + 实验 + 差异 + 小结   ← 九~十一
最该记住的一句 委派不创造新路径:子 Agent 该弹的审批照样弹,该拦的越界照样拦。

📇 术语速查

本章第一次出现的词。先扫一眼,正文里遇到不认识的词回来查即可。不用背
术语一句话解释
委派(task)把一段自包含探索交给独立子 Agent,只拿回一句结论
SubagentTool执行边界的实现:复用同一个 AgentRunner 跑一次内部 run()
自包含 description子 Agent 只能看到 description,看不到父 history,所以描述必须完整
历史隔离父与子各有一份独立 #history,互不可见
边界共享Hook、权限、workspace、identity、进程全部共享一个实例
递归委派子 Agent 自己再调 task。三层防线禁止
evidence-based 结论因为子推理不可审计,所以要求子 Agent 给出有文件证据的结论
subagent_turn_limit子 Agent 跑满 30 轮返回的结构化错误码
subagent_configuration_error子工具集非法(例如包含 task)时的错误码
toolRoundObserver父挂的「计划陈旧提醒」观察器;子 Agent 刻意没挂

📡 先看一眼一次真实的委派

父子日志交错 + 子 Agent 越界写入被拦。
父子日志交错
npm run ch06 -- --prompt "调用 task 独立检查本项目使用的测试框架,只返回有文件证据的结论"
# stderr 上父子交错:
#   [Hook] UserPromptSubmit     (父)
#   [Hook] PreToolUse: task     (父)
#   [Hook] UserPromptSubmit     ← 子 Agent 那次 run() 也打日志!
#   [Hook] PreToolUse: read_file (子)
#   ...
# Hook 是共享实例,所以子 Agent 的 run() 也会打 UserPromptSubmit 和 Stop
观测局限 共享 HookRegistry 的代价是日志里分不清父子。想区分的话,Hook 回调可以自己维护嵌套深度计数——那是读者的扩展,本章不做。
那如果子 Agent 想越界写呢

子 Agent 的 write_file 照样走第 3 章权限:工作区内弹审批、工作区外被硬边界 deny。`task` 自己是 allow(读取类),但子 Agent 的写入照样弹到你面前——审批框上不标注「这是子 Agent 发起的」,因为父子共享 identity。

🧠 核心概念

点击展开。
1 · 这章解决什么:上下文隔离

第 5 章 todo_write 解决「单会话如何保存计划」,但不减少探索过程本身的上下文。第 6 章解决「如何隔离一段独立探索」:父调用 task,运行时创建新 AgentRunner:

父 Agent
  assistant: task(description=...)
      ↓
一次性 Subagent
  system: 专注完成委派任务,不再委派
  user: description
  history: 全新
  tools: shell/read_file/write_file/edit_file/glob/todo_write
      ↓
父 Agent
  tool: 子 Agent 的 finalText
隔离边界 「隔离」只指消息历史隔离。子看不到父历史,父看不到子中间轨迹;但子写出的文件会保留,父子仍同进程、同 Hook、同权限、同 workspace、同 identity。Subagent 不是操作系统沙箱。
2 · 严格的 task 输入:自包含

description 必须 trim 后非空,.strict() 拒绝额外字段。为什么强调「自包含」?因为子 Agent 不继承父 history。

坏描述 "继续处理刚才那个问题" — 子 Agent 看不到「刚才」,信息不足。
好描述 "检查 package.json 和测试目录,确认项目测试框架;只返回能由文件内容支持的结论。" — 对象、目标、证据要求都明确。
3 · 父 history 为什么保持干净

子 Agent 可多轮 read_file/glob/shell,但 SubagentTool 只返回 result.finalText。父 history 只保存:

user:      父任务
assistant: tool_calls=[task(parent-task)]
tool:      子 Agent 的 finalText, toolCallId=parent-task
assistant: 父 Agent 的最终回答

子内部历史不 append 到父。父 Loop 仍按 tool_call_id 配对写 tool message,OpenAI 工具配对契约无例外。

4 · 历史隔离,运行边界共享

子 Agent 用新 AgentRunner,但不是新权限系统。构造时直接传入父会话组合根创建的同一实例:hooks、permissionPolicy、workspace、identity。

子工具调用顺序仍是:PreToolUse → PermissionPolicy → handler → PostToolUse。子尝试向 workspace 外写入,第 3 章硬边界先 deny,审批器不会被调用。合法的子 write_file 仍按原策略请求审批并写入同一 workspace,父之后可继续读。

5 · 为什么禁止递归委派(两层防护)

P06 父工具有 7 个(含 task),子工具工厂只创建前 6 个(不含 task)。两层防护:

  1. 工厂不注册:正常组装路径,子工具集不含 task。
  2. 运行时拒绝:若 tools.names.includes("task") 返回 subagent_configuration_error,保护工厂被错误改写。

若子模型仍生成 task(...),Registry 返回 unknown_tool: Unknown tool: task,不会创建孙 Agent。

6 · 30 轮上限与脱敏错误

DEFAULT_SUBAGENT_MAX_TURNS=30,构造时不允许高于 30。子连续 30 轮只返回工具调用时,公共 Loop 抛 AgentLimitError,task handler 转成稳定错误:

Error [subagent_turn_limit]: Subagent exceeded max_turns=30 without a final answer

父不会收到第 30 轮最后一个工具输出,不能把它误认为结论。其他异常也脱敏:subagent_execution_error,不泄漏 API Key/路径/适配器细节。

7 · effect: external 的含义

task 的 effect 是 "external"(发起一段独立执行,而非直接读写文件)。第 3 章审批仍由工具名和硬边界决定,不会因 task 标 external 就把所有子工具误判成磁盘写入。子工具是否允许写盘,仍由各自工具名和权限边界决定。

▶️ 交互演示:父-子上下文隔离

点「启动 task」看子 Agent 在全新历史里探索,父只收到 finalText。注意子工具集没有 task(禁止递归),且子写文件副作用留在同一 workspace。
父工具:7个 · 子工具:6个(无 task)

🧑 父 Agent 历史 只收 finalText

🧒 子 Agent 历史 全新,父看不到中间

🔑 一句话总结

task = 一个受控的上下文切分点:委派独立工作出去,保留最终结论与所有既有运行边界

子 Agent 可以有很长的探索轨迹,但父上下文只留下被委派的动作和可用于后续推理的结论。历史隔离 ≠ 权限隔离,运行边界共享。

🚀 运行第 6 章

前置条件和前几章一样。下面几步,每步都给出预期结果。
第 0–1 步 · 环境与离线测试
Set-Location 'F:\笔记\Agent实操\code'
npm ci
npm run test:ch06
 Test Files  16 passed (16)
      Tests  118 passed (118)
第 2–4 步 · 观察一次委派、观察审批、统一入口
npm run ch06 -- --prompt "调用 task 独立检查本项目使用的测试框架,只返回有文件证据的结论"
# 父子日志交错;Hook 共享实例,子 Agent 的 run() 也打 UserPromptSubmit/Stop

npm run ch06 -- --prompt "调用 task 让子 Agent 把一句话写入 sub-demo.txt"
# task 自己 allow;子 Agent 的写入照样弹审批框

npm run agent-tutorial -- run --chapter 6 --prompt "调用 task 总结 chapters/ch06/src/core 目录职责,再由父 Agent 给出结论"
常见报错排查表
现象原因 / 处理
Error [subagent_turn_limit]子 Agent 跑满 30 轮还没给最终文本。把委派任务拆小。不要调高上限——构造时会拒绝 >30
子 Agent 越界写入没弹审批框预期行为。硬边界在规则之前,越界直接 deny,压根到不了审批
子 Agent 调 task 报 subagent_configuration_error运行时防递归第三层,正常。子工具集本就不含 task
启动报 maxTurns must be at most 30构造期校验,配置错误在组合根就炸

🧪 验证与实验

npm run test:ch06 执行 16 个测试文件、118 个测试。四个建议动手的小实验:
四个建议动手做的小实验
实验预期学到什么
一·maxTurns 调到 31
在 new SubagentTool 里加 maxTurns: 31
启动就抛 maxTurns must be at most 30,模型一次没被调用上限是构造期校验,配置错误应在组合根就炸
二·让子工具集包含 task返回 subagent_configuration_error,模型请求只有父那一次运行时防递归第三道防线,子 Agent 压根没被创建
三·验证子 Agent 看不到父 history子 Agent 只看到 description 那一句,必须自己再读文件隔离是真的;description 必须自包含
四·观察子 Agent 的 todo_write 有没有提醒没有。子 Agent 没挂 toolRoundObserver刻意的不对称:有工具但没机制

📝 本章小结

三句话版本、一定要记住的八条、以及本章还没做什么
三句话版本
  1. task 把一段自包含探索搬到一个全新历史里跑,父 Agent 只拿回一句结论。
  2. 隔离的只有消息历史和工具注册表;Hook、权限、workspace、identity、进程全部共享。
  3. 委派不创造新路径:子 Agent 该弹的审批照样弹,该拦的越界照样拦。
一定要记住的八条
#结论出现在哪一节
1「隔离」= 上下文隔离,不是沙箱、不是提权、不是降权这章解决什么问题
2子 Agent 看不到父 history,所以 description 必须自包含
3SubagentTool 不是第二套 Loop,它复用同一个 AgentRunner
4父 history 只多三条:assistant(task)、tool(结论)、assistant(最终回答)
5代价是子 Agent 的推理过程无法审计——所以要求 evidence-based 结论
6禁止递归防三层:提示、工厂、运行时;第三层保证「模型零调用」
7跑满轮数返回结构化错误,绝不把最后一个工具输出冒充结论
8子 Agent 有 todo_write 但没有 toolRoundObserver——刻意的不对称
本章代码边界

P06 只加了「一层同步委派」,没加并行、没加持久化、没加沙箱。并行(第 13 章后台任务)、多 Agent 认领(第 17、18 章)、Inbox 异步协作(第 15 章)都在后面。

检查你是否真的读懂了(不看文章回答)
  1. 子 Agent 在 workspace 里写的文件,task 返回后还在吗?为什么?
  2. 父 Agent 调用一次 task,父 history 里多了几条消息?分别是什么?
  3. 子 Agent 的 write_file 会弹审批框吗?审批框上能看出这是子 Agent 吗?
  4. 给子 Agent 配「永远批准」的审批器,它能写出 workspace 吗?
  5. 子模型自己生成了 task(...) 调用,它会收到什么?
  6. 子 Agent 跑满 30 轮,父 Agent 收到的是第 30 轮的工具输出,还是别的东西?
  7. 子 Agent 的工具列表里有 todo_write。它会收到「计划陈旧」提醒吗?

QA 测验