Agent Systems

持久化执行:让长任务真正“断点续跑”

Mason08 MIN

一个 Agent 可能运行数小时,等待人工审批数天,也可能在调用外部工具后瞬间崩溃。如何让它从正确的位置继续,而不是从头来过或重复执行?这篇文章从状态、检查点和确定性重放讲起,最后处理最棘手的副作用崩溃窗口。

先说结论

理解持久化执行,得先分清两样东西:运行状态(state)和长期记忆(memory)。运行状态是这一次任务的现场——执行到哪里、哪些工具已经调了、当前在等什么。长期记忆则是跨任务仍然有价值的知识,比如用户偏好、历史经验和长期事实。两者都要保存,但解决的是完全不同的问题。

所以,持久化执行不是把聊天记录存进数据库就完事了。真正要保存的是已完成的步骤、模型当时的决定、工具返回的结果和等待条件。系统在故障后通过事件重放恢复这些信息,再从中断处接上后续工作。模型调用和外部工具调用天生不确定,恢复时应复用已有结果,而不是重新调模型或让工具再跑一次。

工具调用通常是最该设检查点的地方,但检查点不能自动解决所有问题。比如一笔转账已经在外部系统成功了,Agent 却在记录结果前崩掉;简单重试就可能造成重复扣款。持久化解决的是“已经完成的工作不丢失”,不等于高可用、低延迟或永不宕机。要可靠地处理外部副作用,还得靠幂等性和更谨慎的恢复协议。

问题从哪里开始

一个会调用工具的 Agent,通常都在重复下面这个循环:

读取当前状态 → 模型决定下一步 → 调用工具 → 接收结果 → 更新状态 → 继续

在演示环境里,这些变量往往只活在一个 Python 或 Node.js 进程中。任务几秒钟就完事时看不出问题;一旦碰上容器重启、Serverless 请求超时、部署替换实例,或者等三天的人工审批,内存里的进度就没了。Agent 明明已经正确做完前半段,却只能从头来过,甚至没法知道前面是否已经做过关键动作。

持久化执行(durable execution)在模型与计算资源之间增加了一层运行时:它将关键步骤的输入、输出和完成状态写入外部存储。这样一来,计算进程可以随时消失,任务的身份和进度却不会跟着丢。下一个进程接手时,不需要猜测任务此前发生过什么,只需读取这份执行记录,再继续完成尚未结束的部分。

数据类型保存什么主要用途
run state计划、步骤位置、结果、待审批动作恢复本次任务
memory用户偏好、长期事实、历史经验跨任务复用
trace执行过程与诊断信号观察和调试
stream cache已发送给前端的文本片段刷新与断线续传

MongoDB 的工程文章把这条边界说得很清楚:Agent 回答错误,可能是上下文或模型的问题;已经完成五步却无法从第五步继续,则是状态持久化的问题。阅读 MongoDB 的状态与持久化分析

Mastra 的实现还提醒我们:浏览器刷新后的流续传,与后端崩溃后的执行恢复,是两种不同的保证。阅读 Mastra Durable Agents

如何实现恢复

事件日志与确定性重放

成熟的运行时不会每行代码都存一次内存快照,而是记下一串足以恢复任务的事件:

run_started
llm_step_completed(result=A)
tool_call_prepared(operation_id=42)
tool_call_completed(result=B)
approval_waiting
approval_received

恢复时,工作流代码可能会从头解释,但完成过的步骤会直接返回事件日志中的结果。因此,“代码从头重放”不等于“业务动作从头再做”。对开发者而言,执行函数仍然像普通代码一样从上往下书写;对运行时而言,每个已记录的步骤都是可以跳过的历史。这种分层让普通的控制流代码,也能获得接近工作流引擎的恢复能力。

RULE

LLM 调用、工具调用、数据库读取和外部 API 都不应直接混入需要确定性重放的逻辑。将它们封装为 Activity 或 Step:首次执行时记录结果,恢复时直接读取结果。

否则,同一个 checkpoint 恢复后,模型可能生成不同的参数、工具顺序或幂等键,让执行路径发生语义分叉。Inngest Durable Agents 文档

检查点粒度

检查点的划分没有唯一答案。设置得太粗,恢复时可能需要重做昂贵操作;设置得太细,又会带来更多持久化写入、延迟和协调成本。实践中可以把连续的纯计算和小型只读操作合到一步里,重做一次也花不了多少。

相反,LLM 调用通常应该单独记录,避免恢复时重复消耗 token,也避免拿到不同的回答。任何会改变外部世界的工具调用,例如发送邮件、创建工单或写入数据,同样应是独立步骤。子 Agent 委派也值得单独保存,否则恢复时可能重复创建 worker。人工审批则应该在进入等待之前就把完整的待审批状态存好,然后放掉计算资源;等审批到了,系统再从存好的位置接着跑。

版本与凭据也是恢复状态的一部分

Agent 挂起期间,代码、提示词、模型和工具 schema 都可能发生变化。所以运行记录至少要包含:

workflow_version
state_schema_version
prompt_version
model_config_version
tool_schema_version

长任务最好把代码、提示词和模型配置锁成一个运行 bundle。需要升级时,走显式的状态迁移。凭据不要跟着 checkpoint 长期保存;恢复时重新授权,签发短期凭据。

关键模式:Side-effect Sandwich

上面这些机制可以保存进度,但外部副作用还藏着一个危险窗口:外部系统已经执行成功,但 Agent 还没来得及保存“成功”的结果就崩溃了。比如 CRM 已经创建了客户,支付系统已经扣了款,部署平台已经开始发布;但从 Agent 的记录看,这一步却仍然“未完成”。如果恢复逻辑只会盲目重试,结果就是重复操作。

  1. 01 / PREPARE

    持久化规范化请求、operation_id 和稳定幂等键。

  2. 02 / EXECUTE

    使用同一个 operation_id 调用外部工具。

  3. 03 / COMMIT

    持久化返回值、外部资源 ID 和完成状态。

在调用前写入的 PREPARED 记录至少应包含:

{
  "operation_id": "run-83:create-customer:4",
  "tool": "crm.create_customer",
  "canonical_request": {
    "email": "user@example.com"
  },
  "idempotency_key": "run-83:create-customer:4",
  "status": "PREPARED"
}

恢复时不能把“没有 COMMITTED”直接理解为“尚未执行”。如果记录已经是 COMMITTED,系统直接重放保存的结果即可。如果它仍然停留在 PREPARED,则应先凭 operation_id 或业务唯一键向外部系统查询。

如果动作实际已经发生,就补写 COMMITTED,而不是再次执行;只有确认动作尚未发生时,才使用完全相同的请求和幂等键重试。对于无法查询、工具又不支持幂等性且动作不可逆的场景,自动恢复并不安全,应将任务转入人工复核。这就是 Side-effect Sandwich 的核心:先记录意图,再执行副作用,最后提交结果。

这个模式适用于支付、邮件发送、工单创建、代码合并、部署、文件删除和外部 Agent 委派。最常见的错误,是在恢复时让模型重新生成请求和幂等键;这样看似恢复了流程,实际上已经把一次确定的操作变成了另一次全新的尝试。

BOUNDARY

外部服务既不支持幂等键,也无法按业务标识查询结果时,应用侧很难凭空提供 exactly-once 语义。此时需要事务性 outbox、可信执行代理、补偿动作或人工确认。

延伸阅读

  1. State & Persistence: The Problem of Agent ReliabilityMongoDB · 2026.07.06

    系统梳理 state、memory、checkpoint、版本固定与恢复成本,适合作为入门主线。

  2. Introducing Durable AgentsMastra · 2026.06.26

    借助框架实现,理解流续传、后台执行和客户端观察的区别。

  3. Experiments: safely test code changesInngest · 2026.06.23

    解释为什么运行中的实验分支同样需要被持久化。

  4. Runs list and logs degradedTrigger.dev · 2026.06.30

    一个执行平面、状态真源与分析副本相互隔离的真实案例。

  5. Incident report on June 22, 2026Trigger.dev · 2026.06.24

    说明“可恢复”“可用”与“及时完成”是三项不同的能力。