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 行)——同进程新会话 spawnsubagent-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机制:三种委派模式
- spawn(新会话):子 agent 有全新 session——header 带
parentSession(06 页 SessionHeader 的 lineage 字段)。子会话的日志独立,但血缘可追溯。 - fork(同会话):fork 当前会话到一个边界——06 页的 fork 约束在此生效(边界必须是连续 seq、不得落在 open turn 内)。子 agent 从 fork 点继续,父历史是 seed。
- delegation(跨产品):codex/claude-code provider 把轮次委派给另一个产品的进程——06 页提过的「subagent 提供方在同一个接口之后千差万别」的落点。结果作为消息回到父会话。
无论哪种模式,子 agent 跑的是同一套 05-14 页的循环——subagent 不复制循环,只管理「谁创建谁、血缘如何、结果如何回收」。
§4关键代码
171export class SubagentRuntime extends Service {
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 的「子日志独立」形成对比。