DeepSeek Harness 源码解析 · 第 24 讲
DeepSeek Harness 源码解析系列 第 24 讲:Workflow 工作流引擎 基于 DeepSeek Harness 源码 · 2026-09-07 💡 本讲一句话: 当任务要"扇出到几十个独立子代理"时,逐轮委托太慢——于是模型直接写一段 JS 脚本当编排器。本讲拆开这套引擎:脚本跑在 worker 线程的 vm 沙箱里, agent() 通过消息端口 RPC 回宿主起子代理;并发槽位、总量上限、取消宽限期、"致命错误必须炸穿组合器"的纪律,全部在源码里逐行兑现。 一、为什么需要 Workflow:从"逐轮委托"到"脚本编排" 前几讲我们拆过 subagent(第 18 讲)——模型可以一次把一个子任务委托给一个独立 agent。但真实场景常常是 扇出 :审计 200 个文件、迁移 50 个模块、对同一结论做多角度对抗验证。如果每派一个子代理都要回到主循环、等模型再决策一轮,token 和延迟都会爆炸。 Workflow 的解法很直接: 让模型把编排逻辑写成一段 JavaScript 。脚本里可以 for 、可以 await Promise.all 、可以用组合器 parallel() / pipeline() 并发地调 agent(prompt) 。模型只写"怎么编排",真正的活还是由子代理干。 这套能力在仓库里是 四个包 协作的产物( packages/workflow/ ): 包 角色 关键文件 dsh-workflow Service Definition(能力缝):抽象引擎 + 事件契约 + 错误分类 src/index.ts 、 types.ts dsh-workflow-worker-thread Provider:worker 线程里跑 vm 沙箱 + host⇄worker 协议 + 子代理桥接 runtime.ts 、 host.ts 、 session.ts dsh-tool-workflow Consumer:模型可见的 workflow 工具 + schema + 结果渲染 src/index.ts dsh-tool-ralph 基于 workflow 的"循环重试直到通过"工具(本讲不展开) src/index.ts 注意这个分层:模型只看得见 tool-workflow ;它背后是抽象的 dsh-workflow 缝( ctx.workflowEngine );真正干活的是 worker-thread provider 。这意味着"换一个更硬的引擎"不需要动模型看到的任何东西——这正是第 10 讲讲的 capability seam 思想在编排层的落地。 二、能力缝:一个"只读生命周期 + 致命错误分类" dsh-workflow 的 index.ts 定义了抽象类 WorkflowEngine (只有一个抽象方法 start() )和六个只读生命周期事件: workflow/start|phase|log|agent-start|agent-end|end 。注释里反复强调一件事—— observe-only,绝不暴露 run 控制 :监听器只能"看",不能从事件回调里去改脚本执行。 这套缝最精妙的设计是 错误分类 。它把失败分成两类: 致命错误 (拼错选项、触发上限、子代理基础设施挂了)必须炸穿整个脚本; 普通子任务失败 则只让那一项变成 null 。这个区分靠一个 fatal 标志位显式表达: 📄 packages/workflow/workflow/src/index.ts (第 130-148 行) export class WorkflowError extends HarnessError { // 致命错误类型:继承 llm 的 HarnessError,让 code 可机器路由 // 继承 llm 包的 HarnessError,让 code 成为机器可路由的错误分类 /** Whether combinators must propagate this error instead of nulling the item. */ // fatal 标志的含义:组合器必须重抛、而不是把该项置 null(见下方字段) readonly fatal: boolean // 致命…