跳到主要内容
路径子 Agent

子 Agent 与并行

一个 Agent 可以把任务委派给子 Agent。DSH 的 ctx.subagents 是一个能力缝:调用方只用一个 ctx.subagents API,具体 provider 决定子 Agent 跑在本进程、另一进程,还是通过未来的传输。这是"并行处理复杂任务"的机制:把一个多步骤任务拆给多个隔离的子 Agent 同时推进。

两种子 Agent

DSH 区分两种形态,职责不同:

形态说明关键 API
一次性(one-shot)派一个子 Agent 干活并拿最终结果ctx.subagents.start(name, request)
可续(continuable)建一个持久子会话,可多次发消息、可中断、可冷恢复startContinuable(spec) / followup / interrupt

模型面对的是 tool-subagent(一次性、ctx.subagents.start)与 tool-subagent-control(send_message / interrupt_agent / list_agents),后者就是本篇讲的"可续子会话"的控制面。

一次性委派

// 请求可:选模型、要求结构化输出、限制委托深度、限制子 Agent 工具、设子 persona
const run = await ctx.subagents.start('spawn', {
prompt: '帮我分析这个仓库的 TODO',
outputSchema, // 可选:强约束最终结果结构
depthLimit, // 可选:封顶委派深度
toolFilter, // 可选:限制子 Agent 可见工具
signal, // 必填:发布前的取消通道
})
const result = await run.result // 一次性子 Agent 的最终结果
  • label 是可选的持久展示标签
  • signal 是 once-shot start规范取消通道:发布前 abort → 回滚拒绝;发布后 abort → 取消剩余轮次但不隐藏返回的 run
  • provider 通过 provider.capabilities(outputSchema / depthLimit / toolFilter)宣告支持,不支持的在建子会话前就拒绝

可续子会话(continuable)

const { childId, messageId } = await ctx.subagents.startContinuable({
label: 'research-assistant',
initialPrompt: '这是一个常驻研究助手,随时找我',
})
// 后续发消息(精确的直接父 agent 认证后才放行)
await ctx.subagents.followup(parent, childId, '再查一下 MCP 最新进展')
// 打断一个可续子 Agent 的当前轮次(保留其 inbox 与后代)
await ctx.subagents.interrupt(targetSessionId, authority)
// 列直接会话级子 Agent / 拉平整棵会话树
await ctx.subagents.listChildren(parentSessionId)
await ctx.subagents.listDescendants(rootSessionId)

关键性质(源码 README 原话要点):

  • follow-up 权威来自子 Agent 持久 header 里记录的"确切直接父":父 Agent 在重组期间被注销/替换,无法授权投递
  • 可续子会话要求 ctx.agents + 会话持久化 + 一个具备 prepareContinuable 能力的 provider
  • 冷恢复:父 agent 不在时,从持久化 Session 重建(也需 session persistence)
  • reportFrom 把子 Agent 的某条消息投回其直接父(quiet = 注入上下文;waking = 触发父的新一轮)

provider 家族

packages/subagent/ 下同一 seam 有多个实现。subagent-in-process-driverspawn / fork 两个同进程 provider 的共享 run driver(不是单独注册的 provider 名),其余六个才是 getProvider(name) 能取到的 provider:

provider子 Agent 跑哪挂载
spawn当前进程里一个全新 Agent默认挂载(base)
fork当前进程、带父已完成轮次 seed默认挂载(base)
acp全新子进程,走 Agent Client Protocolopt-in
claude-code全新子进程,桥接 Claude Code CLIprovider 默认挂载,delegation 工具默认禁用
codex全新子进程,桥接 Codex CLIprovider 默认挂载,delegation 工具默认禁用
dsh-sdk全新子进程,完整 DSH runtime(JSON-RPC)opt-in

可以多个 provider 共存在一个 ctx.subagents 后面,getProvider(name) 按名取。

in-process-driver:共享 run driver

subagent-in-process-driver 是 spawn / fork 两个同进程 provider 的唯一实现——spawn 不传 session seed,fork 传父会话已完成轮次前缀,其余(深度校验、子 Agent 创建、persona/工具过滤/结构化输出安装、结果读取、取消、dispose)都在这一个实现里:

  • startInProcessRun 只有子 Agent 在 ctx.agents 发布后才 fulfill;启动被拒时未发布的创建事务已 quiesce,调用方拿不到半建句柄
  • 深度读父的 delegationDepth(持久 header 权威,运行时只能加深不能降低),子深度 = 父 + 1 并写进子 header,所以持久化 + resume 都保住预算;超 maxDepth 报精确错误
  • 结构化输出装整套契约:一个 structured_output 工具 + 一段 order-190 系统提示段 + 一个只在该执行最终工具结果成功后 commit 的观察者 + 单次调用 guard + concludeTurn 收尾
  • 同一 driver 还给子 Agent 装 persona shadow、tool 过滤(删全局 wire schema / 可执行查找 / Code Mode SDK 绑定,但独立注册的指导段保留)、并应用 seam 的委派策略(父的显式沙箱覆盖 + never 批准钉)

spawn:同进程新 Agent

spawn当前进程里创建一个全新子 Agent:有自己的会话、看不到父对话历史,复用宿主的 agent factory 和 LLM/tool 服务。它广告 { outputSchema, depthLimit, toolFilter, persona } 全部 true,因为它在子 Agent 创建窗口内能强推这四个能力。子 Agent 继承父的工作目录/会话血统与模型(除非被覆盖),但从空对话开始。

fork:同进程 + 父已完成轮次

fork 与 spawn 共享全部运行机制,唯一差别是会话 seed:把父会话最后一个 turn/end 之前的连续前缀喂给子 Agent——父当前 in-flight 轮被排除(否则子会拿到不平衡的会话)。只传对话历史,不继承父的工具限制或权威。广告能力与 spawn 相同。因为 continuable 子会额外带 report 工具和提示段、破坏 fork 想复用的父前缀缓存,交付组合把 fork 绑定成 backgroundMode: one-shot

acp:子进程里的 ACP client

acp 把每个子 Agent 跑在全新子进程,作为 Agent Client Protocol client 驱动:spawn → ACP initializenewSession 全成功后 fulfill,表示远端会话已就绪、所有权已交给调用方。它不广告任何 start-time 能力(无法在远端进程里强推 depth / toolFilter / persona / 结构化输出),inheritsParentContext: false,唯一从父侧带过去的是工作目录。permission: reject|allow 自动应答子会话的权限请求(不向人弹窗)。每次 run 一个全新进程,无进程池。

claude-code / codex:桥接两个 CLI

这两个是固定 provider:

  • claude-code 调官方 Claude Agent SDK 的 query(),在父会话工作区解析原生 claude 可执行文件,提交一份自包含文本任务,只回传严格最终答案。SDK 读宿主的原生 Claude 设置与认证,persistSession: false、禁用 AskUserQuestion(无人值守)。
  • codex 起官方 codex app-server --stdio,建一个 ephemeral thread,同样只回传最终答案。无人值守下对命令/文件批准选 cancel/decline,权限请求给空集。contextWindowExceeded 映射 max-tokens,其余异常映射 error

两者都不广告可选能力、inheritsParentContext: false;子侧模型/工具/权限/认证全部来自原生产品安装。provider 行默认挂在 base 的 host plane(加载但不启动任何产品进程),但对应 delegation 工具行在 standard preset 里 disabled: true——复制 preset 并去掉 disabled 才把 subagent_claude_code / subagent_codex 暴露给由该副本组成的 agent。

dsh-sdk:子进程里的完整 harness runtime

dsh-sdk 把每个子 Agent 作为完整 DeepSeek Harness runtime 在全新子进程里跑,经 TypeScript SDK client 走 stdio JSON-RPC。与 acp 的区别在 wire 与子契约:子进程是一个完整 peer harness——自己的 cordis.yml 组合、会话持久化、模型路由与工具。同样不广告 start-time 能力、inheritsParentContext: false;其 delegation 工具设 maxDepth: provider-managed(子 harness 自持递归预算)。完整 runtime 每个 run 要启动整棵插件树,单次 spawn 成本高于 acp 的典型子进程。

什么时候用它

  • 并行:把一个独立子任务(fetch + 摘要 + 写文件)拆给三个子 Agent 同时做
  • 隔离:子 Agent 有自己的作用域与工具限制,不会污染父会话
  • 可续对话:常驻助手、需要跨多轮持有状态(用 continuable)
  • 一次性拿结果(用 one-shot start)

验证

# 组合树里看可用的 subagent provider
dsh web --dump-config | grep -iE "subagent"
# 会话日志里看子 Agent 事件
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E "subagent/" | head

下一步