Workflow 与 Ralph
模型编写的 JavaScript 编排怎样桥接到 Agent 调用
结论:Workflow 是前台编排 VM;Ralph 是建立在它之上的固定策略
DeepSeek Harness 把编排分成三层:ctx.workflowEngine 定义由持有者负责的 run 合同;worker-thread engine 执行一段模型编写的 JavaScript,并把 agent() 桥接到 subagent seam;面向模型的 consumer 决定模型能够控制哪些脚本和策略。通用 workflow 工具暴露脚本,而 ralph 不暴露——它提供一段构建时固定的循环,只接受目标和受限轮数。
这让 Workflow 足够强,但不伪装成持久化基础设施。它是一个具有明确并发、取消、JSON 和生命周期边界的前台扇出/聚合机制,而不是 scheduler、可恢复 job 系统、安全 sandbox 或独立 verifier。
1. 三层架构把策略留在 engine 之外
| 层 | 负责 | 不负责 |
|---|---|---|
| Workflow seam | 启动请求、live run、结果词汇、生命周期事件、fatal error 分类 | JavaScript 实现、面向模型的 schema、持久化策略 |
| Worker-thread provider | VM hooks、worker/host 协议、child admission、上限、取消、清理 | 模型何时应该使用编排,或由哪个 consumer 暴露 |
| Tool consumer | 可见 schema、prompt 指引、结果投影、可选 Session 记录 | ctx.workflowEngine 后面的执行机制 |
请求携带纯 JavaScript body、JSON metadata/args、可选 provider 与 child ceiling,以及一个必需的 live parent Agent。返回的 run 暴露已验证 metadata、永不 reject 的结果 Promise、取消与幂等 dispose。WORKFLOW-SEAM
专门策略可以复用同一 engine,而无需给 Agent loop 再加分支。Ralph 正好证明了这一点:它组合已有 workflow/subagent 服务,而不是在核心 runtime 中创造“Ralph 模式”。
2. Live run 是由持有者负责的资源,不是 fire-and-forget Promise
start(request)
→ 发布前验证
→ 返回 WorkflowRun
result: Promise<WorkflowResult> // 永不 reject
cancel(reason?)
dispose() // 幂等、有界清理
这个 seam 明确了所有权。拿到 run 的 caller 必须 dispose;取消与处置有界;listener failure 被隔离;workflow/end 在结果 settle 时恰好触发一次。普通失败通过 completed、cancelled 或 error 数据表达,而不是让公开 result Promise reject。WORKFLOW-LIFECYCLE
这避免了一个含混状态:Promise reject 究竟表示“脚本失败”,还是“runtime 丢失了所有权”?它也迫使每个 consumer 正视清理,而不是把 child 当作脱离管理的工作。
3. 发布前做请求验证;发布后用结果表达运行失败
Worker provider 会验证并归一化 metadata,用 worker 将要编译的同一个 async wrapper 做 parse check,解析 child provider,并在生成 ID 或发出 workflow/start 之前约束 per-run child override。未知 metadata 字段与畸形 phase 会一次性报告;旧式 export const meta header 会收到有针对性的 parse error。WORKFLOW-START-VALIDATIONWORKFLOW-META
start() 返回之前,无效请求不存在已发布 run;返回以后,script error、worker death、cancellation 与 boundary failure 都通过 run result settle。这与仓库其他位置的事务形状一致:候选对象在变得可观察之前就被拒绝。
4. 每个 run 一个全新 Worker Thread:提供 containment,不提供安全边界
每次运行都会获得新的 Node worker。其 ambient environment 被清空,loader flag 被移除,初始化数据通过 structured clone 跨界。模型编写的代码随后在 worker 内的 vm.Context 中执行。这可防止同步脚本阻塞 host event loop,也给 host 一个可以强制终止的线程。WORKFLOW-WORKER-SPAWN
5. 脚本世界被刻意压得很小
globals = {
agent(prompt, options?),
parallel(thunks),
pipeline(items, ...stages),
phase(title),
log(message),
args
}
其中没有 filesystem、network、timer、Cordis context、Node API 或直接 Session handle。初始同步片段受 syncTimeoutMs 限制;第一次 await 之后,协作式 hook check 与 host 的 cancellation grace 共同提供停止机制。一旦 cancellation 或 disposal 开始,若脚本永久停在不再触及 hook 的 Promise 上,host 会在 grace 之后终止它;若没有这种触发,本 engine 并未定义通用 wall-clock deadline。WORKFLOW-VM-GLOBALSWORKFLOW-CANCEL-BOUNDARY
脚本负责协调 capability,但本身并不拥有这些能力。真实工作属于 child Agent,它会进入自己正常的 tool、permission 与 execution world。
6. agent() 是通往既有 Subagent seam 的 RPC bridge
每次调用验证非空 prompt 与一个封闭 option set:label、phase、schema、provider、model。明确 deferred 的 effort、isolation、agentType 会直接失败。Host 随后通过配置好的 subagent provider 启动 child,并传入 workflow 的 live parent、run 级共享取消信号、可选 output schema,以及可选 provider/model override。WORKFLOW-AGENT-OPTIONSWORKFLOW-CHILD-BRIDGE
没有 schema 时,agent() 只拼接最终 text block;有受支持的 object-rooted schema 时,它返回 structured value。一个 clean child 若缺少承诺的 structured output 会变成 null,每个 child handle 都在 finally 中 dispose。WORKFLOW-AGENT-RESULT
7. 存在两个相互独立的规模 backstop
| 上限 | 默认值 | 控制对象 | 检查时点 |
|---|---|---|---|
maxConcurrentAgents | min(16, max(1, cores - 2)) | 同时活跃的 agent() 调用 | FIFO slot acquisition |
maxTotalAgents | 1000 | 单个 run 生命周期内接受的调用 | 等待 slot 之前 |
maxItemsPerCall | 4096 | 一次 parallel() 或 pipeline() 的 item 数 | combinator 入口 |
Total counter 包括正在排队等待 concurrency slot 的调用,因此堵住了 runaway loop 把无界任务塞在小 pool 后面的常见漏洞。Slot waiter 是 FIFO,取消时会 reject。Consumer 可以降低单次 run 的 total cap,但不能超过 engine ceiling。WORKFLOW-CAPSWORKFLOW-SLOTS
8. parallel() 是 barrier;pipeline() 是逐 item 流动
| Combinator | 执行形状 | 普通 throw | Fatal workflow error |
|---|---|---|---|
parallel([f, g]) | 调用所有 thunk 并等待 Promise.all | 该位置变成 null | 整个脚本失败 |
pipeline(items, s1, s2) | 每个 item 独立执行 s1 → s2;stage 之间没有全局 barrier | 该 item 变成 null,跳过剩余 stage | 整个脚本失败 |
两者都经由 Promise.all 返回,所以保留输入位置。Pipeline 因此并不表示“所有 item 完成 stage 1 后再开始 stage 2”;快 item 可以进入下一 stage,而慢 sibling 仍停在上一 stage。WORKFLOW-COMBINATORS
9. Error taxonomy 将工作失败与合同失败分开
普通 child stop reason 会变成 null,让脚本能够筛选或聚合部分工作;combinator 内普通用户脚本 throw 也只会把该 item 置 null。但是参数错误、不支持的 option/schema、cap violation、provider start failure、child-result transport rejection、不可序列化输出与 cancellation 都是 typed fatal WorkflowError,不能溶解成 null。WORKFLOW-ERROR-TAXONOMYWORKFLOW-FATALITY
“一个 reviewer 没有发现结果”与“编排合同已经坏了”不能长得一样。Fatality check 是 host realm 的 instanceof,模型脚本无法伪造一个 plain object 冒充可忽略或 fatal 的 engine error。
10. 每个跨 realm 结果都必须是无损 JSON
Materializer 接受有限数值、boolean、string、null、dense array 与 plain object;拒绝 bigint、function、symbol、嵌套 undefined、非有限数、cycle、sparse array、exotic prototype、symbol key 与 array extra property。它用 defineProperty 复制 __proto__,因此数据 key 不会改变 host copy 的 prototype。WORKFLOW-JSON-BOUNDARY
Child result 从 host 进入 worker 之前还会单独 snapshot。无法完成这一跨越的 child value 会成为 infrastructure failure,而不是被静默损失。WORKFLOW-CHILD-SNAPSHOT
11. Cancellation 是只有一个赢家的竞态,随后进入有界 quiescence
cancel(reason)
→ 关闭新工作 admission
→ 通知 worker
→ abort pending/published child 共享的唯一 signal
→ reject queued slot 与未来 hook call
→ grace timer
└─ 若仍未 settle:补齐缺失 agent-end,settle cancelled,
terminate worker
首个 worker result、worker death 或 cancellation grace expiry 拥有 terminal settlement。已经获胜的 result 不会被 cleanup callback 改写;竞争中的非 cancel result 若晚于已接受的 cancellation,就会报告 cancelled。Host 会抑制迟到的 phase/log narration,但为每个已发布 child start 保留或合成恰好一个 end event。WORKFLOW-CANCEL-RACEWORKFLOW-EVENT-PAIRING
12. dispose() 立即开始 child cleanup,并与 worker 共享同一个 grace
Disposal 被 memoize。它移除 input signal、按需取消、立刻开始 dispose 所有已登记 child,等待“result + child quiescence”或 grace 两者中先到者,然后无条件 terminate worker。Per-child disposal 同样 memoize,因此 worker RPC、host disposal 与 death cleanup 会汇合到一个操作。超过 grace 的慢清理会被刻意停止等待,而不是让 run 永远存活;若它之后 reject,则会被记录并隔离。WORKFLOW-DISPOSEWORKFLOW-CHILD-QUIESCENCE
Engine 在返回 run 之前捕获一个 holder-bound subagent service。卸载 engine 会移除启动新 workflow 的能力,但不会让已接受 run 失去启动和清理 child 的能力。WORKFLOW-HMR-HOLDER
13. Workflow event 只供观察,并刻意省略返回值
Seam 发出 start、phase、log、agent-start、agent-end 和 end。Phase 只是进度标签,不会调度工作。Listener 收到借用的 identity/outcome snapshot,而非 control handle;listener failure 会被捕获并记录。最终 event 不含 script value:只有 run holder 可以 await 并消费它。WORKFLOW-EVENTS
Observer 可以构建进度 UI 或 metrics,但不能取消 run、修改结果,也不会意外持有 live child-control capability。
14. 通用工具增加模型合同和一份窄耐久投影
workflow 工具暴露 script、meta 与可选 object-shaped args。Description 就是 authoring specification;独立 system-prompt section 则要求只有在人类明确要求 workflow 或大规模多 Agent 编排时才使用。工具等待前台结果,把非 completed outcome 映射为错误,只限制渲染后的 JSON,并始终 dispose run。WORKFLOW-TOOL-CONTRACTWORKFLOW-TOOL-EXECUTE
仅对 root transport call,它会把 run start/end 与配对 member start/end 投影进 parent Session。Nested dispatch 正常执行,但不写这些记录。Append 失败会禁用该 run 后续 recording,却不影响执行,因此只会留下“无记录”或一个合法连续前缀。WORKFLOW-DURABLE-RECORD
15. 耐久记录是 audit trail,不是 workflow recovery
四种 Session event 记录 run name、member sequence/label/phase/child ID、member outcome 与 terminal reason。Package invariant 会拒绝重复 start、未配对 end、有开放 member 的 terminal event 和 terminal 之后的更新,同时允许 crash 后缺少 terminal suffix。WORKFLOW-RECORD-TYPESWORKFLOW-RECORD-INVARIANT
16. Ralph 把 script、provider 与 report schema 收回部署控制
面向模型的 Ralph schema 只有必填 objective 和可选 maxRounds。Plugin 拥有固定 JavaScript body、固定 metadata、structured round schema、provider route、handoff limit 与 terminal validation。配置的 provider 必须存在、支持 structured output,并声明自己不继承 parent context。RALPH-CONTRACTRALPH-FRESH-PROVIDER
通用 Workflow 让 parent model 编写 control flow;Ralph 只让模型提供数据,并运行部署方审核过的 control flow。两者都使用同一个 escapable VM,但 Ralph 暴露给 calling model 的攻击面与可靠性表面小得多。
17. Ralph 状态机很小,也很明确
previous = none
for round in 1..maxRounds:
report = await fresh agent(objective, round, previous, workspace rules)
null → round-failed
complete → complete
blocked → blocked
continue → previous = report
loop exhausted → budget-limited(previous)
Report 包含 status、summary、evidence、next steps 与 blocker text。Continue 要求有 next steps 且无 blocker;complete 要求有 evidence、无 next steps、无 blocker;blocked 要求具体 blocker。所有字符串必须 normalized,每个 serialized handoff 必须落在 maxHandoffChars 内。RALPH-SCRIPT
18. Fresh-context 路由由 provider 声明门控;连续性保存在 workspace
每一轮 prompt 都说明它既不接收 parent conversation,也不接收之前的 child Session。它只携带 immutable objective、当前 round/cap、上一个 bounded report,以及“把 shared workspace 当长期事实源、先检查再验证”的指令。Live parent 仍向 subagent seam 提供 cwd 与 lineage,但所选 provider 必须承诺 inheritsParentContext:false。RALPH-ROUND-PROMPT
这形成一套刻意的 memory hierarchy:文件与 working tree 是耐久事实;一份小 report 是协调状态;未提交的对话推理随每轮消失。它可以减少长对话带来的锚定,也使 filesystem hygiene 与诚实 handoff 成为关键条件。
19. Ralph 在 trust boundary 两侧验证同一份 claim
固定脚本在携带 child report 进入下一轮之前先验证一次。Workflow 返回后,TypeScript 再验证 exact terminal object、允许的 key、round count、各 status report 语义与 size。模型选择的 round cap 必须是正 safe integer,且不超过 deployment ceiling;同一数值也成为 engine total-child ceiling。RALPH-DOUBLE-VALIDATIONRALPH-EXECUTE
maxHandoffChars 保护每个跨轮 report 与 canonical terminal report;maxResultChars 只截断 parent-facing rendered text,不会改写 canonical value。RALPH-RENDER
20. Completion 与 blockage 是 worker report,不是独立判断
Renderer 明确写的是“worker reported completion”或“worker reported a blocker”。Child failure 会立刻结束 run,返回 failed round 与最后一次成功 handoff;它不会重试。Cancellation 与 workflow infrastructure failure 都是错误,绝不是部分成功。只有在最后一个有效 report 仍然表示 continue 时,达到 round cap 才会得到 budget-limited。RALPH-OUTCOMES
21. 默认发行组合真实挂载两种工具,但仍要求显式使用
Base composition 以 spawn provider 挂载 worker engine、通用 workflow tool,以及 deployment ceiling 为 64 轮的 Ralph。Standard CLI preset 在 delegation group 内隔离 workflow service,并挂载同样三件套。Host-plane web bundle 会禁用这些 base row,让选定的 per-session preset 拥有它们。WORKFLOW-SHIPPED-BASEWORKFLOW-SHIPPED-PRESETWORKFLOW-WEB-ISOLATION
所以 availability 取决于所选 Agent preset,而 usage policy 仍然保守:普通一两个 child 的 delegation 应使用 subagent tool;Ralph 要求人类直接提出;通用 Workflow 用于明确的大型编排。
22. 聚焦 Codex 对比:相似的 delegation 目标,不同的编排产品
| 问题 | DeepSeek Workflow / Ralph | Codex 公开合同 |
|---|---|---|
| 谁编写编排? | 通用 Workflow:parent model 编写 JavaScript;Ralph:部署方发布固定 JavaScript | Codex 通过文档化的 subagent workflow 与 role config 做委派;其公开指南没有描述面向模型的 JavaScript workflow VM |
| 主要 fan-out 单位 | 一个前台 run 内的 agent() 调用 | 专门 subagent thread,通常用于可并行、可分离工作 |
| 周期/后台工作 | 本子系统不提供 | Scheduled tasks 是另一个文档化产品面 |
| Worker 间连续性 | Ralph 使用显式 JSON handoff 与 shared workspace | 公开 multi-agent workflow 下的 parent/subagent summary 与 thread management |
Codex 公开指南建议把 subagent 用于独立且以读取为主的探索、测试或 triage,并提醒重叠的 write-heavy 工作会冲突。DeepSeek 的通用 Workflow 增加可编程 parallel/pipeline 聚合,Ralph 则要求 provider 声明每轮使用 fresh context。反过来,Codex 把 scheduled tasks 文档化为独立的周期任务产品面;DeepSeek Workflow 明确只在前台运行。参见官方 Codex subagents 指南。检索于 2026-08-13。
23. 哪些值得复用,哪些不能泛化
复用 seam
让 live-run ownership、never-reject result、有界 dispose 和 observe-only event 独立于某一种 engine。
分离 policy
只在必要处暴露自由 control flow;可重复的运维策略应发布固定 workflow。
给 failure class 命名
部分工作失败可以是数据;合同与基础设施失败必须保持 fatal。
不要过度声称
Worker thread 不是 sandbox,event trace 不是 checkpoint,worker completion report 也不是 verification。
这个子系统最强的想法并不是 JavaScript 本身,而是把编排放进受所有权约束的生命周期,然后让不同 consumer 选择暴露多少控制权。
验证说明
本章在固定源码基线上追踪了 workflow seam、worker-thread host/runtime/realm、通用 workflow consumer、耐久 event invariant、Ralph consumer 与发行组合。定向 Vitest 覆盖 12 个文件,隔离临时根目录下 190 项测试全部通过。Claim 明确区分源码事实与设计评价;Codex 对比仅使用官方公开文档。
我的学习体会
内容仅自动保存到当前浏览器,不上传、不进入仓库。你可以导出 Markdown 自行归档。