Skip to content

Durable Execution:持久化、幂等和恢复没有因为 Agent 改变物理规律 ​

“可持续运行”“自动恢复”“持久 Agent”听起来像模型获得了更强的生命力。落到故障助手,问题依然具体:进程退出前写了什么,远端工单是否已经创建,恢复后拿哪一份草稿继续。

在所有 Agent 概念中,这一组最适合直接用后端语言解释。但即便它采用成熟技术,也不能把几个组件的名字相加,得出业务恰好执行一次的结论。

一个工单,跨越两个事实来源 ​

助手被授权创建工单,发生如下时序:

text
本地保存操作意图 op-42
    → 请求远端创建工单
    → 远端写入 ticket-9
    → 响应丢失
    → 本地超时

本地事实是“没有收到响应”,远端事实是“工单存在”。把本地超时写成业务失败,再让模型换一个调用 ID 重试,可能创建第二张工单。

这个问题在没有语言模型的系统里也存在。操作身份、幂等、唯一约束和对账仍然是主要工具。Agent 增加的变数在于,它可能自行重述请求,把同一次业务意图误当成一个新动作。

因此稳定操作 ID 应由应用管理,并绑定租户、目标及内容指纹。传输重试继续使用原 ID;真正修改工单内容,需要明确是更新原操作、报告冲突还是创建新意图,不能靠模型临时猜测。

检查点保存到哪里,保证就到哪里 ​

将任务状态保存在数据库,可以帮助恢复本地进度;将模型响应保存成产物,可以避免重新生成;保存工具结果引用,可以帮助继续下游步骤。这些保证都值得拥有,但它们的覆盖范围不同。

如果框架只在节点之间保存检查点,一个节点先调用远端,再保存本地结果,节点中途崩溃仍可能导致重做。恢复点位于节点边界还是细粒度任务边界,需要读具体实现。

LangGraph 的检查点文档介绍图状态保存与恢复能力。使用这类机制时,业务代码仍要核对自己把副作用放在哪里,不能从框架支持持久化推导任意外部调用都不会重复。

Outbox 能解决哪一段,不能解决哪一段 ​

可以在同一个本地数据库事务里同时保存任务更新和待发送操作,后台 dispatcher 读取并发送。这样避免“任务说要创建工单,进程却在发送前退出而永久漏发”的部分问题。

但 dispatcher 发送成功后、标记已发送前崩溃,仍会再次投递。Outbox 使本地意图与发送安排保持一致,不自动保证远端效果只发生一次。要么下游按操作 ID 去重,要么可以按业务键查询并对账,要么承认结果未知需要人工处理。

本地数据库事务、消息投递和远端业务操作之间的边界必须画出来。把它们都叫“持久执行”,容易让一个准确的局部保证变成不准确的全局承诺。

模型输出使重放多了一种变化来源 ​

用户确认草稿 R1 后进程退出。恢复器重新调用模型,得到 R2,再沿用旧确认执行。即使最终只创建一张工单,内容也可能已经不是用户确认的内容。

所以,非确定输出应作为可以版本化、校验和引用的产物处理。恢复时可复用已记录的模型结果;如果必须重新生成,就按新产物重新检查依赖和确认。不能把“同一个 prompt”视为“同一份结果”的证明。

温度设置也不能代替这种设计。即使输出偶然完全一致,系统仍需要证据说明提交了哪份产物,以及它对应哪个任务版本。

取消与多个恢复者仍是分布式问题 ​

用户点击取消时,远端请求可能已经在执行。正确状态可能是“停止后续调查,工单创建结果待对账”,而不是立刻宣称所有影响都已撤销。

两个 worker 同时恢复任务,则需要版本条件提交、租约或相应的所有权机制。租约过期不证明旧 worker 已经停止;若必须阻止它继续外部写入,需要执行端识别过期身份或隔离令牌。仅锁住本地状态不能隔空约束所有远端服务。

这些机制各自处理一个故障窗口。用一个“可靠性”开关替代它们,会让问题难以排查。

一个可检验的能力声明应该怎样写 ​

比起“支持自动恢复”,更有用的声明是:本地任务与产物跨重启保存;支持幂等的模拟工单服务在指定故障点重试后只保留一个业务结果;无查询能力的外部动作停在待对账状态。

然后逐项演练:发送前退出、服务端成功后丢响应、两个恢复者竞争、草稿在确认后变化。分别核对本地状态、远端记录和产物版本,不能只观察最后一句回复。

本篇判断:持久执行的主要基础是成熟的事务、消息与恢复工程。模型带来的非确定产物与自主重试让边界更容易被忽略,因此需要更严格地管理业务意图,而非假定框架消除了故障窗口。

返回去魅总览 · 下一篇:Guardrails 与人工确认