AI Agent 崩溃后如何避免重复退款?CellaFlow 的持久化执行方案

Multi-Agent系统中,进程崩溃导致的重复副作用与工作流死锁,需要持久化执行运行时来同时保证安全性与活性。
文章围绕一个退款场景揭示了生产级Multi-Agent系统的核心难题:当进程在执行不可逆操作(如支付)后、写入结果前崩溃,系统要么重复副作用,要么永久卡死。作者通过开源基准测试对比了四种方案,发现Durable claim虽能防止重复,却在持有者消失时引发死锁——即只满足「安全性」而破坏了「活性」。真正的解法需要租约、心跳、所有权转移、栅栏等原语的组合,而对于不参与事务的外部API,exactly-once语义本身就是不可能实现的,重点应转向让执行过程本身可恢复。在多Agent场景下,应以业务标识(而非调用者身份)作为幂等协调单元,让多个Agent对同一业务操作收敛到同一次执行。
多智能体(Multi-Agent)系统正在从「提示词→模型→工具→回答」的简单模式,走向真正嵌入生产环境的复杂执行流。当 Agent 开始触发退款、下单、调用支付 API 这类无法安全撤销的操作时,一个看似简单的问题会变得极其棘手:如果执行到一半进程崩溃了,会发生什么?
Reddit 上一位正在构建 CellaFlow 的开发者,围绕这个「崩溃基准测试(crash benchmark)」分享了他们的思考。核心论点很直接:Agent 本身是短暂的(ephemeral),但执行不应该是。

一个退款场景引出的经典难题
设想这样一条流程:文本 Agent 决定发起退款 → 系统退还 100 美元 → 就在这一刻,运行退款逻辑的 pod 崩溃了 → 结果还没来得及写入 checkpoint → Agent 重启 → 它是否会再次发起退款?
最直观的问题是「重复副作用(duplicate side effects)」——客户被退了两次款。但作者指出了一个同样重要、却常被忽略的问题:如果你为了防止重复而引入的机制,反而让整个工作流永久卡死了怎么办?
这就把讨论从「安全性(safety)」推向了「活性(liveness)」。只保证「操作没被重复执行」是不够的,你还得保证「操作最终能被完成」。
四种方案的基准测试对比
作者构建了一个开源、可复现的基准测试,对比了四种方案在多种故障模式下的表现:无防护(No guard)、Postgres advisory lock、Durable claim(持久化认领)、以及 CellaFlow。
测试覆盖的故障模式包括:并发重试、认领工作后崩溃、外部副作用发生后崩溃、慢速 worker、worker 永久消失、以及所有权转移。
简化后的结果颇具启发性:
| 故障模式 | 无防护 | Advisory lock | Durable claim | CellaFlow |
|---|---|---|---|---|
| 并发重试 | 5 次副作用 | 5 次副作用 | 1 次 | 1 次 |
| 认领后崩溃 | 2 次 | 2 次 | 1 次 | 1 次 |
| 副作用后崩溃 | 2,可恢复 | 2,可恢复 | 1,死锁 | 2,可恢复 |
| 慢速持有者 | 5 次 | 5 次 | 1,等待 | 1,等待 |
| 持有者消失 | - | - | 1,死锁 | 2,可恢复 |
关键洞察在于:「只发生一次」并不等于「成功的结果」。可以看到,Durable claim 在防止重复上表现优秀,却在两个场景下陷入死锁——它做对了「防重复」,却牺牲了「可恢复」。
为什么「持久化认领」还不够
作者用一段流程解释了 Durable claim 的死穴:worker A 认领了一个操作 → A 崩溃了 → 认领记录仍然存在 → worker B 来了,看到「已被认领」→ 于是死锁。
这个认领确实完成了它的任务——阻止了重复。但它也阻止了任何其他人来完成这项工作。
要让认领变得可恢复,你不得不逐步加上:认领 + 租约(lease)+ 心跳(heartbeat)+ 所有权转移 + 栅栏(fencing)。到了这一步,你已经不再是「加一个幂等性检查」,而是在构建一整套执行系统了。这正是 CellaFlow 想要提供的东西。
租约(Lease)与心跳(Heartbeat) 是分布式系统中管理资源所有权的标准机制。租约是一种带有过期时间的所有权声明——worker 在认领任务时获得一个有限时长的租约,必须在到期前通过心跳续约,否则系统认为该 worker 已经失联,允许其他 worker 接管。心跳本质上是 worker 定期向协调中心发送「我还活着」的信号。
所有权转移(Ownership Transfer) 指当原 worker 租约失效后,系统将任务控制权交给新 worker 的过程,这需要原子性地更新所有权记录以防止并发争抢。栅栏(Fencing) 则是配套的安全机制:每次所有权转移时,系统递增一个「纪元编号(epoch)」,并在每次写操作时验证当前 worker 持有的纪元是否仍为最新。这样即使僵尸 worker 复活,其持有的旧纪元令牌会被拒绝,无法写入过期状态。这四个机制组合在一起,才能在保证「不重复」的同时兼顾「可恢复」。
最难啃的骨头:副作用已经发生
真正让人不安的场景是:认领操作 → 调用支付 API → 支付 API 已经接受了请求 → 进程崩溃 → 结果从未被记录。
如果外部系统不参与你的协议,运行时根本无法知道支付到底成功了没有。于是你只剩两个糟糕的选项:不重试(可能永久卡住),或者重试(可能重复扣款)。
作者诚实地承认:没有任何分布式系统的技巧能对一个不参与事务的任意外部 API 提供「恰好一次(exactly-once)」语义。 如果下游 API 支持幂等键(idempotency key),就用它。CellaFlow 的目标是另一件事——让围绕副作用的执行过程本身变得持久且可恢复:一旦所有权丢失,另一个 worker 最终能接手,而不是让工作流永久卡死。这个区分,正是他们想要测量的核心。
幂等键(Idempotency Key) 是解决外部 API 重复调用问题的工业标准做法。调用方在每次请求中携带一个唯一标识符,外部服务端负责检测并去重:如果同一个幂等键的请求已经处理过,直接返回原始结果而不再执行操作。Stripe、Braintree 等主流支付服务均原生支持幂等键。
Exactly-once 语义是分布式系统中最强的交付保证,意味着某个操作恰好被执行一次,既不丢失也不重复。相比之下,At-least-once(至少一次,可能重复)和 At-most-once(至多一次,可能丢失)是更容易实现的弱保证。在涉及不可控外部系统时,exactly-once 通常只能通过「外部系统主动参与两阶段提交(2PC)」或「外部系统提供幂等接口」才能实现,否则调用方无论多精巧都无法单方面保证。这正是作者坦言「没有分布式技巧能对任意外部 API 提供 exactly-once」的根本原因。
多 Agent 带来的协调难题
当系统里不止一个 Agent 时,问题变得更有趣。想象 support agent、billing agent、fraud agent 同时指向工单 12345。它们可能有完全不同的 session、thread ID、执行历史、模型和提示词,但它们都可能独立地得出结论:「这个客户需要退款。」
如果用「每个 Agent 各自的幂等键」,系统会看到三个不同的操作(support/thread-A、billing/thread-B、fraud/thread-C),但业务上这本该是同一件事。
作者提出的观点很有价值:协调的单元不应该是调用者,而应该是「工作」本身。 CellaFlow 的做法是让幂等性作用域绑定到业务标识上:
@tool(
tool_name="issue_refund",
scope=IdempotencyScope.SCOPE_SHARED,
shared_on=["ticket_id"],
)
这样,无论哪个 Agent 发起,只要 ticket_id = 12345,它们就会收敛到同一次执行。Agent 的身份变得次要,工作的身份才是第一位的。
幂等性作用域(Idempotency Scope) 决定了系统用哪些维度来判断「两次调用是否属于同一件事」。最常见的设计是以调用者身份(如 session ID、thread ID)为作用域,这在单 Agent 场景下足够用,但在多 Agent 场景下会导致同一业务操作被系统视为多个不同请求。
将作用域绑定到业务标识(如 ticket_id、order_id)是一种「以工作为中心(work-centric)」的设计哲学,与传统 RPC 框架「以调用者为中心」的思路形成对比。这种设计在事件驱动架构和 Saga 模式中也有类似体现:协调的主体是业务事务本身,而非触发它的某个微服务实例。对于 Multi-Agent 系统而言,这意味着需要在框架层面而非业务代码层面维护一张「正在进行中的工作」注册表,并以业务键作为全局去重的依据。
僵尸 worker 与栅栏机制
还有一个「僵尸(zombie)」问题:worker A 持有某操作 → 卡住 → 租约过期 → worker B 接管 → B 完成了工作 → A 突然又醒了。
A 并不一定是恶意或损坏的,它只是不知道自己已经失去了所有权。如果没有栅栏机制,它可能在 B 接管之后回来写入过期状态,破坏数据一致性。
所以系统需要知道的不仅是「这项工作被认领了吗?」,而是「现在谁拥有它,这个 worker 还被允许行动吗?」这正是所有权纪元(ownership epochs)和栅栏(fencing)要解决的问题。
CellaFlow 想解决的问题空间
作者总结道,模型可以做出正确的决策,难点在于——当进程崩溃、网络超时、worker 消失、重试启动、两个 worker 竞争、所有权变更、旧 worker 醒来、另一个 Agent 独立尝试同一业务操作时,如何确保那个决策依然正确。
所需的原语(primitives)最终会归结为:持久性 + 共享工作标识 + 幂等性 + 租约 + 恢复 + 栅栏。
这套讨论对任何正在构建生产级 Agent、工作流引擎、分布式系统或支付/订单基础设施的人都值得一读。作者本人也坦言,比起 CellaFlow 是否赢得基准测试,他更想听到的是:如果你曾遇到 worker 在执行不可逆副作用之后、记录结果之前崩溃,你当时是怎么处理的? 基准测试与项目均已在 GitHub 开源。
相关推荐

@ai-sdk/zai@3.0.10 发布:依赖更新的补丁版本解析
Vercel AI SDK 发布 @ai-sdk/zai@3.0.10 补丁版本,同步更新 provider、provider-utils 与 openai-compatible 等底层依赖。本文解析该版本变更内容及 AI SDK provider 体系的设计意义。

Vercel AI SDK 更新:@ai-sdk/workflow 2.0.29 修复工具结果保留问题
Vercel AI SDK 发布 @ai-sdk/workflow 2.0.29 补丁版本,核心修复工作流在终止、延迟、暂停三种响应状态下 provider 工具执行结果的保留问题,并同步升级 ai@7.0.98 等核心依赖。

Vercel AI SDK 更新:@ai-sdk/xai 4.0.58 批处理与图像生成改进
Vercel AI SDK 发布 @ai-sdk/xai 4.0.58 版本更新,新增批处理图像生成支持,修复批处理请求类型校验及 DeepSeek 推理流问题,并同步升级 provider 相关依赖。