CH 13 · 子代理与多 Agent 编排
本章目标
一个任务太大、太绕,与其让一个 Agent 吭哧吭哧跑到底,不如让它自己拆成几份、派几个子代理并行去干,干完统一收结果。这一章讲清楚:为什么要多 Agent、dsh 的子代理长什么样,然后亲手跑一次"拆任务 → 并行派活 → 合并结果",最后把工具全家桶收进清单。
为什么要多 Agent:单线程的三个硬伤
一个 Agent 从头跑到尾,会撞上三个问题:
| 硬伤 | 表现 |
|---|---|
| 上下文污染 | 任务跑一半,早期塞进来的步骤、中间产物、报错全堆在同一个会话里,模型越来越分不清当下哪些信息最重要 |
| 无法并行 | 查资料、写初稿、审代码三件事只能排队,前一件没完,后一件干等着 |
| 越跑越偏 | 长任务里模型容易在一个错误方向钻牛角尖,反复试同一条失败的路径,白白烧 token |
多 Agent 的思路正好对症:上下文隔离。每个子代理只拿着自己那一份任务相关的上下文,主代理负责拆任务、派发、收结果。token 消耗可控,每一步都能在轨迹里回看。
一个关键事实:子代理也是插件
先给你吃颗定心丸:子代理不是 dsh 另外藏的一套东西,它就是插件——CH 08 那句"一切皆插件"的又一次验证。而且子代理早就是现在 Agent 工具的标配了——Claude Code、Codex 都有这个能力;区别只是 dsh 依旧以插件的形式存在:想要就要,不想要可以整个换掉。
在配置树里,你能看到一条委托契约 ctx.subagents,它只干一件事:注册"谁能当子代理"。然后由不同的 provider 插件往上面挂具体的后端:
| 插件 | 跑的是什么 |
|---|---|
subagent-spawn-in-process | 全新开一个进程内子代理(独立上下文) |
subagent-fork-in-process | 从父会话已完成的历史里"派生"一个子代理 |
subagent-acp | 走 Agent Client Protocol 协议,接进程外的 ACP 兼容 Agent |
subagent-codex | 跑一个真的 Codex(官方 app-server 协议) |
subagent-claude-code | 跑一个真的 Claude Code(官方 Agent SDK) |
subagent-dsh-sdk | 通过 TypeScript SDK 再起一个完整的 Harness |
主代理能看到什么?是另外几个"模型可见工具"。打开 web 的配置树,默认长这样:
| 工具 | 干什么 | 默认 |
|---|---|---|
subagent | 派一个子代理,持续模式(continuable) | 开 |
subagent_fork | 派一个带父会话记忆的子代理,一次性(one-shot) | 开 |
ralph | 每轮换新子代理推进同一个目标 | 开 |
workflow | 写脚本并行编排多个子任务 | 关(默认不启用) |
send_message | 给指定子代理补指令 | 关(默认不启用) |
interrupt_agent | 中断某个子代理当前这一轮 | 关(默认不启用) |
list_agents | 列出当前有哪些子代理、什么状态 | 关(默认不启用) |
所以开箱即用,你至少能直接喊 subagent、subagent_fork 和 ralph。剩下的工具,要么默认关闭需要手动开,要么本身就是"持续模式"才用得上的辅助——后面一节会讲到它们各自出场的位置。
spawn 与 fork:两种"开局"
两个最常用的子代理,差别就一个:要不要带上主会话的历史。
subagent(spawn):全新上下文。子代理不知道主代理之前聊了什么,纯粹按你这次给的指令干活。省 token,适合互相独立的任务。subagent_fork(fork):继承父会话已完成的部分。子代理知道"我们刚才分析到哪了",可以顺着往下做。多花一点重复上下文的成本,适合要接着前面结论继续推进的任务。
有个细节值得记一下:dsh 会把"继承与否"直接写进工具描述里——spawn 类的工具描述会说"它看不到本次对话",fork 类的会说"它看不到正在进行的这一轮"。也就是说,模型自己就知道该不该在指令里把背景再复述一遍。
一句话记法
subagent 像空降一个新同事,只带你的口头任务书; subagent_fork 像把会议纪要拍给他,说"接着刚才聊的往下做"。
动手:第一次并行子代理
并行启动三个子代理,分别做:
- 梳理
deepseek-harness仓库的packages/目录结构,说明每个能力族是干什么的;- 总结根目录 README 讲了哪些核心概念;
- 在
docs/里找子代理(subagent)相关的文档,列出来。 等三个都完成后,把结果合并成一份摘要。
注意这句话里的三个要素,缺一个就容易乱:
| 要素 | 为什么必须有 |
|---|---|
| 任务拆分规则 | 告诉模型"拆成哪几份",不拆它就自己瞎猜 |
| 等待机制(等都完成) | 不加这句,主代理可能在一个子代理还没跑完时就开始整理不完整的结果 |
| 输出要求(合并成摘要) | 不给,三个子代理各自丢回一堆原始文本,你还得自己拼 |
发出去之后,你在对话里能看到模型连着发起好几次 subagent 调用——这就是它在派活。默认的持续模式下,模型会"先把能并行的一起发出去,然后自己继续干别的",而不是干等第一个子代理返回。

有子代理先跑完也没关系——主代理会继续等剩下的人,全部齐了才动手合并:


前台 / 后台 / 持续:三种跑法
子代理不是只有"派出去等结果"一种姿势。按"要不要等、能不能继续对话"分成三种:
| 跑法 | 什么时候用 | 你看到什么 |
|---|---|---|
| 前台一次性(one-shot 前台) | 下一步必须立刻依赖这个结果 | 等到子代理返回最终文本 |
| 后台 Job(one-shot 后台) | 不用干等,稍后收结果 | started background subagent job <id>,用 job_output 收、job_kill 停 |
| 持续子代理(continuable) | 后面可能还要给它补指令、来回几轮 | started subagent <childId>,后续可发消息 |
默认的 subagent 就是持续模式:派出去默认后台跑,主代理该干嘛干嘛;等子代理跑完,会有一条回传通知把它"结束了、结果是什么"送回来。只有当下一步真的需要它的结果时,模型才会改成前台等着。
想对持续子代理做更多控制时,才需要把 send_message(补指令)、interrupt_agent(中断当前这轮,不销毁,可以再续)、list_agents(列出子代理和状态)这几个工具打开。它们默认关着,是因为大多数场景用不到——属于"你需要时再开"的进阶能力。
workflow 与 ralph:两种"编排型"工具
除了"拆几份并行跑",dsh 还内置了两种更结构化的编排,都是插件:
| 工具 | 干什么 | 适合 |
|---|---|---|
workflow | 让模型写一小段编排脚本,用 agent() / parallel() / pipeline() 扇出子任务,最后返回一个结果 | 任务数量多、结构固定、要精确控制执行顺序(比如同时分析 10 份文档) |
ralph | 每轮换一个新子代理推进同一个目标,每轮结束写交接报告给下一轮 | 需要多轮打磨、容易钻牛角尖的创作或调试 |
ralph 默认就开着(子代理用 spawn,最多 64 轮)。workflow 默认关——它的脚本跑在独立线程里,属于"编排约束"而不是安全沙箱,需要明确的批量场景才值得开。
一句总结
日常并行 → subagent; 要带着前面的结论继续 → subagent_fork; 结构化大批量 → workflow(默认关); 防钻牛角尖的反复打磨 → ralph。
常见坑
| 坑 | 怎么避开 |
|---|---|
| 多个子代理同时写同一个文件 | 并行任务优先做读(搜索、分析、审查);要写,就让主代理收齐所有结果后统一写 |
| 指令里没写"等都完成" | 派活时把等待机制写进去,否则主代理可能拿到半截结果就开干 |
| 任务拆得太细 | 每个子代理都有启动成本,一个 5 分钟的任务拆成 10 个子代理反而更慢;拆到"独立完成一件有意义的事"为止 |
| 子代理无限套娃 | 委托深度有上限(默认 3 层,0 表示禁止委托);保持"主代理 → 子代理"的星型结构最稳 |
这一章你学到了什么
能自己完成下面几条,就算过关:
- [ ] 说出单 Agent 长任务的三个硬伤,以及多 Agent 的核心价值(上下文隔离)
- [ ] 知道子代理也是插件:
ctx.subagents是委托契约,spawn / fork 是最常用的两个后端 - [ ] 分清
subagent(全新上下文)和subagent_fork(带父会话已完成历史)的区别 - [ ] 跑过一次"拆任务 + 等都完成 + 合并结果"的并行子代理,并在轨迹里看到 subagent 调用
- [ ] 说出前台、后台 Job、持续子代理三种跑法的区别
- [ ] 知道
workflow、ralph、send_message、interrupt_agent、list_agents各自是干什么的
