Appearance
第 16 章 会协作:子 Agent 与多 Agent
本章要解决的问题
第 14 章已经提供可恢复、可 fork 的会话,第 15 章又说明了哪些知识值得跨会话复用。但在一个复杂任务中,多项互不相关的调查仍可能挤在同一个上下文里,并且只能依次推进。
本章讨论什么时候值得拆分、子会话带多少历史、消息怎样传递,以及怎样限制深度、预算、取消和写入冲突。
16.1 多 Agent 首先解决上下文隔离与并行
第 13 章已经说明上下文是有限资源。单 Agent 处理大任务时,窗口会堆满搜过的文件、失败尝试和无关输出。真正有用的信息占比降低后,模型更容易忘记任务目标、重复读取文件或在细节里打转。
多 Agent 的第一层价值是把上下文拆开:
并行是第二个稳定收益,例如让三个彼此独立的调查同时运行。先问“上下文是否过载、工作是否真的独立”,再考虑多 Agent。如果单 Agent 已经足够,增加 Agent 只会增加沟通成本和失败点。
还要注意:子 Agent 通常只隔离模型上下文,文件系统、凭证和进程环境可能仍然共享。多 Agent 不是安全边界。
多 Agent 什么时候真的会更准确?
多 Agent 能不能提升正确性,看协作过程是否带来了单个 Agent 生成答案时没有的新信息:
| 协作方式 | 新信息 | 通常的价值 |
|---|---|---|
| 同一模型重新阅读同一段文本、自我辩论 | 无 | 可能只是多花 token,甚至把正确答案改错 |
| 子 Agent 去不同模块检索并返回原始代码位置 | 有:新的代码证据 | 扩大覆盖面,但结论仍需核对原文 |
| 审查 Agent 运行编译器、测试、静态分析 | 有:程序给出的明确结果 | 能证明或推翻实现结论 |
| 审查 Agent 渲染页面并查看截图 | 有:真实视觉结果 | 能发现只看源码看不出的布局问题 |
| 换一个模型再表达观点 | 只有独立样本,没有外部证据 | 可降低部分相关盲点,不能把“两个模型同意”当事实 |
因此,审查 Agent 的任务书应要求它获取独立证据,而不是只评价前一个 Agent 的摘要。不同 Agent 可以共享目标和验收标准,但高风险结论要回到测试、截图、源码位置或多个相互独立的信息源。拆开上下文解决“装不下”;是否带回新证据,才决定“为什么可能更准”。
16.2 怎样选择上下文共享方式和负责人?
设计多 Agent 协作时,要分别回答两个互不替代的问题:
问题一:上下文共享还是隔离?
| 共享 | 隔离 | |
|---|---|---|
| 形态 | 移交(handoff):同一会话换个角色 | 派生(spawn):创建独立会话;初始历史可选不带、最近 N 轮或全部带入 |
| 优点 | 上下文零成本保留 | 后续上下文彼此独立,可并行;不带历史时窗口最干净 |
| 风险 | 历史越滚越长,角色指令被稀释 | 不带历史时任务书必须自包含;带全部历史时仍会继承噪声 |
问题二:Agent 对等协作,还是由主 Agent 统一分配任务?
- 对等:Agent 间直接通信(适合两个专长互补的伙伴)
- 主 Agent 统一分配:主 Agent 只派任务、收结论,不读取每个子任务的全部过程。例如翻译一本书时,主 Agent 只保存任务清单和文件索引,完整译文写入文件,因此上下文不会持续增长
- 没有统一负责人:Agent 之间自行协调,本书不展开
codex、grok 和 dsh 都内置了创建独立子会话的能力,但初始历史并不都为空——codex v2 默认带入全部历史,dsh 随附配置中的 subagent_fork 会复制已完成前缀。pi 核心没有子 Agent 协议,示例扩展通过启动独立 pi 进程实现隔离上下文。对于大内容,本书推荐写入文件或对象存储,回给主 Agent 的消息只放结论和引用;这是一项协议选择,不是说四个实现都强制这样做。
16.3 怎样创建、等待、继续和停止子 Agent?
多 Agent 运行期只有三类需求——派出去、等回来、必要时打断,其余动作都由这三类衍生。codex 的 collaboration 工具族(v2)把这些动作做成了模型可调用的工具,可以直接借鉴它的命名:
text
spawn_agent 派生:任务书 → 返回 agent id
send_message 通信:给指定 agent 发消息(不触发新轮)
followup_task 追加:给空闲的 agent 派后续任务
wait_agent 等待:任一活跃 agent 完成或有新消息时返回
list_agents 视察:列出当前活跃的 agent 树
interrupt_agent 打断:中断某个 agent 的当前轮
(close_agent 收尾:关闭 agent 及其后代 —— v1 词汇)grok 对应的则是一个 task 工具,外加负责子任务排队、等待、取消、结果投递和用量结算的 coordinator。
dsh 的随附配置把两种后端绑定成 subagent / subagent_fork:前者创建可继续的后台子 Agent,后者继承父会话已完成前缀并一次性返回。全局 control 工具提供 send_message、interrupt_agent 和 list_agents;report 只装在可继续的子 Agent 内,用于主动把结果送回父 Agent。
pi 刻意不内置 spawn、wait、message 或 Agent 树。仓库提供的示例扩展会把子任务交给新的 pi 进程,未单独指定时继承模型和 thinking 配置,但上下文相互隔离。要把示例升级为生产协议,仍需补父子关系、消息持久化、总预算、取消确认和共享文件冲突处理。
grok-build:前台超时不等于杀掉子 Agent
grok-build 的 coordinator 把子任务分成 queued / initializing / running / completed,终态再区分 completed、failed 和 cancelled。内部还分别保存等待者、待投递完成通知和最近完成记录。这样“任务正在初始化”不会被查询接口误报成 not found,“用户取消”也不会被混成执行失败。
一次 task 调用有三种等待方式:
text
前台等待 在本轮等结果,但只等到 foreground await budget
一直等待完成 不受前台 handoff 截止时间影响
显式后台 立即返回 task id,完成后再通知最容易写错的是第一种:超过前台等待预算后,coordinator 返回 backgrounded + taskId,子 Agent 继续运行。排队中的任务也一样,只把调用者从“等结果”切成“拿句柄”,不会为了按时返回而把任务从队列删除。调用者已经断开时,同样自动转后台,避免一个被丢弃的 response channel 杀掉仍有价值的工作。
完成结果可能有三个接收者:原始前台调用、正在阻塞查询的等待者、异步完成提醒。coordinator 保存一份 delivery disposition,记录结果是否已经交给前台或等待者;只有没有成功交付、任务并非显式 kill,且配置要求展示时,才再唤醒父 Agent。这个判断防止同一个结果既作为工具返回出现一次,又作为“后台任务完成”提醒出现第二次。
结果不仅是一段文字,还带 success/cancelled/error、子会话 ID、turn/tool-call 数、耗时、token 用量、worktree 路径和“用量是否不完整”。大输出可以单独持久化,内存中的完成记录只保留引用;待提醒队列和完成缓存都有上限,防止父会话长期不读取时无限增长。
取消也按所有权分层:可以取消一个子任务、当前 parent prompt 创建的任务、整个 parent session 的非 workflow 任务,或一个 workflow run。显式 kill 后即使任务稍晚完成也不再弹完成提醒;删除父会话时先关闭新 spawn,取消并等待子任务排空,另有最终截止时间防止永远卡住。这里体现了一个通用原则:取消请求、子进程真正结束、结果是否还应投递,是三个状态,不是一件事。
用量结算同样不能靠“结果到了”推断完成。若子 Agent 的 token 账单未能合入父 prompt,会留下 sticky incomplete 标记;冻结或取消时必须先快照账单,再显式清除标记。否则后台任务跨轮完成后,父任务的成本上限会被低估。
16.4 怎样限制递归并保留父子关系?
多 Agent 会增加三个单 Agent 没有的问题,参考实现分别采取了限制措施:
① 无限递归(agent 生成 agent 生成 agent……):
- codex V2 会记录父子关系和层级,并限制同一会话树的并发线程数;截至审阅时,它的 V2 配置与 spawn 路径里没有嵌套深度上限。因此“有层级字段”不等于“超深时会拒绝”,递归边界仍主要依赖协作模式指令和任务书
- dsh:
maxDepth默认 3,运行时拒绝超深委派;父会话和层级(delegation_depth)记在每条会话上
② 上下文汇入失控(所有子 agent 的产出都涌回主窗口,隔离白做):
- 解法是把完整产物写入文件,只把摘要和文件路径发回主 Agent。第 14 章把完整会话写入日志,第 13 章把超长输出写入文件,目的相同:不要把大内容都塞进主 Agent 的上下文
③ 子 Agent 已无人使用,却仍在占用资源:
- codex 旧版提供
close_agent,当前 V2 的模型工具只提供打断当前轮,没有等价的显式关闭工具;dsh 的 one-shot 模式执行后退出,continuable 模式则通过 control 工具继续或打断当前轮。无论哪种实现,运行时仍要负责清理不再被引用的会话与进程
pi 核心不承担上述三项治理;外置子进程方案必须由扩展保存进程 id、父任务、预算和最终状态。核心更小,不意味着递归、孤儿进程或写入冲突自然消失。
16.5 dsh 怎样继承父会话并调用其他 Code Agent?
dsh 怎样复用父会话中已经完成的内容?
16.2~16.4 先拆开了上下文继承、生命周期动作和父子关系。dsh 把这三件事连在一起,适合作为一个完整例子。它的 subagent_fork 使用会话 fork:复制父会话中已经完成的事件前缀,再从分叉点开始子任务。它适合“基于刚才的调查,现在专门修改 X”这类需要继承既有结论的任务。
实现依托第 14 章的事件日志:fork(source, boundary) 从安全边界复制前缀。当前代码会截取前缀、逐事件生成快照并保存子日志,时间和空间成本都是 O(前缀长度),不是 Git 那种 O(1) 指针分支。
dsh 怎样把 Codex / Claude Code 当子 Agent 驱动?
dsh 的 subagent 后端不只支持自身:已有两套后端实现并测试了把 OpenAI Codex 和 Claude Code 作为子进程驱动。
它们在 dsh 的几组预置配置中(见第 17 章)有配置项,但默认不启用。用户需要明确打开,并准备对应的 CLI/SDK 和认证。启用后,主 Agent 可以按任务选择不同引擎,但这不是默认会话一定具备的能力。
16.6 部署前最容易出哪些问题,怎样防止?
到 16.5 为止,子 Agent 已经能创建、等待、继续并保留父子关系。这只证明生命周期能跑通,不代表协作就会正确。先列出部署时最容易出现的故障;后面 16.7~16.9 再分别从任务书、消息协议和资源所有权解决它们。
下面是常见问题与四个实现暴露出的边界:
- 过早宣布完成:子 agent 说"我做完了"但没验证 → 主 agent 应要求结论附带证据(测试输出、diff)
- 结论过载:子 agent 返回 5000 token 的"总结" → 按任务设置返回字段和篇幅上限;短调查可以要求 200~500 token,代码任务还必须列出文件路径、验证证据和阻塞项,细节写入文件
- 消息时序错乱:主 agent 还在等 A,B 的消息先到 → 任一个完成或发来消息就让 wait 返回,别假设顺序
- 结果重复投递:前台调用已经收到结果,后台完成提醒又投递一次 → 保存 delivery disposition,同一 terminal result 只结算一次
- 前台等待超时误杀任务:UI 不想继续等,却把子任务也取消 → 把“交还 taskId”和“取消执行”拆开;grok-build 的前台预算到期会自动转后台
- 同文件丢失更新:两个子 Agent 基于同一旧版本改同一文件 → 优先拆成不相交写集或独立 worktree;共享工作区必须用第 7 章的版本 CAS(compare-and-swap,写入前确认版本仍与读取时一致)/锚点,不能让后写覆盖先写
- 跨文件行为冲突:A 改接口,B 在另一个文件继续调用旧接口,文件层面完全不冲突 → 任务书声明读/写集和依赖,合并后跑跨模块类型检查、测试与全局一致性检查
- 错误继续传给后续任务:前一个 Agent 的摘要写错,后一个 Agent 把它当事实继续放大 → 每个关键结论附
evidenceRefs(源码位置、命令输出、截图或产物 revision);独立审查 Agent 从原始证据重新判断,不要把“多个 Agent 都沿用了同一句话”当成交叉验证
16.7 怎样写一份子 Agent 能直接执行的任务书?
这里把发给子 Agent 的 message 称为“任务说明”。16.6 的许多问题都来自任务说明不清楚。
codex 源码中同时保留 V2 与 V1 两版工具描述。V2 要求子任务具体、有边界,并且能与主任务中的有效工作并行;授权规则由协作模式另行注入。旧版 V1 还把任务说明和等待规则写得更细。下面会明确区分两版,不要把旧版文字说成当前 V2 的统一行为。
① 是否需要用户明确允许创建子 Agent?
旧版 V1 的原始英文要求
"Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work. Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn."
这段严格限制仍能在旧版 V1 工具描述中看到。当前 V2 没把它写死在 spawn 的基础描述里,而是交给可切换的协作模式:默认的 ExplicitRequestOnly 模式会用 developer 指令要求用户或项目规则明确授权;主动模式则允许模型自行委派,自定义模式还可以替换这段策略。
这说明“是否必须得到用户明确授权”是产品策略,不是 spawn 的技术定律。自建 Agent 要明确选择,并把规则放进模型实际能看到、能随模式更新的指令中。
② 下一步是否依赖这个子任务?(旧版 V1 指引)
codex 旧版 V1 的工具说明要求主 Agent 先判断哪些工作会阻塞下一步,哪些工作可以独立并行。
更准确的规则是:下一步依赖该结果,它就在关键路径上。主 Agent 可以自己做,也可以委派后明确等待,但不能把它当成不阻塞的并行任务。只有不影响当前下一步、并且能独立推进的工作,才适合在后台委派。先决定主 Agent 现在做什么,再创建子 Agent,可以避免主 Agent 无事可做地等待。
③ 任务书必须说清哪六件事?(旧版 V1 指引)
codex 原文的"Designing delegated subtasks"一节,几乎每条都在防一个具体问题:
| 要求(原文大意) | 防的问题 |
|---|---|
| 任务必须具体、定义良好、自包含 | 子 Agent 反问"你说的是哪个文件?"(来回空转) |
| 任务必须实质推进主任务 | 创建与下一步无关的研究任务 |
| 主任务与子任务不重复劳动 | 两边各改一遍同一个文件再冲突 |
| 收窄到"你下一步具体需要什么产出" | 子 Agent 返回大量无法直接使用的背景材料 |
| 编码任务优先委派"有界的代码修改"而非只读探索 | 探索型子任务产出不可验证 |
| 多个编码任务并行时,不要修改同一组文件 | 两个子 Agent 同时修改同一文件,后写覆盖先写 |
编码任务还应要求子模型直接在它的 fork 工作区里改文件,并在最终回答中列出修改过的文件路径。这样主 Agent 只需读取文件清单和必要结论,不用接收完整修改过程。
④ 子 Agent 运行时,主 Agent 应该做什么?(旧版 V1 指引)
"Call wait_agent very sparingly. Only call wait_agent when you need the result immediately for the next critical-path step and you are blocked until it returns." "While the subagent is running in the background, do meaningful non-overlapping work immediately." "Do not redo delegated subagent tasks yourself."
子 Agent 运行时,主 Agent 应立即处理不重叠的工作,不要反复调用 wait 查询进度。子 Agent 交回修改后应快速审查并整合,不必把同一任务重新做一遍。
⑤ 怎样用 fork_turns 明确传多少历史?
codex v2 的 spawn_agent 用 fork_turns 明确上下文传递量:"none" 不带历史,正整数带最近 N 轮,"all" 带全部对话历史;省略时默认 all。
工具描述提醒 none 可能让子 Agent 缺少必要背景。另一面是,带入越多历史,越可能把无关内容一起传过去。dsh 的 fork 会复制父会话已经完成的前缀,它们都在回答同一个问题:应根据子任务选择传多少上下文。要得到干净窗口,必须显式使用 none 并写完整任务说明。
怎样补全最小 spawn_subtask 的工具描述?
实现最小 spawn_subtask 时,至少把上面的任务边界、上下文传递量和交付格式写进工具描述。模型实际看得到这些规则,才更容易正确创建子任务。与增加新的协调代码相比,先把工具描述写清楚通常更简单。
16.8 消息怎样送达,什么时候唤醒 Agent?
任务书解决"派出去说什么";真正的协议难点在派出去之后:消息怎么送达、什么时候触发新回合、最终答案怎么回来。这三个问题答不好,多 Agent 系统会陷入"消息错乱"或"空转等待"。
发一条消息会不会立即开始新回合?
codex 的两个工具有一个关键行为差异(直接引自工具描述):
text
send_message "The message will be delivered promptly.
Does NOT trigger a new turn." ← 异步投递:进待处理队列,不打断
followup_task "...trigger a turn IF it is idle" ← 显式触发:对方空闲才开新回合想象子 Agent 正在修改文件,主 Agent 发来“顺便看看 B 模块”。如果消息立即触发新回合,当前工作就会被打断,第 4 章 4.5.2 的中断问题会再次出现。
codex 因此把两种行为分开:普通消息先进入待处理队列,接收方在自然间隙处理;确实需要转向时,再显式使用 followup_task 或 interrupt_agent。dsh 的 continuable 模式则配合 control 工具继续任务。
跨进程消息怎样避免重复和丢失?
如果自己实现跨进程或可恢复协作,每条消息至少要说明:
messageId:重试时保持不变,接收端据此去重。taskId,以及可选的goalId/goalRevision:明确消息属于哪个任务,并拒绝旧目标留下的迟到指令。goal 是一组相关任务的父目标,goalRevision 随目标改版递增。from/to和消息类型:说明发送方、接收方以及这是任务、状态、结果、控制还是确认消息。- 通道内递增的
seq:用于排序和发现缺失消息。 - 可选的
replyTo、deadline和artifactRefs:关联前一条消息、限制时效,并让产物只传引用而不传完整内容。
不要承诺“消息绝不会重复,并且只处理一次”;跨进程系统很难可靠做到。更实际的做法是:允许消息重复投递,接收端根据 messageId 去重。同一条消息即使重复到达,也只能产生一次效果。
- 发送方先把同一
messageId写入持久化发送队列,超时可重投但不能换 ID。 - 接收方先以
messageId写入持久化接收队列/去重表,再确认接收;重复消息返回同一 ack,不重复启动任务。 goalRevision已过期、deadline 已过或目标已经结束时,消息进入明确的 rejected/expired 结果,不能静默执行。- 业务处理失败与投递失败分开:前者返回结构化 result,后者才由传输层重投。
同时把控制消息与大文件、产物分开:
text
控制消息:task/status/cancel/ack/terminal result 小、可排序、需要可靠投递
大文件与产物:代码、diff、日志、截图、测试证据 落文件/对象存储,消息只传 ref+revision/hash大产物直接塞消息会让待处理队列、父 Agent 上下文和重试成本一起膨胀。artifactRefs 还必须继承原资源的 ACL;拿到路径不等于获得读取权限。上述持久化收发消息队列是生产系统的通用设计空间,不是声称四个参考 Agent 都已实现这套机制。
多个子任务并行时应该等哪一个?
text
codex v2 wait_agent: "Wait for a mailbox update from ANY live agent"这里的 mailbox 指 Agent 的待处理消息队列;保留英文是因为它来自源码工具描述。
当多个结果彼此独立,并且任何一个先完成都能推进工作时,可以等待任一子 Agent 回信。主 Agent 先处理最早到达的结果,其他任务继续运行;整合时再按任务编号归位。这也是 codex wait_agent 的行为,不需要反复轮询。
如果下一步明确依赖某个特定子任务,就应该等待那个任务或由主 Agent 自己完成,不能机械地使用 wait-any。
怎样避免同名子 Agent 收错消息?
子 Agent 一多,短名称可能重复。codex v2 的每次 spawn 都携带 task_name,系统再生成包含父子层级的完整名称,例如 /root/task1/task_3。同一棵任务树内可以用短名,跨树引用必须用完整名称。
因此 /root/task1/task_3 和 /root/task2/task_3 是两个不同的 Agent。dsh 则通过 parent_session 和 delegation_depth 保存父子关系。
子 Agent 完成后怎样交回答案?
codex 的工具描述说明,子 Agent 完成后会把最终答案交给父 Agent。答案进入父 Agent 的待处理队列,由父 Agent 在下一次处理消息时读取。
任务说明应要求最终答案只包含完成下一步所需的结论、修改文件和验证证据,不要返回完整过程。
dsh 提供两种运行模式:one-shot 做完即结束,适合独立子任务;continuable 做完后仍存活,可以继续对话,并由 report/list_agents 管理。
codex 与 dsh 当前分别支持哪些消息行为?
| 协议问题 | codex | dsh |
|---|---|---|
| 异步消息 | send_message(不触发回合) | send_message(排到可继续子 Agent 的下一轮) |
| 显式触发 | followup_task(空闲才触发)/ interrupt_agent | send_message 启动下一轮 / interrupt_agent 只打断当前轮 |
| 等待 | wait_agent:任一待处理消息到达即唤醒 | 无同名 wait;后台结束由运行时主动通知 |
| 寻址 | 规范名 /root/task1/task_3(跨树必须规范名) | 父会话与层级 parent_session/depth |
| 完成结果 | 完成时答案进入父 Agent 的待处理队列 | one-shot 做完退出 / continuable 继续存活 |
| 防失控 | 父子关系与层级可追踪;当前 V2 限并发但不设 max depth | maxDepth=3 + delegation_depth |
grok-build 没有使用 codex 这组同名消息工具,但它补上了另一半协议:任务状态查询、阻塞等待超时、前台转后台、分层取消、完成去重和用量结算。对自建系统来说,两部分都需要:既要定义 Agent 之间“怎么发消息”,也要定义任务“最终由谁、在什么时候结算”。
16.9 除了 spawn API,还必须管理什么?
即使所有子 Agent 都运行在一台机器上,仍要处理消息重复、状态变化和资源归属。至少要明确四类负责人:
text
任务所有权 parentAgentId / goalId / goalRevision / taskId
资源所有权 workspaceId / jobId / processGroup / approvalId
预算所有权 父级总额 → 子 Agent token/时间/并发配额
结果由谁负责 产物路径 / 证据 / 审查结论 / 合并状态每个子任务还要有明确状态,不要只保存“正在运行/已完成”两个布尔值:
text
queued → running → needs_input
→ completed / failed / cancelled / timed_out / orphanedlastActivity 或执行权到期只说明任务可能失联,不能伪造为失败或完成。批量派发前还要选定收集规则:必须全部成功(all)、任一有效结果即可(any),还是达到约定数量(quorum)。第一个满足规则的结果只能结算一次;之后到达的结果标为迟到,是否继续采用由父任务显式决定。部分失败时必须列出未覆盖的范围,不能用已有结果冒充完整结论。
共享文件系统时再把目录分成四类,并给远程子 Agent 传可下载的产物引用,不要只传本机绝对路径:
| 区域 | 默认规则 |
|---|---|
| 子 Agent 临时目录 | 仅该任务可写,任务结束后按留存策略清理 |
| 用户可见产物目录 | 允许受控写入,用版本检查处理并发修改 |
| 外部挂载目录 | 默认只读,沿用来源系统的访问控制 |
| 内置 Skill、模板和检查程序 | 只读,升级走独立发布流程 |
取消父任务时,要分别处理每种子资源。 系统需要决定:是否取消排队子任务、怎样通知运行中的子 Agent、是否终止前台进程、是否保留后台 dev server,以及是否保留已经完成的产物。每一步重复执行都应得到相同结果;未能回收的资源要明确标记为 orphaned(失去负责人)。
父任务必须限制所有子任务的总用量。 每个子 Agent 分别有限额,不代表多个子 Agent 合起来仍不会超支。父级还要限制一次可创建的子 Agent 数量、总 token、总时间、并发工具数和审查 Agent 成本;子级未用完的额度是否回收也要明确。
第 19 章会把模型服务重试、Goal 轮次、审查 Agent 和子 Agent 的用量计入同一个总预算。
完成不是只返回一段文字。 子 Agent 的交付应包含明确状态(complete/blocked/cancelled/budget-limited)、产物路径、验证证据和仍在运行的资源。
关键断言应绑定 evidenceRef + revision/hash + producedBy,否则文件更新后,旧摘要仍可能被误认为当前证据。多个 worktree 合并后还要再跑一次集成验证:每个分支各自测试通过,不等于合并结果通过。
自建起步:怎样给第 4 章循环增加最小 spawn?
最小实现可以理解为:子 Agent 使用独立的 messages 数组,运行自己的 loop。在第 4 章的循环上增加一个 spawn_subtask 工具,再明确下面三项行为:
① 用全新上下文运行同一个循环。
执行函数内部用全新的 messages 数组和同一套工具运行 loop,最后只返回 assistant 的最终消息。这样子任务的中间过程不会进入主 Agent 上下文。
② 把使用规则写进工具描述。
spawn_subtask 的 description 要说明:它创建隔离的子 Agent,中间过程不进入主上下文;适合独立搜索、有明确边界的代码修改和批量操作。还要提醒模型,任务说明必须自包含,并明确返回字段和篇幅上限。
③ 把任务说明当作接口。
给子 Agent 的 message 包含两部分:一份不依赖额外对话也能读懂的任务说明,以及一份返回要求,例如“列出结论、修改路径、验证证据和阻塞项,背景材料写入指定文件”。16.7 的规则应同时写进工具描述。
可以用这个任务验证实现:”分别调查仓库里 HTTP 层和存储层的错误处理方式,对比后给出建议”。模型应创建两个互不干扰的子任务,主上下文只接收两份结论和证据。
也要清楚这个版本做不到什么:子任务不能并行、不能中途取消,没有 token 与时间预算控制,子任务也无法向主 Agent 反向通信。无并行与无取消对应 16.8 的等待唤醒与打断,无预算对应 16.9 的预算所有权;返回篇幅没有约束,16.6 的结论过载同样会发生。最小实现只验证了上下文隔离,这些边界要在扩展时逐项补上。
【对比小结】
- 多 Agent 最稳定的收益是拆开上下文 + 独立任务并行;只有引入测试、截图、源码位置或独立来源等新信息,才有提高正确性的依据
- 两个独立决策:子会话怎样继承初始历史、后续上下文是否独立;Agent 对等协作还是由主 Agent 分配任务。前三家内置独立子会话,pi 用扩展示例启动独立进程;大内容宜写文件,只返回结论和路径
- 常用工具动作:spawn/send/wait/list/interrupt;有些协议还提供 close。可靠消息还要稳定 message ID、接收端去重、分开控制消息与大文件/产物,并为证据保留 revision
- 前台等待到期不应自动杀死子任务;完成结果要记录是否已交给前台、等待者或异步提醒,避免重复投递和重复结算。grok-build 还把取消范围、结果缓存与用量未结算状态显式化
- 不能只限制嵌套深度,还要限制一次派出的子 Agent 数量和总预算,记录资源由谁负责,把取消传给子任务,用 CAS 防止同一文件被旧版本覆盖,检查跨文件依赖,清理失去父任务的进程,并在合并后验证
- dsh 的 fork 以 O(n) 复制成本继承完整前缀;跨产品委派则允许不同子任务使用不同 Code Agent
多 Agent 解决了“工作怎样拆分”,但每个 Agent 仍只能使用其运行程序已经提供的能力。下一章讨论怎样通过 MCP、Skill 与插件接入新工具和操作说明,而不是把所有能力都写死在核心代码中。