单个 agent 是"模型 → 工具"的循环。多 agent 是另一层问题:怎么分工、怎么防递归、怎么收结果。这一章看 dsh 的四种多 agent 机制:subagent(spawn/fork 两种后端)、workflow(编排脚本)、ralph(新鲜循环)、goal(跨轮目标)。
对照点:Pi 教程第 1 章说过 Pi 刻意不做子 Agent("增加复杂度,降低可观察性")。dsh 不仅做了,还做了四种。
| 机制 | 是什么 | 典型用途 | 产物 |
|---|---|---|---|
| subagent spawn | 全新子 agent | 独立调研、并行任务 | 一次回复 |
| subagent fork | 继承日志前缀的子 agent | 基于当前上下文的延续分析 | 一次回复 |
| workflow | 模型写的 JS 编排脚本 | 多阶段扇出、批量处理 | 结构化结果 |
| ralph | 新鲜 agent 循环 | 迭代逼近不可预测的目标 | 状态推进 |
| goal | 事件溯源的目标状态 | 跨轮持续任务 | 目标达成/阻塞 |
dsh-subagent 是委派 seam(第 4 章清单里有 ctx.subagents)。它提供两种后端:
| 后端 | 包 | 语义 |
|---|---|---|
| spawn | dsh-subagent-spawn-in-process | 跑一个全新的子 agent |
| fork | dsh-subagent-fork-in-process | 跑一个继承父日志前缀的子 agent |
fork 继承什么? 包描述 [官方文档]:
进程内 fork subagent 后端:运行一个以父日志前缀为种子的子 agent。 —— dsh-subagent-fork-in-process/README.zh.md
理解:fork 的"种子"是父会话已完成轮次的前缀 —— 子 agent 一开始就"看过"父 agent 之前的对话,但它是从那里继续而不是复制整个状态。这与第 5 章的 resume 机制共享同一个底层(会话日志前缀重建)。
dsh-subagent 的能力还包括:
持久化描述符 · 委派深度 · 一次性所有权与生命周期 · 可继续子 agent 与 Activation · 结算投递 · 生命周期事件 · 收集模型 —— dsh-subagent/README.zh.md 的章节标题
委派深度(delegation depth):子 agent 还能不能再派子 agent?dsh-subagent 有深度限制,防无限递归委派。本机会话日志实测 —— 会话头部的 delegationDepth: 0(根会话深度 0)。
结算投递(settlement delivery):子 agent 结束后,它的最终回复怎么送回父 agent —— 通过"结算通知"机制投递,父 agent 以一条消息收到子 agent 的结论。
本机 preset 里实际注册了四种委派 provider [实测](第 3 章 preset 清单):
生态插槽 除了进程内后端,dsh 的委派 seam 还预留了外部 CLI 桥接(codex、claude-code)。这意味着你可以让 dsh 的子 agent 实际跑在 Codex 或 Claude Code 上 —— 跨 harness 委派。本机装了这两个 CLI,但默认走进程内实现。
dsh-workflow 是编排 seam:
工作流 seam(扩展点,ctx.workflowEngine)执行由模型编写、可扇出 subagent 的编排脚本。该 seam 定义脚本、运行、结果、错误和事件契约;引擎负责决定如何隔离并执行脚本。 —— dsh-workflow/README.zh.md
dsh-workflow-worker-thread 是它的实现:
worker-thread 工作流引擎:执行模型编写的编排脚本,脱离宿主事件循环,把 agent() 调用桥接回 ctx.subagents。 —— dsh-workflow-worker-thread/README.zh.md
工作流脚本是一个 JS 函数体(顶层 await),暴露几个原语:
| 原语 | 作用 | 关键语义 |
|---|---|---|
| agent(prompt, opts?) | 跑一个 subagent | 可用 opts.schema 要求结构化结果;失败解析为 null |
| pipeline(items, ...stages) | 每个 item 依次过多个 stage | 无 barrier:前一个 stage 完成后立刻进下一个 |
| parallel(thunks) | 并发跑多个函数并等待全部 | 有 barrier:等所有 thunk 结束 |
| phase(title) / log(message) | 进度分组 / 记录 | 给 UI 看 |
pipeline 与 parallel 的区别是核心:
pipeline:把每个 item 独立地依次通过各 stage,阶段之间没有屏障(stage 2 不等所有 item 的 stage 1 完成)。 parallel:并发运行零参函数并等待全部(一道屏障;只有某阶段真的需要所有先前结果一起时才用)。
脚本的限制(刻意):没有文件系统、网络、定时器、Node API —— "工作是 agent 做的,脚本只做协调"。这保证了编排逻辑不可滥用。
dsh-tool-workflow 的说明:
只在用户明确要求 workflow 或大型多 agent 编排时使用……脚本通过子 agent 扇出工作。
dsh-tool-ralph:
面向模型的新鲜 agent Ralph 循环,构建于 workflow 与 subagent seam 之上。 —— dsh-tool-ralph/README.zh.md
机制:每轮开一个全新的子 agent(不带父对话种子),共享工作区作为长期记忆,每轮只交回一个有界的结构化报告(完成或阻塞)。目标不可变,轮次有限额。
理解:ralph 适合"目标明确但路径不可预测、需要多轮逼近"的任务 —— 比如长时间的调研/调试。每轮新鲜上下文的代价是"每轮都要重新读工作区",收益是"不会带着错误前见继续错"。
它的命名(Ralph)和 goal 的配合见下节。
dsh-goal:
事件溯源的同会话目标状态。该服务在 agent 的现有会话中保留一个当前待完成目标,同时将继续执行的权限作为进程本地续行启用状态。 —— dsh-goal/README.zh.md
它和会话日志一体 —— 本机实测 goal/change 事件:
目标状态(phase)的取值:active / paused / blocked / completed 等,由 update_goal 驱动,每次更新都落账(revision 递增)。
dsh-goal-round-driver:
竞态围栏的同会话目标轮次驱动。 —— dsh-goal-round-driver/README.zh.md
理解:"竞态围栏"防的是多轮并发踩踏:自动续轮时,上一轮还没结算就开下一轮会造成状态错乱。围栏保证同一时刻只有一个轮次在推进同一个 goal。
blocked 的判定是个刻意的保守设计(本机会话的 goal 工具说明实测):
blocked 在达到最低轮次计数之前会被拒绝;模型负责判断同一阻塞条件是否持续了至少 3 个连续 goal 轮次,并在 blocked_reason 中说明该具体条件。 —— 本机会话的 goal 工具描述
为什么 3 轮? 防止"刚遇到困难就放弃"。难度/不确定性/还有可用工作 ≠ 阻塞;只有同一条件持续 3 轮才允许宣告 blocked。

配图说明:spawn(全新子 agent,一次性委派)vs fork(继承日志前缀,延续上下文分析)vs workflow(JS 编排脚本,worker 线程跑,pipeline 无 barrier / parallel 有 barrier)vs ralph + goal(每轮全新 agent 迭代逼近 + 事件溯源跨轮目标,blocked 需连续 3 轮,可跨会话恢复)。
| 场景 | 选它 |
|---|---|
| 一次独立的调研/任务,结果直接可用 | subagent spawn |
| 延续当前上下文的深度分析 | subagent fork |
| 大批量、多阶段、需要结构化汇总 | workflow |
| 目标明确但路径不可预测,多轮逼近 | ralph |
| 跨轮持续任务(会话重启也能继续) | goal |
判据一句话:subagent 是一次性委派,workflow 是确定性编排,ralph 是迭代逼近,goal 是状态记忆。前三个都在单次会话内,goal 跨轮次(甚至跨会话恢复)。
dsh 的多 agent 体系是一组递进的工具:
对照 Pi 的"不做子 Agent",dsh 的选择再次体现了"全插件化"的路线:四种机制都是可插拔的,你要哪种装哪种。
下一章是人与模型的界面:技能、人机界面与可扩展点。