DeepSeek Harness 源码精读 08:编排——让模型“分身”去干活

第 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 键是复数还是单数,就能判断一个机制是注册表还是单一服务

本章目标

  1. 拿到六个编排机制的实测全景:包数、ctx 键
  2. 理解 ctx.subagents 为什么是多提供方接缝,而 ctx.shell 是单提供方
  3. 掌握两条"能力即类型"的手法:SubagentCapabilities 的 flag 自报、prepareContinuable 的可选方法
  4. 看懂一个工具包靠 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 项抽出的原文):

  1. 每个 flag 与 SubagentStartRequest 的一个选项一一对应:"Each flag corresponds one-to-one to a SubagentStartRequest option: depthLimit to maxDepth; the other names match."
  2. 不支持就 typed error,不静默忽略:"is rejected with a typed error rather than accepted-then-ignored (the 'fail loud, no silent degradation' rule)."
  3. flag 只描述 one-shot 路径:"These flags describe the ONE-SHOT SubagentProvider.start path, where the provider composes the child; continuable children are composed by the continuation manager itself and are gated by prepareContinuable instead."

第 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 · 勘误总表。