Skip to content

CH 13 · 子代理与多 Agent 编排

全文字数3798 字预估耗时约 22 分钟前置CH 08、CH 11难度可照做

本章目标

一个任务太大、太绕,与其让一个 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列出当前有哪些子代理、什么状态关(默认不启用)

所以开箱即用,你至少能直接喊 subagentsubagent_forkralph。剩下的工具,要么默认关闭需要手动开,要么本身就是"持续模式"才用得上的辅助——后面一节会讲到它们各自出场的位置。

spawn 与 fork:两种"开局"

两个最常用的子代理,差别就一个:要不要带上主会话的历史

  • subagent(spawn):全新上下文。子代理不知道主代理之前聊了什么,纯粹按你这次给的指令干活。省 token,适合互相独立的任务。
  • subagent_fork(fork):继承父会话已完成的部分。子代理知道"我们刚才分析到哪了",可以顺着往下做。多花一点重复上下文的成本,适合要接着前面结论继续推进的任务。

有个细节值得记一下:dsh 会把"继承与否"直接写进工具描述里——spawn 类的工具描述会说"它看不到本次对话",fork 类的会说"它看不到正在进行的这一轮"。也就是说,模型自己就知道该不该在指令里把背景再复述一遍。

并行子代理:主 Agent 拆任务、派活、收结果

一句话记法

subagent 像空降一个新同事,只带你的口头任务书; subagent_fork 像把会议纪要拍给他,说"接着刚才聊的往下做"。

动手:第一次并行子代理

并行启动三个子代理,分别做:

  1. 梳理 deepseek-harness 仓库的 packages/ 目录结构,说明每个能力族是干什么的;
  2. 总结根目录 README 讲了哪些核心概念;
  3. docs/ 里找子代理(subagent)相关的文档,列出来。 等三个都完成后,把结果合并成一份摘要。

注意这句话里的三个要素,缺一个就容易乱:

要素为什么必须有
任务拆分规则告诉模型"拆成哪几份",不拆它就自己瞎猜
等待机制(等都完成)不加这句,主代理可能在一个子代理还没跑完时就开始整理不完整的结果
输出要求(合并成摘要)不给,三个子代理各自丢回一堆原始文本,你还得自己拼

发出去之后,你在对话里能看到模型连着发起好几次 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、持续子代理三种跑法的区别
  • [ ] 知道 workflowralphsend_messageinterrupt_agentlist_agents 各自是干什么的

Open Source · MIT · Community Driven