Volume 2 · Chapter 22

子代理:在同一个 harness 里嵌套 agent

子代理是 harness 最大的能力域之一(packages/subagent/subagent 18 文件 / 4684 行)。它把「一个 agent 把工作委派给另一个 agent」做成一个 seam:同进程 spawn、fork 同一会话、甚至把轮次委派给别的产品(Claude Code、Codex)——都在同一个 Service Definition 之后。

(卷一连接点) 05 页 ctx.agents 创建 · 07 页 inbox · 11 页 tool 结果 → 本域:subagent / spawn-in-process / fork-in-process / tool-subagent* / subagent-codex / subagent-claude-code → (回心脏) 子代理同样跑 05-14 页的循环,只是会话 header 带 parentSession

示例本次示例:模型把「读代码」委派给子代理

示例轨迹 22-1 · spawn 模式的完整生命周期
# 主会话里模型调用 subagent 工具:
# tool-subagent 执行(inject=['agents','tools','subagents'...])
# → SubagentRuntime(subagent/src/index.ts:171)发起委派
# → spawn-in-process provider(inject=['subagents']):
#    ① sessions.create(子会话, meta:{ parentSession: 主会话id, delegationDepth: 1 })
#    ② AgentFactory 创建子 agent(同进程,跑 05-14 页同一套循环)
#    ③ 子会话日志独立:~/.dsh/sessions/<子sid>.jsonl
# 子 agent 完成 → 结果消息回主会话 → 主会话日志只多一条「委派结果」
#
# 真实 fixture 里的血缘(06 页示例轨迹 06-1):
{ "type": "session", "id": "eeeeeeee-1111-4222-8333-444444444444",
  "parentSession": "{{SID}}", "delegationDepth": 1 }   ← 子会话头
来源:22 页 §3 + 06 页 SessionHeader 的 parentSession/delegationDepth + 真实 fixture
示例轨迹 22-2 · fork 与 spawn 的取舍
# fork:模型想「基于当前上下文另开一路探索」
# → fork-in-process:fork 边界必须连续 seq、不在 open turn(06 页约束)
# → 子会话 = 父历史 seed + 自己的新事件;父历史是 seed 不是复制
# spawn:全新上下文(如「去查这个库的文档」)→ 全新会话更干净
# 判断标准(24 页同款):结果要不要留在当前日志里
来源:22 页 §3 + 06 页 fork 边界约束

§1挂载条目

  • subagent → @deepseek-ai/dsh-subagent——运行时核心(Definition + 注册)
  • subagent-spawn-in-process → dsh-subagent-spawn-in-process(94 行)——同进程新会话 spawn
  • subagent-fork-in-process → dsh-subagent-fork-in-process(124 行)——fork 当前会话
  • tool-subagent-control / tool-subagent / tool-subagent-fork / tool-subagent-report / tool-subagent-list-agents——模型可见的委派工具家族
  • 可选挂载(不在 base bundle):subagent-codex(1348 行)、subagent-claude-code(936 行)——把轮次委派给其他产品的 provider

§2包文件地图

包规模角色
packages/subagent/subagent/18 文件 / 4684 行Service Definition + 编排:SubagentRuntime extends Service(src/index.ts:171)——委派生命周期、描述符、回收
packages/subagent/subagent-spawn-in-process/2 文件 / 94 行Provider 1:inject = ['subagents'](src/index.ts:22)——新开一个子会话 + 子 agent
packages/subagent/subagent-fork-in-process/2 文件 / 124 行Provider 2:inject = ['subagents'](src/index.ts:28)——fork 当前会话(06 页 fork 边界约束的消费者)
packages/subagent/tool-subagent/2 文件 / 506 行Consumer:模型发起委派的工具
packages/subagent/tool-subagent-report/2 文件 / 172 行委派结果回写

§3机制:三种委派模式

  1. spawn(新会话):子 agent 有全新 session——header 带 parentSession(06 页 SessionHeader 的 lineage 字段)。子会话的日志独立,但血缘可追溯。
  2. fork(同会话):fork 当前会话到一个边界——06 页的 fork 约束在此生效(边界必须是连续 seq、不得落在 open turn 内)。子 agent 从 fork 点继续,父历史是 seed。
  3. delegation(跨产品):codex/claude-code provider 把轮次委派给另一个产品的进程——06 页提过的「subagent 提供方在同一个接口之后千差万别」的落点。结果作为消息回到父会话。

无论哪种模式,子 agent 跑的是同一套 05-14 页的循环——subagent 不复制循环,只管理「谁创建谁、血缘如何、结果如何回收」。

§4关键代码

packages/subagent/subagent/src/index.ts运行时本体171
171export class SubagentRuntime extends Service {
packages/subagent/subagent-spawn-in-process/src/index.tsspawn 提供方的依赖面22
22export const inject = ['subagents']
171

又是 seam 模板——但注意 subagent 的规模(4684 行)远超其他 seam:它不只是「委派」,还管描述符、回收、递归预算(delegationDepth,06 页 header 字段)等编排。

22

provider 只依赖 subagents 服务——一个 94 行的 provider 就能实现一种委派模式。这就是 seam 的收益:新增委派模式 = 94 行。

§5易错点

递归预算必须持久(06 页 delegationDepth 的 JSDoc):只用运行期深度,一个 resume 的子代理会重置回顶层——递归预算必须进 header 才能在重启后存活。

跨产品委派的会话日志:被委派的产品有自己的会话语义,回收的是「结果消息」——父会话日志里只出现结果,不出现中间过程。这与 spawn/fork 的「子日志独立」形成对比。