Agent Durable Execution 生产实战:用 Checkpoint、幂等副作用与 Replay Contract 防止长任务重复执行
背景:Agent 最危险的故障不是「失败」,而是「恢复后重复成功」
单次聊天失败通常容易处理:返回错误,用户重试即可。但生产级 Agent 往往是一个持续数分钟甚至数小时的状态机:
- 调用 LLM 规划任务;
- 查询数据库;
- 创建工单;
- 等待人工审批;
- 调用支付或订单接口;
- 发送邮件;
- 汇总结果。
如果 Worker 在第 5 步之后崩溃,系统必须知道:前四步哪些已经完成,第五步是否已经真正落到外部系统,第六步是否应该继续。
这时只保存 conversation history 不够。Durable Execution 的目标不是「记住更多上下文」,而是让执行进度、非确定性结果和外部副作用之间形成可恢复的一致性边界。
LangGraph、Temporal 和 Microsoft Agent Framework 的具体 API 不同,但它们都暴露出三个必须单独治理的问题:
- Checkpoint Boundary:从哪里恢复;
- Side-effect Boundary:哪些外部动作可能重复;
- Replay Contract:恢复时哪些代码可以重新执行,哪些结果必须复用历史记录。
理解这三个边界,比简单地「给 Agent 加一个 Redis 状态表」更重要。
核心原理一:Checkpoint 保存的是执行状态,不是 exactly-once 保证
Checkpoint 很容易给人一种错觉:既然状态保存了,恢复后就不会重复执行。实际上,Checkpoint 只能回答「恢复时系统知道什么」,不能自动回答「外部世界已经发生了什么」。
LangGraph 的检查点在执行边界保存图状态,官方文档明确提醒:节点在中断或恢复后可能从头重新执行,因此中断前发生的副作用必须具备幂等性,或者被拆到独立 Task/Node 中。Microsoft Agent Framework 同样在 superstep 结束后创建 Checkpoint,并保存 executor state、pending messages、pending requests/responses 和 shared state,可以恢复工作流,但并不意味着任意外部写操作天然拥有 exactly-once 语义。
因此生产设计中,应把状态分为两类:
1. 可重放状态(适合进入 Checkpoint)
- 当前步骤;
- Planner 输出;
- 已获得的工具结果;
- 待审批请求;
- 中间推理结果;
- 下一步路由。
2. 外部副作用(必须设计自己的幂等协议)
- 扣款;
- 创建订单;
- 发券;
- 发短信;
- 创建工单;
- 写第三方 CRM;
- 调用不可查询状态的遗留系统。
这些动作不能仅依赖 Checkpoint 保证正确性。
核心原理二:Side Effect 必须拥有稳定的逻辑身份
最实用的方法不是问「这个函数执行过没有」,而是给每一次业务动作一个稳定的 logical operation id。
import hashlib
def operation_id(run_id: str, step: str, business_key: str) -> str:
raw = f"{run_id}:{step}:{business_key}"
return hashlib.sha256(raw.encode()).hexdigest()[:32]
假设 Agent 要为订单 ORD-20260902-001 创建退款申请,可以生成:
operation_id = operation_id(run_id, "create_refund", order_id)
然后将这个 ID 同时用于:
- 下游 API 的
Idempotency-Key; - 本地 side-effect ledger 的唯一键;
- Outbox 表唯一约束;
- 日志与 Trace 关联字段。
这样即使 Worker 崩溃后重新进入同一步骤,系统也不会把「同一个逻辑动作」误认为一项全新的业务请求。
推荐的 Side-effect Ledger:
CREATE TABLE agent_side_effect (
operation_id VARCHAR(64) PRIMARY KEY,
run_id VARCHAR(64) NOT NULL,
action_name VARCHAR(128) NOT NULL,
business_key VARCHAR(128) NOT NULL,
request_hash VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
external_ref VARCHAR(128),
result_json TEXT,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
执行逻辑不要写成:
result = external_api.create_order(payload)
checkpoint["order_id"] = result["id"]
更稳妥的是:
op_id = operation_id(run_id, "create_order", business_key)
cached = ledger.get(op_id)
if cached and cached.status == "SUCCEEDED":
return cached.result
ledger.mark_started(op_id, request_hash(payload))
result = external_api.create_order(
payload,
idempotency_key=op_id,
)
ledger.mark_succeeded(op_id, result)
return result
这里的关键不是数据库表本身,而是恢复之后仍然能计算出同一个 operation_id。
核心原理三:LLM 与外部 I/O 不应该直接进入可回放控制逻辑
Temporal 对这一点的约束最明确:Workflow 代码需要保持确定性,而 LLM 调用、工具调用、数据库访问和外部 API 都属于非确定性 I/O,应放进 Activity。
原因并不复杂。假设恢复时重新执行:
decision = llm.invoke(prompt)
第一次模型输出 call_tool("refund");第二次由于模型采样、模型版本或服务端行为变化,可能输出 call_tool("cancel_order")。此时系统已经不是「恢复」,而是在历史中间重新生成了一条新的未来。
Durable Runtime 更合理的语义是:
Workflow / Orchestrator
|
+-- schedule LLM Activity
| |
| +-- historical result recorded
|
+-- replay uses recorded result
恢复时复用已经完成的非确定性结果,而不是重新询问模型。LangGraph 的 Functional API 也采用相似思路:Task 和 subgraph 的结果可以持久化,恢复时已完成的 Task 不必重新计算;同时文档明确要求副作用函数保持幂等。
Replay Contract:代码升级也可能破坏恢复
Durable Execution 还有一个经常被忽略的问题:一个运行中的 Agent 可能跨越应用版本升级。
例如某个任务周一启动:
planner -> lookup -> approval -> payment -> notify
周二发布新代码,变成:
planner -> lookup -> risk_check -> approval -> payment -> notify
周三一个旧任务从 approval Checkpoint 恢复。此时新的代码拓扑、节点身份、Task 顺序和历史 Checkpoint 是否兼容?这就是 Replay Contract。
Microsoft Agent Framework 的 Checkpoint 文档明确指出,rehydration 时需要保留工作流 topology 和 executor identity。LangGraph 则提醒,当节点内部 Task 或 interrupt 的顺序发生改变时,恢复可能无法正确匹配已经保存的结果。
因此,长期运行 Agent 不应该只给 Prompt 和 Tool Schema 做版本管理,还需要给 workflow definition 做版本治理。建议至少记录:
run_id: agt_20260902_001
workflow_name: claims-agent
workflow_version: 7
checkpoint_schema_version: 3
tool_catalog_version: 18
prompt_release: prod-2026-09-02
model_policy_version: 11
恢复时先做兼容性判断,而不是直接拿最新代码强行继续。
工程落地:把一个 Agent Step 拆成四个阶段
生产中可以把每个具有副作用的步骤拆成四段:
Prepare -> Execute -> Commit -> Checkpoint
- Prepare:计算 operation_id、请求参数、request hash、当前业务前置条件。此阶段不产生不可逆副作用。
- Execute:真正调用外部系统。如果下游支持 idempotency key,必须传递 operation_id;如果下游不支持,就需要通过本地 ledger + 查询接口 + 唯一业务键尽量构造幂等语义。
- Commit:记录 external reference、执行结果、committed timestamp、response hash。此时本地系统已经知道外部动作完成。
- Checkpoint:最后再推进 Workflow 状态。
这种顺序无法消灭所有分布式系统中的不确定窗口,但能大幅缩小「外部已经成功、本地完全不知道」的范围。
最棘手的窗口:外部成功,本地 Commit 前崩溃
这是所有 Durable Agent 都绕不开的经典问题:
Agent -> Payment API: charge()
Payment API -> success
Agent process crashes
Ledger.mark_succeeded() 尚未执行
恢复后,本地看到的仍是 STARTED。此时正确策略不是盲目重试,而是进入 reconciliation:
- 如果下游支持 idempotency key,使用同一 key 重试;
- 如果下游支持查询,按 business key / external request id 查询;
- 如果无法确认,标记为
UNKNOWN; - 高风险业务进入人工补偿,而不是让 Agent 猜。
推荐状态机:
PENDING
|
v
STARTED
├──→ SUCCEEDED
├──→ FAILED_RETRYABLE
├──→ FAILED_FINAL
└──→ UNKNOWN
其中 UNKNOWN 是非常重要的状态。支付、理赔、订单、发券等业务宁可进入人工核对,也不要把「不知道是否成功」错误地压缩成「失败」。
Retry Policy:不要让每一层都自己重试
Agent 系统很容易出现重试风暴:
HTTP Client retry 3 次 × Tool SDK retry 3 次 × Agent node retry 3 次 × Workflow retry 3 次 = 最坏 81 次调用
生产环境应该确定唯一的 retry owner:
| 层次 | 职责 |
|---|---|
| HTTP SDK | 关闭自动重试 |
| Tool Adapter | 只做错误分类 |
| Durable Activity | 负责网络类重试 |
| Workflow | 负责业务级补偿和状态推进 |
还需要区分错误类型:
Retryable
- 429;
- 短暂 5xx;
- 网络超时;
- 临时依赖不可用。
Non-retryable
- 参数非法;
- 权限不足;
- 内容策略拒绝;
- 明确业务拒绝;
- Schema 不兼容。
这不仅降低故障放大,也使历史记录更容易解释。
Human-in-the-loop:暂停越久,版本漂移越值得警惕
人工审批是 Durable Execution 最典型的使用场景之一。一个 Agent 可以今天发出审批请求,下周才收到回复。Microsoft Agent Framework 的 Checkpoint 能保存 pending request,并在恢复时重新发出相应事件。
但生产系统还应额外保存:
- 审批请求对应的 workflow version;
- 审批时看到的业务快照;
- 审批对象 hash;
- 过期时间;
- 恢复前是否需要重新校验业务条件。
例如,一周前批准的是 500 元退款,但恢复时订单已经被人工处理。Agent 不应该仅凭旧 Checkpoint 继续执行。因此,Checkpoint 保证执行连续性,业务前置条件校验保证语义仍然有效。
上线监控:不要只监控 Agent 成功率
Durable Agent 至少需要下面几类指标:
恢复指标
workflow_resume_totalcheckpoint_restore_failure_totalresume_latencycheckpoint_age
重放指标
replayed_step_totaltask_result_reused_totalworkflow_version_mismatch_total
副作用指标
side_effect_duplicate_prevented_totalidempotency_key_hit_totalside_effect_unknown_totalreconciliation_total
重试指标
activity_retry_totalretry_exhausted_totalnon_retryable_failure_total
其中最值得告警的不是普通 retry,而是:
side_effect_unknown_total > 0
因为它意味着系统已经进入「无法确认外部副作用是否完成」的灰区。
适用场景
Durable Execution 特别适合:
- 跨多个外部系统的业务 Agent;
- 需要人工审批的 Agent;
- 数分钟到数天的长任务;
- 多 Agent 协作工作流;
- 支付、订单、工单、通知等带副作用场景;
- Worker 需要弹性伸缩、允许随时重启的运行环境。
如果 Agent 只是一次请求内完成的问答,没有外部写操作,也不需要暂停恢复,引入完整 Durable Runtime 可能反而增加复杂度。
常见误区
误区一:有 Checkpoint 就是 exactly-once。 不是。Checkpoint 解决恢复状态,幂等解决重复副作用,它们是两件事。
误区二:恢复就是从最后一行代码继续。 多数框架的恢复语义更接近「从一个稳定边界重新执行,并复用已持久化结果」,而不是进程级指令指针恢复。
误区三:所有 API 都可以简单重试。 对于支付、发券、创建订单等副作用接口,如果没有幂等语义,自动重试本身就是风险源。
误区四:只有 Agent State 需要版本化。 长期任务还需要考虑 Workflow Definition、Tool Contract、Prompt Release 和 Checkpoint Schema 的版本兼容。
误区五:Agent Memory 等于 Durable Execution。 Memory 解决「Agent 记得什么」;Durable Execution 解决「Agent 做到哪一步、哪些动作已经发生、失败后如何继续」。两者职责不同。
上线检查
在生产发布前,建议至少验证:
- 每个不可逆 Side Effect 是否都有稳定 operation_id;
- 下游是否支持 idempotency key;
- 不支持幂等的系统是否有 reconciliation 路径;
- Checkpoint 恢复是否可能重新进入某个 Node;
- LLM/Tool/API 是否被隔离到可持久化结果的 Task/Activity;
- Retry owner 是否唯一;
- 是否区分 retryable 与 non-retryable;
- UNKNOWN 状态是否有人工处理流程;
- Workflow 升级后旧 Checkpoint 是否仍可恢复;
- Human-in-the-loop 恢复前是否重新校验业务前置条件;
- 故障演练是否覆盖「外部成功、本地 Commit 前崩溃」。
FAQ
Q:Checkpoint 是否就意味着 Agent 获得了 exactly-once 执行语义?
不是。Checkpoint 能保存执行进度,但外部系统的副作用可能发生在两个 Checkpoint 之间。要防止重复动作,仍需要 idempotency key、唯一约束、side-effect ledger 或 reconciliation。
Q:为什么要强调 Replay Contract?
因为一个运行中的 Agent 可能跨越代码发布。恢复时,如果节点身份、Task 顺序、Workflow topology 或 Checkpoint Schema 已改变,历史状态未必能被新版本安全解释。Replay Contract 就是在明确什么变化兼容、什么变化必须启动新执行。
Q:Durable Execution 会不会让所有 Agent 都变复杂?
会增加基础设施和状态治理成本,所以不应该滥用。短请求、只读工具、无副作用场景可以保持简单;长任务、人工暂停和高价值副作用场景才最值得引入。
参考资料
- LangGraph Functional API — Durable execution & idempotency:https://docs.langchain.com/oss/python/langgraph/functional-api
- LangGraph Graph API — Re-execution and idempotency:https://docs.langchain.com/oss/python/langgraph/graph-api
- LangGraph Interrupts — Side effects before interrupt must be idempotent:https://docs.langchain.com/oss/python/langgraph/interrupts
- Temporal AI Agent Reference Architecture — Workflows orchestrate, Activities execute:https://go.temporal.io/platform-hub/ai-engineering/ai-reference-architecture
- Temporal AI Engineering Patterns:https://go.temporal.io/platform-hub/ai-engineering/ai-patterns
- Microsoft Agent Framework Workflows — Checkpoints:https://learn.microsoft.com/en-us/agent-framework/workflows/checkpoints
- Microsoft Agent Framework — Durable Extension:https://learn.microsoft.com/en-us/agent-framework/integrations/durable-extension