Skip to content

3.4 子代理 Subagent:委派与续跑

本章概览:Subagent 能力缝(capability seam)让一个代理把工作委派给子代理。本章覆盖 ctx.subagents 服务 API、one-shot 与 continuable 两种委派模式、持久化子代理描述符、委派深度、进程内与进程外提供方,以及续跑子代理的 Activation 编排。

概述 / 定位

Subagent 是 DSH 的一个可选能力缝,不属于 agent 循环(agent loop)本身;与 bash 只允许一个执行器不同,同一上下文可以共存多个命名提供方(provider),统一注册在 ctx.subagents 之下:调用方只面对一个服务 API,提供方决定子代理在本进程、另一进程还是未来某种传输上运行。能力缝三角色齐全——Service Definition 是 dsh-subagent,Service Providers 是 spawn/fork/acp 等兄弟包,模型侧 Consumers 是 dsh-tool-subagentdsh-tool-subagent-controldsh-tool-subagent-report

出处:packages/subagent/README.mddeepseek-harness-src/packages/subagent/README.md)、docs/subsystems/subagent.mddeepseek-harness-src/docs/subsystems/subagent.md

服务 API:一个入口、两种委派

SubagentRuntimectx.subagents)的操作分三组:

成员含义
registerProvider / getProvider / list提供方注册表;注册是 effect 作用域,移除阻止新启动但不撤销已返回的运行
start(name, request)one-shot 运行:校验能力 → 解析 detached 描述符 → 等待提供方发布真实子代理。发布前失败回滚全部未发布资源;发布后轮次/基础设施故障经 run 结算
startContinuable(spec)建立持久 continuable 子代理并投递初始提示;inbox 接纳即解析 { childId, messageId },不等待轮次开始或消息进入会话日志
followup(parent, childId, content, opts)从精确活父代理向子代理投递一条后续消息,作为下一个 FIFO 轮次;resident 子代理直接接纳(唤醒等待中的 Activation),absent 则从持久化会话冷恢复
interrupt(targetSessionId, authority)以人类持久父地址({ kind: 'user', parentSessionId })或精确活祖先 Agent 中断一个活 continuable 子代理的当前轮次;签发 Agent.cancel(cause, { keepInbox: true }) 后立即返回,不对齐静止
reportFrom(child, content, opts)从精确活 continuable 子代理向精确活直接父代理投递一条选定消息,返回被接纳的稳定 MessageId
listChildren / listDescendants枚举会话支持的子代理(含 one-shot/continuable、running/inactive、hasChildren),不加载也不恢复任何 Agent

one-shot 与 continuable 的差异:one-shot 是一次可弃的前台委派,SubagentRun.result 解析为 { output, structured?, stopReason },调用方必须在每条路径上 dispose();continuable 则是一个持久子代理 Session 加至多一个进程本地 Activation——没有 run、没有 Task、没有中间结果包装,其结果留在子代理自己的会话里,续跑经 send_message 投递,结束由服务送达结算通知(settlement notice)。

出处:packages/subagent/subagent/README.mddeepseek-harness-src/packages/subagent/subagent/README.md

子代理描述符与委派深度

每个会话支持的子代理启动都会追加一条版本化的 subagent/descriptor 会话事件。该事件只做日志记录(log-only):无 surfaceOp、不进模型历史、追加日志在压缩(compaction)后保留。one-shot 描述符可选携带调用方拥有的展示 label;continuable 描述符要求持久的创建 label,并额外记录解析后的 agentOptions.provider/model 与可选 persona/toolFilter 供冷恢复——从不快照可合并扩展的 AgentOptions 对象本身,也省略 subagentDepth(持久头部的 delegationDepth 是单调下限)与 outputSchema(一次运行的结算契约)。

委派深度(delegation depth)词汇由该缝所有:持久化 SessionHeader.delegationDepth 权威且单调(monotone)——运行时选项可以加深计数但绝不降低,因此恢复的子代理不会被重新计为顶层;启动时对超过绝对上限 maxDepth 的请求报错。inheritsParentContext 是描述性的:只说明子代理是否看到父代理的已完成对话历史(fork 有,spawn 与进程外提供方没有),不说明工具、服务或权威的继承。

出处:packages/subagent/subagent/README.md(同上)

权限与策略

follow-up 权威来自子代理持久头部记录的那个精确活直接父代理;冷恢复在重建前和最终 inbox 接纳前都检查该权威,父代理在物化期间注销或替换都不能授权投递。interrupt 的权威刻意比投递更宽:人类以持久直接父地址出现时,即使父代理离线,活子代理仍可被停止(停止是幂等的、不投递内容)。

进程内委派在委派边界固定子代理的权限作用域:快照父会话的显式沙箱覆盖(sandbox override),并把子代理审批策略钉为 'never'——被委派子代理只能在其继承的沙箱作用域内行动,任何越权(例如 sandbox_permissions 升级)被确定性拒绝而不是等待无人值守的提示;策略事件以 source: 'delegation' 追加到子代理日志。

进程内与进程外提供方

  • 进程内spawn(全新会话、无父历史)与 fork(以父代理已完成轮次的平衡前缀播种——截至最后一个 turn/end,避免把未平衡的在飞轮次复制给子代理)。两者共享 dsh-subagent-in-process-driver 的运行驱动。
  • 进程外acp(Agent Client Protocol 客户端)、codex(官方 codex app-server)、claude-code(官方 Claude Agent SDK)、dsh-sdk(经 stdio JSON-RPC 驱动一个完整 Harness 运行时作为子代理)。
  • 进程外提供方不声明任何启动期能力(outputSchema/depthLimit/toolFilter/persona 全为 false),并报告 inheritsParentContext: false;对它们部署 maxDepth: 'provider-managed'——子代理自己的 harness 持有递归预算。

出处:packages/subagent/subagent-spawn-in-process/README.mddeepseek-harness-src/packages/subagent/subagent-spawn-in-process/README.md)、subagent-fork-in-process/README.mddeepseek-harness-src/packages/subagent/subagent-fork-in-process/README.md)、subagent-acp/README.mddeepseek-harness-src/packages/subagent/subagent-acp/README.md)、subagent-dsh-sdk/README.mddeepseek-harness-src/packages/subagent/subagent-dsh-sdk/README.md

模型侧工具与续跑编排

  • dsh-tool-subagent:委派工具(默认名 subagent),每个实例绑定一个提供方;backgroundMode 可选 one-shot(注册普通 Task 后台任务)或 continuable(返回持久子代理 id)。
  • dsh-tool-subagent-control:可选全局 send_message / interrupt_agent / list_agents 控件,只负责父到子方向。
  • dsh-tool-subagent-report:子代理作用域内的 report 返回通道(经 registerContinuableSetup 注入,不受子代理 toolFilter 影响)。

continuable 子代理 = 一个持久 Session + 至多一个进程本地 Activation——一次重建子代理 Agent 的驻留期(residency epoch),不是请求、结果、取消或 Task 边界。管理器从 Agent 静止状态与 owned-child 集合派生三个驻留条件:running(活跃接纳、开轮次或唤醒中的 inbox 工作)、waiting(静止但仍拥有至少一个未处置子代理)、settled(全部处置后 dispose AgentHandle 并移除 Activation)。followup 路由只依赖驻留状态:running 入队、waiting 唤醒同一 Activation、无 Activation 则冷恢复。冷恢复不经过提供方——持久会话已含初始前缀,折叠后的描述符就是全部重建输入。所有续跑消息都走 Agent.followup() 成为 FIFO 轮次,不转向当前轮次。

出处:docs/subsystems/subagent.mddeepseek-harness-src/docs/subsystems/subagent.md

关键设计决策

  • 命名提供方注册表而非单执行器:与 LLM 适配器注册表同形,同一调用契约可切换传输(spawn ↔ fork ↔ acp ↔ dsh-sdk)。
  • 描述符事件 log-only:延续"模型可见即已记录"——编排事实不进模型历史,但日志保留可重建性。
  • 委派深度持久化且单调:恢复的子代理不能重新获得顶层预算。
  • 权限在委派边界固定:子代理审批钉为 'never',越权确定性拒绝。
  • 续跑只经 Agent inbox 排队:不转向当前轮次,每个被接纳消息只有一个可观察顺序。
  • 结算通知无条件送达:对每个调用方实际收到 id 的子代理,其结束方式(令牌上限、模型失败、取消、拆除)必须被说明——即使子代理从未调用 report

扩展点

  • 新提供方:实现 SubagentProvider(含可选 prepareContinuable 能力)并 registerProvidersubagent-dsh-sdk 展示了完整 Harness 运行时作为子代理的接法。
  • 续跑能力注入registerContinuableSetup(contribution) 向每个 continuable 子代理的未发布作用域安装能力(report 工具即此路径)。
  • 生命周期观察:订阅 subagent/startsubagent/endsubagent/provider-addedsubagent/provider-removed;事件按委派父代理作用域派发。
  • 部署策略:每个工具实例固定一个提供方、persona、工具过滤与深度上限;需要不同策略就加载另一个命名实例。

参考资料

本文档为学习用途的原创讲解,基于 MIT 许可的开源项目 deepseek-ai/deepseek-harness 编写;所有引用均注明出处。