一个 11 天搬完 75 万行代码的 PR
先看一个数字。
Bun 的作者 Jarred Sumner 做了一次运行时迁移:约 75 万行 Rust 代码,11 天搬完,原有测试 99.8% 通过。对应的 PR 是 oven-sh/bun#30412,公开可查。
75 万行不是改几个函数,是改一整套运行时。11 天也不是一个团队连轴转的工期,而是一个人带着工具干出来的活。这次迁移的主力,就是 Claude Code 在 v2.1.154 里随 Opus 4.8 一起放出来的新功能------Dynamic Workflows。
我第一次看到这个案例时的反应是:一个对话窗口里的 AI,怎么可能扛得动这么大的活?答案是,它没在对话窗口里扛。它把活拆成了一段在你本机跑的脚本。
这篇文章讲清楚这件事是怎么发生的,以及它和你已经在用的 subagent、skill、agent teams 有什么本质不同。先说明,这是 research preview(研究预览),还在早期。
单个 agent 一次跑不完的那些活
日常用 Claude Code,大部分任务一个会话就解决了:读几个文件、改一处逻辑、跑个命令、修个 bug。这类活串行干完就行。
但总有一类任务,一次对话装不下:
- 整个服务范围的 bug 排查------你不知道问题藏在哪几个文件里,得把范围铺开了一处处看。
- 动辄上百文件的迁移------每个文件的改法相似,但数量大到一轮一轮派根本派不完。
- 一个需要从多个角度反复推敲才敢拍板的方案------你想要的不是一个答案,是好几个独立答案打过架之后剩下的那个。
这三类活有个共同点:中间结果太多。排查会产出一大堆"疑似问题",迁移会产出上百个"改完待验证的文件",方案推敲会产出 N 个互相竞争的草案。这些中间结果,传统模式下全都得经过 Claude 的脑子。这就是问题所在。
核心转变:把编排写进代码
官方对这个功能的一句话定义是:A workflow moves the plan into code.------一个 workflow 把计划搬进了代码。
平时 Claude 干复杂活,是这么干的:想一步,派个 subagent 去做,等它回来,读结果,再想下一步,再派。编排逻辑活在 Claude 的脑子里,靠一轮一轮的对话往前推。
Dynamic Workflows 改的是这件事。Claude 不再逐轮在脑子里调度,而是先把整个编排过程写成一段可执行的 JavaScript 脚本:哪里要扇出上百个任务、哪里要并发、哪里要等齐、哪个结果喂给哪个下一步------全写进代码里。写完,这段脚本自己跑。
换句话说,编排从"一段对话"变成了"一段程序"。循环、分支、把中间结果攒起来这些事,本来压在 Claude 的注意力上,现在压给了 JS 运行时。
运行模型:它不是服务端的引擎

这是最容易误解、也最关键的一节。很多人一听"workflow"就以为是跑在 Anthropic 服务端的某个编排引擎。不是。
Workflow 就是 Claude Code 在你本机跑的一段 JavaScript 脚本。agent()、parallel()、pipeline() 这些控制流,全在你的机器上执行。
这个运行时是"无脑的、确定性的"。它只会做三件事:循环、拼字符串、await。它本身不含任何 LLM------它就是个普通的 JS 运行时,给它同样的输入会走同样的路径。
那智能从哪来?只有当脚本执行到 agent() 那一行,运行时才临时去雇一个 LLM subagent 来干这一段活。这个 subagent 调模型的方式,和你对话窗口里的主 Claude 完全一样,走 Anthropic 原生的 Messages API。脚本是骨架,agent() 是骨架上临时接进来的肌肉。
还有一个反直觉的点:脚本跑的时候,你正在对话的那个主 Claude,根本没在运行。它发出 Workflow 调用的那一回合就结束了,脚本在后台独立跑。等脚本跑完,一条通知把主 Claude 叫醒,它再去读最终结果。主 Claude 全程在睡觉,醒来只看结论。运行情况你可以用 /workflows 命令查。
这就是为什么它扛得住 75 万行的迁移:真正循环上百遍的是那个本身不调用模型的 JS 运行时,不是任何一个 LLM 的上下文窗口。
跟 subagent / skill / agent teams 的区别

把已有的协作能力捋一遍,分层就清楚了:
- session:单个 agent 实例,从头干到尾,串行。
- subagent:主 agent 派生小弟去搜文件、读代码、跑命令,干完汇报回来。
- agent teams:多个独立的 Claude Code 实例像团队一样并行,队员之间还能互相通信,是网状协作。
- workflow:树状结构------一个 claude 扇出上百个 task,每个 task 走 implementer → 两个 verifier → fixer 三层,最后扇入返回。
subagent 和 skill 模式下,编排者始终是 Claude:它逐轮决定下一步派谁,而每个 subagent 的返回都要先回到 Claude 的上下文窗口,它读完才能决定下一步。规模一大,上下文装不下海量中间结果,注意力也被稀释------这正是上面"中间结果太多"那个痛点的根。
workflow 模式把编排者换掉了:脚本自己持有循环、分支、中间结果,Claude 的上下文里只剩最后那个答案。
复用维度也不一样。subagent 复用的是"一个工作者",skill 复用的是"一条指令",而 workflow 复用的是"整套编排逻辑"------写好一次能存下来反复运行。
怎么用
入口是 /workflows 命令,用它创建和查看运行。
每个脚本必须以 export const meta = {...} 开头。meta 必须是纯字面量------不能有变量、不能有函数调用、不能有模板插值。它定义 name、一行 description(会显示在权限弹窗里)、以及 phases。
核心原语有这么几个:
javascript
export const meta = {
name: "migrate-files",
description: "迁移并逐文件验证",
phases: [{ title: "scan" }, { title: "migrate" }, { title: "verify" }],
};
// 脚本体直接从 meta 之后开始,是顶层 await ------ 没有 export default,不用包在函数里
// agent(prompt, opts):spawn 一个 subagent,带 schema 时强制结构化输出
const files = await agent("列出所有待迁移文件", { schema: FileList });
// pipeline(items, ...stages):流水线,无 barrier,默认推荐
await pipeline(
files,
(f) => agent("迁移 " + f), // stage 1
(f) => agent("验证 " + f), // stage 2
);
// parallel(thunks):并发,是 barrier,等所有完成才返回
await parallel([() => agent("汇总报告"), () => agent("生成 changelog")]);
pipeline 没有 barrier------item A 可以已经走到 stage 3,item B 还在 stage 1,谁也不等谁。parallel 是 barrier,等所有任务都完成才返回。另外还有 phase(title) 分阶段、log(msg) 输出进度、args 入参、budget 控制 token 预算,以及 workflow() 嵌套一层。
两条硬上限要记住:并发上限是 min(16, CPU 核数 - 2);单个 workflow 生命周期里 agent 总数上限 1000。
几个值得抄的编排范式
脚本能写编排,就意味着你可以把一些"反复推敲"的套路固化下来。官方给了几个:
- adversarial verify(对抗式校验):对每个发现,spawn 多个独立的 skeptic 来投票,多数否决就把这个发现杀掉。专治那种"看着合理、其实是错的"结论。
- judge panel(评委团):生成 N 个独立方案,并行打分,从胜出的那个综合出最终结果。前面说的"从多个角度反复推敲才敢拍板",落地就是这个。
- loop-until-dry:面对规模未知的发现,持续 spawn,直到连续 K 轮都没有新东西冒出来为止。你不知道要排查多少处时用它。
- pipeline 默认优于 barrier :默认用
pipeline,让每个 item 各跑各的。只有当某个阶段真的需要前一阶段的全部结果时,才换成parallelbarrier 等齐。
这几个范式都不是新概念,但能写进一段可存可复用的脚本里,意义就不一样了------下次遇到同类任务,直接跑。
小结
Dynamic Workflows 做的事,一句话说完:把 Claude 脑子里的编排逻辑,搬进一段在你本机跑的、无脑确定性的 JavaScript 脚本;脚本负责循环和攒中间结果,只在 agent() 那一行临时雇 LLM 干活,跑完叫醒主 Claude 读结论。
它不和 subagent、skill、agent teams 抢饭碗,而是补上了它们够不着的那一格:单个 agent 一次跑不完、中间结果多到上下文装不下的大任务。75 万行的 Bun 迁移就是证据。
它还是 research preview,但这个方向值得早点上手------用 /workflows 起一个最小脚本,把你手头那个"一次对话装不下"的活试着拆进代码里。