Cloudflare Workflows 按通道迁移:Dynamic Workflows 与 V2 的取舍
Cloudflare Workflows 是否该按内容通道拆成 Dynamic Workflows?本文结合 6 条 AI 发布流程,拆解 Workflows V2 的并发与队列上限、确定性重放和实际迁移成本,并说明为何 publish-{brief_id} 幂等键必须留在 HTTP 调度边界。

每天都有 6 个 Anthropic 侧的发布例程向我的系统发起请求,每一个都会 POST 到同一个静态 PublishWorkflow,实例键为 publish-{brief_id}。5 月 1 日,Cloudflare 发布了 @cloudflare/dynamic-workflows;几天后,又推出了 Workflows V2,其控制平面专门针对 Agent 触发的负载进行了重构。真正值得关注的不是新品发布本身,而是另一件事:我当初为了保证幂等性而选择单一静态工作流,如今不再是沿用平台限制,而成了一个必须重新论证的架构决定。
Cloudflare Workflows 真正让我意外的变化
我原以为这次发布只会宣布“Workflows 可以承载更大规模了”:上限提高、队列加深,再来一次常见的年度升级。但这其实只是公告中较次要的一半。
更重要的变化是,工作流代码如今可以在运行时按租户而不同。@cloudflare/dynamic-workflows 于 5 月 1 日发布,采用 MIT 许可证,并基于 Dynamic Workers 构建。它允许开发者注册一个 WorkflowEntrypoint,在创建实例时再从 Dynamic Worker 解析其执行体。这样一来,步骤图便能随租户变化,不必固化在部署包中。Cloudflare 自己将其概括为“跟随租户的持久执行”。如果你曾尝试用功能开关把客户专属逻辑塞进同一套工作流,就会知道这句话有多准确。
我的 6 个发布例程——news、dev、build、design、marketing、founders、business——本质上就是共享同一个 PublishWorkflow 的 6 个租户。它们不是客户,而是 Anthropic 侧的 AI 例程,每个例程负责一条不同的内容通道。但多租户模型恰好适用:编排主干相同,各通道的具体形态略有差异,最终都调度到同一个 Cloudflare 账户。
几天后 Workflows V2 上线,而那篇公告的叙事重点比数字更值得留意。V2 的架构明确围绕一个前提重做:工作流实例由 Agent 以机器速度 创建,而不是由人点击按钮触发。这正是我的触发模式。发布例程决定上线一篇文章,本质上是一个非人工事件,由此创建一个持久执行实例;V1 的控制平面面向的则是另一种负载形态。
Dynamic Workflows:跟随租户的持久执行
Cloudflare 关于 @cloudflare/dynamic-workflows 的发布文章;该项目采用 MIT 许可证并基于 Dynamic Workers 构建。
非技术创始人真正需要关注什么
“持久执行”这个词听起来很容易让人望而却步。说白了,它就是一项能熬过系统崩溃的多步骤任务:恢复后从上一个已完成步骤继续,而不是从头重跑。如果 8 个步骤中的第 5 步因为某个 API 宕机而失败,系统只会重试第 5 步,不会重新执行第 1 到第 4 步,也不会让你为这些步骤再付一次钱。
这才是创始人需要在意的部分。按步骤划分的重试边界,同时也是按步骤划分的成本边界。假设流水线在第 3 步和第 6 步分别调用付费 AI 模型,而第 7 步崩溃,你希望重试的是第 7 步,而不是整项任务。对一条付费流水线做非幂等重跑,造成的可不只是数据库里多出一行,而是账单被重复计算。
关于该构建还是暂缓,你可以用一句话告诉承包商:只有当各产品线的逻辑确实出现分化时,才拆分工作流;不能只因为平台现在支持拆分就这样做。 Cloudflare 刚推出了一项强大的新抽象,看到这类能力,人很容易围绕它重新设计系统。但 N 个工作流定义意味着 N 倍的维护工作,只有各通道确实在执行不同任务时,收益才会出现。如果有人说“既然现在能拆,就该把每条产品线都改成独立工作流”,创始人应该追问的是业务流程发生了什么变化,而不是平台增加了什么能力。
我实际运行的架构
这套系统的真实结构很小,完全可以装进脑子里。Anthropic 侧的每个发布例程完成调研并起草 brief 后,都会向网站的 /api/admin/publish 发起 POST。该端点校验 payload、生成实例 ID,然后在名为 PublishWorkflow 的单一 WorkflowEntrypoint 上调用 instances.create。
工作流本身由 8 个具备幂等性的 step.do 阶段组成:
- validate(校验 brief schema、slug 唯一性)
- ground(抓取引用、解析链接)
- generate(调用 Claude 生成正文)
- clean(校验 markdown directive)
- persist(写入 Postgres、执行版本管理)
- index(生成 embeddings、刷新搜索)
- cover(生成图片并上传至 R2)
- publish(切换状态、ping sitemap)
export class PublishWorkflow extends WorkflowEntrypoint<Env, PublishParams> {
async run(event: WorkflowEvent<PublishParams>, step: WorkflowStep) {
const brief = await step.do("validate", () => validateBrief(event.payload));
const grounded = await step.do("ground", () => groundCitations(brief));
const draft = await step.do("generate", () => generateBody(grounded));
const cleaned = await step.do("clean", () => validateDirectives(draft));
const row = await step.do("persist", () => persistArticle(cleaned));
await step.do("index", () => reindex(row.id));
await step.do("cover", () => generateCover(row.id));
await step.do("publish", () => flipStatus(row.id));
}
}调度边界的代码如下:
const id = `publish-${brief.brief_id}`;
try {
await env.PUBLISH.create({ id, params: brief });
} catch (e) {
if (isDuplicateIdError(e)) return new Response("already queued", { status: 200 });
throw e;
}instances.create 遇到重复 ID 时会抛出异常。这一行承担着关键职责:它是阻止 Anthropic 例程重复触发后再次发布同一篇文章、并让我为其中的 AI 调用支付双份费用的唯一屏障。
为什么 6 条通道只用一个定义?因为所有通道的 8 个阶段完全相同,区别仅在编辑配置包,包括语气规范、受众规范、反模式清单和 brief 提示。这些内容作为运行时数据放在调度 envelope 中传递,而不是写进代码。通道名决定把哪套配置交给第 3 步(generate);其他步骤都不关心通道。
真正的压力点——也是唯一值得认真讨论 Dynamic Workflows 的理由——在于其中一条通道开始需要不同的 步骤图,而不只是不同的配置包。重调研通道希望在生成前多做一轮事实依据补充,并可能在生成后增加事实核查步骤。这是结构差异,不是数据差异。现在它被写成 ground 内部的条件分支,确实能运行;但如果再让 3 条通道加入各自的特殊分支,这类代码很快就会腐化。
迁移到 Cloudflare Dynamic Workflows 的真实成本
这正是 createDynamicWorkflowEntrypoint 带来的能力。系统会在运行时从 Dynamic Worker 加载各通道的工作流代码,因此当天没有触发的通道几乎不产生保留成本。你不再需要承担“所有通道始终打进同一个部署包”的维护负担,也可以独立演进各通道代码,无需重新部署共享主干。

公告没有着重展开的,是这项能力的真实代价。你原本拥有一个经过类型检查的 WorkflowEntrypoint,步骤输入契约一旦漂移,编译器就会提醒;迁移后则变成 N 个运行时加载的定义,编译器无法提供同样的保障。跨通道步骤契约漂移会从构建期错误变成运行时故障。假如你有 6 条通道,其中 2 条的 persist 步骤因为有人只重构了一半通道、忘了其余部分,而开始期待略有不同的数据行结构,那么问题不会在 CI 运行时暴露,而要等到真实文章尝试发布时才出现。
这次迁移不必全有或全无。把它说成二选一,往往就是过度设计的起点。我的计划是:
- 将共享的 8 阶段主干继续保留为静态
WorkflowEntrypoint。这是热路径,6 条通道中有 5 条会原样使用。 - 把重调研通道构建成拥有独立步骤图的 Dynamic Workflow,加入额外的依据补充与事实核查步骤。
- 在
/publish边界按通道调度,只需根据brief.lane做一行 switch,选择在哪个 binding 上调用create。
何时应该删除 Cloudflare Workflow 代码:Managed Agents、outcomes 与 webhooks
反过来的架构决策:Anthropic Managed Agents 何时会让你的 CF Workflow 变得多余。
我的判断规则带有一个明确数字:只有当某条通道的步骤图与主干相差超过一个阶段时,才将它迁移到独立的 Dynamic Workflow。 低于这个阈值,在现有步骤中增加条件分支,比维护第二个工作流定义更省成本。超过阈值后,步骤内分支会开始掩盖通道究竟在做什么,独立定义才值得。
一条通道超过阈值,5 条仍低于阈值。两句话就能讲完这份迁移计划。如果承包商拿着同一份 brief,建议我第一天就把全部 6 条通道拆成 Dynamic Workflows,我不会批准。构建成本真实存在,而收益恰好只集中在一条通道上。
Cloudflare Workflows V2 为机器速度触发带来了什么
V2 最醒目的数字是:每个工作流可同时运行 50,000 个实例、排队 2,000,000 个实例;V1 的排队上限是 1,000,000 个。这些数字并非虚荣指标,但也远远超出我的实际规模。6 个例程每天各触发一两次,我的负载大约比新上限低 9 个数量级。
Cloudflare Workflows V2:确定性重放与 50k 并发实例
InfoQ 对 V2 控制平面重构及新并发上限的报道。
对我的技术栈而言,重要的不是这些数字,而是 V2 的控制平面把 Agent 触发的实例创建设为首要场景。V1 针对的是人工触发模式:用户点击按钮、实例启动,系统处理有突发但仍有边界的负载。V2 假设触发者是进程而非人,速率取决于上游 Agent 的吞吐量,而不是用户界面的点击次数。
确定性重放是 V2 最值得用通俗语言再讲一次的特性。每个步骤都彼此隔离、可以重放、具备幂等性;发生重试时,工作流会从最后一个成功步骤继续。这 恰好 就是我在调度边界用 publish-{brief_id} 实例 ID 手动守住的属性。V1 已经提供按步骤重试的保证;V2 进一步强化了重放语义,让我一直保护的结构性约束也得到下层平台的保障。
本周最实际的动作是:热路径什么也不改。V2 需要通过重新部署来选择启用,而对一条运行良好的幂等流水线来说,最糟糕的做法就是为了追逐已有的语义,匆忙迁移到新的控制平面。我会在把重调研通道构建为 Dynamic Workflow 时将它迁至 V2,因为让新代码直接采用新模型成本很低。其余 5 条静态通道保持原样,直到我有一个不是“平台发布了新版本”的真正改动理由。
我绝不会放弃的那条约束
以 publish-{brief_id} 作为实例 ID 承担着系统稳定性的重任。一旦去掉它,Anthropic 例程在自身一侧重试时,流水线就会再次触发,不仅重复发布同一篇文章,还会让工作流内部每个付费 API 调用都计费两次。
生产环境 AI Agent 防护栏:爆炸半径控制手册
幂等步骤是控制爆炸半径的一层,成本卡点则是另一层。
Dynamic Workflows 本身不会直接破坏这条约束,instances.create 的 ID 契约没有改变。真正可能威胁它的,是草率的按通道重构:如果通道代码开始在本地重新派生 ID,例如某条通道为了所谓的“更安全”而把时间戳放进 ID,那么每次重试都会生成唯一 ID,去重机制也就悄无声息地失效了。
无论版本如何升级,下面这条规则都必须保留。在允许任何其他人改动代码前,我甚至愿意把它写在墙上:
这类约束不会出现在发布公告或迁移指南里,因为只有真正经历过它所防止的故障,你才会意识到它的存在。与之对应的成本卡点——即使去重失效,也不能让一次工作流运行演变成失控账单——属于另一层,应设在模型调用层,而不是实例层。两层防护存在的理由相同:当流水线的每一步都在花钱时,结构正确性就是最便宜的保险。
如果在 2026 年 5 月从零开始,我会怎么做
如果今天才开始搭建这套系统,而不是接手一年前的架构决定,我会改变 3 件事。
第一,我会从共享静态主干开始,再为任何真正出现分化的通道增加 一个 Dynamic Workflow,而不是直接创建 6 个独立定义。新抽象出现时,人总想把它用到所有地方,但 N 个工作流定义带来的维护成本,会比规模收益更早到来。6 个定义意味着修复一个 bug 要改 6 个地方,升级依赖要处理 6 个地方,步骤契约也可能在 6 个地方发生漂移。“一条主干加一个例外”才是满足需求的最小拆分方案。
第二,我会从第一天起就把幂等键放在 HTTP 边界。等到发生重复发布事故后再补上 publish-{brief_id},代价要高得多:你必须核对重复发布的数据行、退还受影响的费用,还要在系统已经上线后给调度层补监控。相反,在流水线首次上线前加入 try { create({id}) } catch (dup) {} 模式只需十分钟,却能消灭一整类故障。
第三,我会把 Workflows V2 的确定性视为设计的 最低标准,而不是以后再选择启用的功能。即使永远不会碰到 50k 并发上限,也要让每个步骤都能安全重放。安全重放不是扩展性属性,而是正确性属性。不能安全重放的步骤一定会在重试时出错,而任何规模的系统都会发生重试。
最后这一点,是披着技术决策外衣的资深工程判断。陷阱在于把平台新能力当作等待采用的功能,真正的工程纪律则是把它们当作设计约束——无论你最终是否会用到那些容量余量。
所有租户运行相同逻辑时,还需要 Dynamic Workflows 吗?
不需要。如果步骤图完全相同、只有数据不同,就把数据放进调度 payload,继续使用一个静态工作流。只有代码本身因租户而异,也就是步骤图不同而不只是参数变化时,Dynamic Workflows 才能体现价值。
Workflows V2 会破坏现有的 V1 工作流吗?
V2 是经过重构的控制平面,重点支持确定性的 Agent 触发执行。应把迁移视为需要主动选择的操作;移动热路径之前先验证幂等性,也不要仅仅因为新版本出现,就迁移运行正常的代码。
现在实际的并发上限是多少?
每个工作流可同时运行 50,000 个实例、排队 2,000,000 个实例,原来的排队上限是 1,000,000 个。对大多数运营者来说,这远超实际需求;相比新上限,V2 更重要的特性是确定性重放。
@cloudflare/dynamic-workflows 已可用于生产环境,还是仍处于预览阶段?
它于 2026 年 5 月 1 日发布,采用 MIT 许可证,并基于 Dynamic Workers 构建。真正应该作为门槛风险来评估的是库的成熟度和你的幂等契约,而不是许可证。如果调度层足够可靠,那么这套库的就绪速度会快于你的迁移计划。
创始人应该在什么时候花钱请工程师做这次迁移?
当某条产品线的自动化逻辑确实与其他产品线出现分化时——步骤图不同,而不只是参数不同。仅仅为扩展规模而拆分还为时过早;逻辑分化才是真正的触发条件。如果你的团队也需要算清这类迁移账,这正是我通过 DVNC.dev 提供的架构评审服务。
2026年9月4日







