跳转到内容

Durable Execution:长任务为什么不能只靠 while 循环

一个 Agent 能连续运行十分钟,不代表它能可靠运行十小时。

进程会升级,机器会重启,网络会超时,第三方 API 会限流,人工审批可能第二天才回来。只要任务跨越故障和等待,普通 while 循环就不再足够。

Durable Execution 解决的不是“怎样让模型更坚持”,而是:当执行环境中断以后,系统怎样从已经发生的事实继续,而且不重复造成副作用。

这是一类分布式系统问题。

假设退款流程执行两步:

  1. 调用支付系统退款;
  2. 把任务状态写成完成。

如果第一步已经成功,进程却在第二步之前崩溃,重启后会看到“任务未完成”。它如果直接重试,可能重复退款。

反过来,如果先把任务写成完成,再调用支付系统,第二步失败时系统又会错误宣称成功。

这就是副作用与状态提交之间的故障窗口。

任何会付款、发信、建单、删文件、提交代码的 Agent 都会遇到它。

描述长期业务过程和状态转移。Workflow 代码应尽量确定,方便重放历史。

真正访问外部世界的操作,例如调用模型、执行工具、发邮件。Activity 可以失败和重试。

记录已发生的事件、结果、定时器和信号。恢复时,运行时依据历史重建 Workflow 状态。

保存任务当前可恢复位置和必要状态。不同框架可能采用快照、事件历史或数据库事务。

外部事件和人工审批可以在未来到达,唤醒暂停的 Workflow。

明确哪些错误可重试、间隔、最大次数和超时。重试不是统一的“再来一次”。

三、“Exactly Once”为什么要谨慎

Section titled “三、“Exactly Once”为什么要谨慎”

跨网络和外部系统时,很难简单承诺每个副作用恰好发生一次。

更常见的工程方法是:

  • Workflow 状态可靠保存;
  • Activity 可能至少执行一次;
  • 有副作用工具使用 Idempotency Key;
  • 超时后查询外部终态;
  • 重复请求被外部系统去重;
  • 无法去重的动作需要补偿或人工处理。
Retry safely, not blindly.
能安全重试,才允许自动重试。

Agent 提供的自然语言总结不能填补故障窗口。幂等和状态查询必须写进工具契约。

Temporal 将长期过程写成 Workflow,把外部副作用放进 Activity。平台持久化事件历史,Worker 崩溃后可以重新执行 Workflow 逻辑并恢复到一致状态。[1]

Workflow 重放要求确定性:同一历史应得到同样控制结果。因此模型调用、随机数、当前时间和网络访问通常不能随意写在 Workflow 逻辑里,而应封装为 Activity 或受控 API。

这与 Agent 结合时很自然:

Durable Workflow:决定生命周期、等待、重试和补偿
Agent Activity:执行一次模型决策或开放任务
Tool Activity:执行外部副作用

模型可以非确定,Workflow 的恢复语义仍然明确。

DBOS 使用数据库支持 Durable Workflow 与事务式执行,强调把持久化和应用状态放在熟悉的数据基础设施中。[2]

它与 Temporal 的实现路线不同,但解决的核心问题相近:任务不能因为进程故障丢失,已经完成的步骤需要被记住。

选择时要看团队基础设施、语言、运维和事务需求,而不是只看 API 是否简短。

LangGraph 提供 Checkpoint、Interrupt、Time Travel 等能力,使图运行可以暂停、恢复和检查。[3]

PydanticAI 支持与 Temporal、DBOS、Prefect 等 Durable Execution 方案结合。[4]

Google ADK、Microsoft Agent Framework 也提供 Session、Workflow 和状态能力。框架内持久化适合保存 Agent 状态,外部 Durable Runtime 更擅长跨服务、定时器和长期业务事务。

两者可能同时使用。关键是避免双重权威:谁拥有 Workflow 生命周期,谁只是保存 Agent 节点状态,必须写清楚。

七、人工审批是长任务,不是弹窗装饰

Section titled “七、人工审批是长任务,不是弹窗装饰”

很多 Agent Demo 显示一个“Approve”按钮,却没有处理:

  • 用户隔夜才点击;
  • 审批前资源状态已变化;
  • 同一动作被批准两次;
  • 审批者身份和范围;
  • 用户拒绝后怎样补偿;
  • 请求超时后怎样结束。

真正的 Human-in-the-loop 需要一个持久化等待状态。恢复时还要重新校验环境,而不是执行昨天生成的旧参数。

approval grants permission;
it does not freeze the world.

批准给予权限,不会让外部世界停止变化。

一份可恢复状态通常包含:

{
"run_id": "run_demo",
"workflow_version": 3,
"current_step": "awaiting_approval",
"goal": "refund order_demo",
"artifacts": ["policy-check.json"],
"pending_action": {
"tool": "refund",
"arguments_hash": "...",
"idempotency_key": "refund-order_demo"
},
"budget": {"attempts_remaining": 2},
"last_evidence_version": "order-state-18"
}

不要把完整 Secret 和无关上下文写入 Checkpoint。恢复需要的是事实、引用和安全凭据句柄。

长任务运行期间,代码可能从 v1 升到 v2。新代码删除了旧节点,正在等待的任务该从哪里继续?

Durable 系统需要版本兼容策略:

  • 保留旧 Workflow 代码直到旧 Run 完成;
  • 在历史中记录版本分支;
  • 对状态执行显式迁移;
  • 新旧 Worker 分流;
  • 无法迁移时暂停并人工处理。

Agent Prompt、Tool Schema 和验证规则同样属于版本。只迁移状态结构,不记录行为版本,仍然无法解释恢复后的差异。

旧稿的 CI Sweeper 是一个很好的长任务案例:CI 失败触发,Agent 在隔离 Worktree 中修复,Verifier 跑测试,随后等待人工审查。[5]

CI Sweeper starter

图 1:CI Sweeper starter 把触发、状态和适配文件外置。来源见文末。

CI Sweeper GitHub Actions 示例

图 2:CI 事件负责交付事实,不应直接把“失败”解释成“必须自动修复”。来源见文末。

这个 Loop 可能跨多个 CI Run 和人工审查。若状态只存在聊天里,重复触发、证据过期和并发修复很难避免。

十一、真实实验:副作用以后崩溃,恢复时不重复

Section titled “十一、真实实验:副作用以后崩溃,恢复时不重复”

本讲的 durable_checkpoint.py 模拟最危险的窗口。

第一次运行:

  1. 写入 Checkpoint;
  2. 执行 refund-order-demo 副作用;
  3. 在完成状态提交前模拟进程崩溃。

恢复以后,脚本再次请求同一动作。因为使用相同 Idempotency Key,结果为 deduplicated,副作用列表仍然只有一项。

{
"side_effects": ["refund-order-demo"],
"events": [
{"phase": "first_run", "effect": "created"},
{"phase": "crash"},
{"phase": "resume", "effect": "deduplicated"}
]
}

完整结果与 Checkpoint 见 experiment-result.json

实验是文件级模拟,不具备真实数据库事务,却清楚展示了 Durable Agent 的基本原则:恢复不是重做全部步骤,而是根据历史安全地继续。

  • 进程崩溃后任务会不会丢;
  • 外部副作用是否幂等;
  • 超时后能否查询终态;
  • Checkpoint 是否包含版本;
  • 人工审批能否隔夜恢复;
  • 取消和补偿是否明确;
  • 重试是否按错误分类;
  • 同一目标是否有锁或租约;
  • Workflow 升级是否兼容在途任务;
  • Trace 是否跨越多次 Worker 执行。

长时 Agent 的可靠性,来自持久化历史、幂等副作用、可恢复状态和明确的版本,不来自模型“记得继续”。

while True 可以表达循环,不能替系统承担故障。

下一讲进入最具行动感、也最危险的环境:浏览器、终端和桌面。Computer Use 与 Coding Agent 为什么必须和沙箱、权限及终态验证一起设计?


[1] Temporal, Durable Execution. https://docs.temporal.io/

[2] DBOS, Durable Workflows. https://docs.dbos.dev/

[3] LangChain, LangGraph Persistence. https://docs.langchain.com/oss/python/langgraph/persistence

[4] PydanticAI, Durable Execution. https://pydantic.dev/docs/ai/integrations/durable_execution/overview/

[5] cobusgreyling, CI Sweeper. https://github.com/cobusgreyling/loop-engineering/tree/main/starters/ci-sweeper