第 08 章 编排:让模型"分身"去干活
代码基线:commit
8f86b979a1(2026-10-01)|0.2.0-rc.1| 本系列讲次:M7
本章导读
这一章讲一支由模型组建的团队。
第 6 章讲手脚耳目,这一章讲分身:子 Agent 接缝、持续子 Agent 的激活模型、Workflow、Goal、Plan、Schedule、Jobs。
这一章是全书核对下来书稿最准的一章,几乎不需要勘误。 所以它的价值不在"纠正",而在"把书稿已经说对的地方落到代码上":ctx.subagents 有 7 个 Provider 分三类;SubagentCapabilities 的五个 flag 只管一次性路径,可续能力由一个可选方法 prepareContinuable 的存在性判定——存在本身就是能力,TypeScript 把可用与不可用收窄得明明白白。
本章还会解一个 M0 留下的谜团。你在 preset 里见过四行 provider: spawn / fork / codex / claude-code 的配置,那是同一个工具包靠 config 字段实例化出来的四个模型可见工具。规律自己浮现:进程内的用 continuable,外部协议的用 one-shot,而外部协议的深度预算交给 provider 自己管。
学习要点
- 多提供方接缝 vs 单提供方接缝:
ctx.subagents与ctx.shell是两种形态- 一次性与可续走两套不同的能力判定机制,不要混
prepareContinuable这种"能力即存在"的写法,比运行时布尔字段可靠- 一个工具包 + 四个 config 字段 = 四个模型可见工具,不必写一行代码
- 看 ctx 键是复数还是单数,就能判断一个机制是注册表还是单一服务
本章目标
- 拿到六个编排机制的实测全景:包数、ctx 键
- 理解
ctx.subagents为什么是多提供方接缝,而ctx.shell是单提供方 - 掌握两条"能力即类型"的手法:
SubagentCapabilities的 flag 自报、prepareContinuable的可选方法 - 看懂一个工具包靠 config 实例化出四个模型可见工具——M0 那个 preset 里已经见过
一、六个编排机制的全景
| 机制 | 包数 | ctx 键 / 服务 |
|---|---|---|
| subagent | 10 | ctx.subagents → SubagentRuntime |
| goal | 4 | ctx.goals → GoalService |
| workflow | 4 | ctx.workflowEngine → WorkflowEngine |
| jobs | 3 | ctx.jobs → JobRegistry |
| schedule | 1 | ctx.schedule → ScheduleService |
注意 ctx.goals 和 ctx.subagents 都是复数(和 M6 的 ctx.terminals 一致),而 ctx.schedule 是单数。
一条贯穿的判据(M6 起就在用):看一个机制是"能力"还是"调度器",看它的 ctx 键是复数(一个注册表,容纳许多实例)还是单数(一个服务,一个实例)。
ctx.subagents容纳 7 个 Provider,ctx.goals容纳每个会话的目标,ctx.schedule则是一个统一的调度服务。
二、★ ctx.subagents 的 Provider 全景:一个清晰的分层
书稿 §7.2 说它"和 shell 那种单提供方接缝正好是两种形态",完全正确。实测 7 个 Provider(脚本第 2 项):
| 包 | 类别 | description 要点 |
|---|---|---|
spawn-in-process |
进程内 | 跑一个全新子 agent on ctx.agents(无前缀) |
fork-in-process |
进程内 | 跑一个用父会话前缀做种子的子 agent |
in-process-driver |
共享驱动 | 两个进程内后端共用的 run driver(不是 Provider) |
codex |
外部协议·一次性 | over the official app-server protocol |
claude-code |
外部协议·一次性 | over the official Agent SDK |
acp |
进程外 | 在派生子进程里驱动子 agent,走 ACP 协议 |
dsh-sdk |
进程外 | 在派生子进程里驱动一个完整 DSH 运行时,走 SDK |
三个维度:{进程内, 进程外, 外部产品协议} × {全新, 前缀派生} × {一次性, 可续}。
教学点:书稿 §7.2 那句"派遣这件事既能发生在 dsh 内部,也能伸向外部产品的 agent",在这张表上是一目了然的。
三、★ 能力自报:SubagentCapabilities 的五个 flag
书稿 §7.2.1 说这是"一个 fail loud 的契约",实测完全属实:
export interface SubagentCapabilities {
readonly agentOptions: boolean
readonly outputSchema: boolean
readonly depthLimit: boolean
readonly toolFilter: boolean
readonly persona: boolean
}
注释把三件事写死了(脚本第 3 项抽出的原文):
- 每个 flag 与
SubagentStartRequest的一个选项一一对应:"Each flag corresponds one-to-one to aSubagentStartRequestoption:depthLimittomaxDepth; the other names match." - 不支持就 typed error,不静默忽略:"is rejected with a typed error rather than accepted-then-ignored (the 'fail loud, no silent degradation' rule)."
- flag 只描述 one-shot 路径:"These flags describe the ONE-SHOT
SubagentProvider.startpath, where the provider composes the child; continuable children are composed by the continuation manager itself and are gated byprepareContinuableinstead."
第 3 条是书稿没写清楚的分界:一次性派遣和可续派遣走的是两套完全不同的能力判定机制。flag 管前者,可选方法管后者。
四、★ "能力即存在":prepareContinuable 是可选方法
脚本第 4 项的实测输出:
SubagentProvider 必选方法: start
SubagentProvider 可选方法: prepareContinuable
整个接口只有一个必选方法 start。 可续能力完全由可选方法是否存在来表达。
书稿 §7.2.1 的原话:
"可续子 agent"的能力,则是靠一个可选方法
prepareContinuable是否存在来判定的——它的存在本身就是那个能力,TS 类型会把可用/不可用收窄得明明白白。
这句一字不用改。 它精准地描述了 TypeScript 可选方法的能力判别模式:不需要额外的 supportsContinuable: boolean 字段,不需要运行时的 if-else,存在性就是能力。
对应的服务侧钩子在 continuation.ts:69-74:ContinuationHost 接口收 prepareContinuable 与 observeActivation,由拥有服务注入。
五、★ 一个工具包,四个模型可见工具
这是 M0 那个 preset dump 里已经出现过的东西,现在能完整解释了。
tool-subagent/src/index.ts 一个包的 Config schema:
provider : string (required)
toolName : string (default 'subagent')
modelSelectionSettings : boolean
enableRunInBackground : boolean
backgroundMode : 'one-shot' | 'continuable' (default 'one-shot')
maxDepth : number | 'provider-managed'
provider + toolName 两个字段,就能把同一个包实例化成多个模型可见的工具。 你本机 preset 里实际挂载的(脚本第 6 项):
| 配置行 id | provider | toolName | backgroundMode | maxDepth |
|---|---|---|---|---|
tool-subagent |
spawn |
subagent |
continuable |
— |
tool-subagent-fork |
fork |
subagent_fork |
continuable |
— |
tool-subagent-codex |
codex |
subagent_codex |
one-shot |
provider-managed |
tool-subagent-claude-code |
claude-code |
subagent_claude_code |
one-shot |
provider-managed |
两条规律自己浮现出来了:
- 两个进程内后端用
continuable,两个外部协议用one-shot - 外部协议那两个的
maxDepth是'provider-managed'—— 因为它们的深度预算属于外部产品,dsh 不替它决定
第 316 行还写明了这个 'provider-managed' 的处理:只有不是 'provider-managed' 时才走 dsh 自己的 assertSubagentMaxDepth 检查。
这正是 M2 那条"一切皆组合"的延续:模型看到的工具名、能力、预算,全部由 YAML 决定,不写一行代码。
六、激活模型的路由是纯判断
书稿 §7.3 描述的三分支路由(running→enqueue、waiting→wake、none→cold-resume),在 continuation.ts:274-301 落实为一个朴素判断:
if (activation === undefined) return this.coldResume(parent, childId, content, options)
有激活就投递,没有激活就冷恢复。 没有状态机堆栈,没有私有状态变量 —— 激活存不存在本身就是状态。
书稿 §7.3 那句"状态即数据的纯折叠哲学在编排层的又一次现身",实测属实。
七、本章核对结论:书稿第七章基本无需勘误
| 书稿断言 | 实测 |
|---|---|
ctx.subagents 是多提供方接缝,与 shell 的单提供方形态相对 |
✅ |
| Provider 包含"进程内派生、fork、Codex、Claude Code" | ✅ 且实测有 7 个,比书稿列的多 |
可续能力由可选方法 prepareContinuable 的存在性判定 |
✅ 一字不差 |
maxDepth 用 delegationDepth 记账并预算约束 |
✅ glossary 亦记 "durable delegationDepth" |
| 激活路由是纯函数,不靠状态堆栈 | ✅ |
只有一处需要补充(见 S-36):书稿没有点明一次性与可续走两套能力判定机制(flag vs 可选方法),也没列出 SubagentCapabilities 的五个具体 flag 名。
本章实验(附录)
八、动手验证
export PATH="/opt/homebrew/bin:$PATH"
cd /Users/ygs/ygs/deepseek-harness
node --import tsx/esm "ygsdoc/学习笔记/labs/M7-orchestration-lab.ts"
六组输出:① 五个编排机制的包数与 ctx 键;② 7 个 subagent Provider 的分类;③ SubagentCapabilities 的五个 flag 与两条注释原文;④ SubagentProvider 的必选/可选方法;⑤ backgroundMode 与 maxDepth 的取值域;⑥ 本机 preset 实际挂载的四行配置。
M7 通过标准:能说出为什么"外部协议 Provider 的 maxDepth 必须是 'provider-managed' 而不是具体数字"——因为深度预算属于外部产品,dsh 不替它决定(tool-subagent/src/index.ts:316 对此有专门分支)。
实验脚本:labs/M7-orchestration-lab.ts
运行(在仓库根目录):
export PATH="/opt/homebrew/bin:$PATH"
cd /Users/ygs/ygs/deepseek-harness
node --import tsx/esm "dsh-源码精读/labs/M7-orchestration-lab.ts"
预期输出(本机实测,任何一行对不上就说明基线变了):
1) 六个编排机制:包数 / ctx 键 / 服务类
────────────────────────────────────────────────────────────────────────────
2) ctx.subagents 的 Provider(按 package.json description 分类)
────────────────────────────────────────────────────────────────────────────
3) SubagentCapabilities 的 5 个 flag: agentOptions, outputSchema, depthLimit, toolFilter, persona
注释原文(能力自报 → typed error 而非静默忽略):
is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
(未匹配)
continuable children are composed by the continuation manager itself and are * gated by {@link SubagentProvider.prepareContinuable} instead. Each flag * corresponds one-to-on
4) 可续子 Agent 的能力判定
SubagentProvider 必选方法: start
SubagentProvider 可选方法: prepareContinuable
prepareContinuable 出现在: continuation.ts(ContinuationHost 钩子)
5) 一个工具包靠 config 实例化出多个模型可见工具
backgroundMode 取值: one-shot | continuable (默认 one-shot)
maxDepth 取值 : number | 'provider-managed'
→ provider 字段决定"派给谁",toolName 决定"模型看到什么工具名"
6) 本机 web profile 里实际挂载的 subagent 工具(来自 M0 的 dump 结论)
tool-subagent provider=spawn toolName=subagent backgroundMode=continuable
tool-subagent-fork provider=fork toolName=subagent_fork backgroundMode=continuable
tool-subagent-codex provider=codex toolName=subagent_codex backgroundMode=one-shot maxDepth=provider-managed
tool-subagent-claude-code provider=claude-code toolName=subagent_claude_code one-shot maxDepth=provider-managed
→ 两个进程内用 continuable;两个进程外 one-shot 且深度交给 provider 自管
…(完整输出见运行脚本)
本章的勘误条目见 附录 A · 勘误总表。