跳转到主要内容

持久化执行才是长时运行 Agent 的真正基础设施需求

围绕持久化执行的融资与产品动向,指向了 Agent 基础设施中真正困难的问题:对跨越数小时、涉及外部副作用的运行,实现确定性重放与幂等副作用。

Fuel train and refinery tanks in Trzebinia, Poland, showcasing industrial energy infrastructure.

为什么 Agent 运行会打破请求-响应模型

大多数 Agent 框架都围绕请求-响应循环构建:发送 prompt,接收 completion,追加到缓冲区,重复。这个模型适用于短交互。但对于跨越数小时、调用外部系统、等待人工输入、并在被中断前部分完成的运行,它会失效。

长时运行的 Agent 不是一次单独的 inference。它是一个工作流:一系列决策、tool call、等待,以及对写入外部系统。每一步都可能独立失败。进程可能因部署滚动而重启。Provider 可能在调用中途超时。Worker 可能被驱逐。当这些情况发生时,运行会处于不一致状态:一些副作用已经落地,另一些没有,而承载计划的对话缓冲区已经丢失。

没有重放机制,唯一的恢复策略就是从头开始。从头开始会重复每一个已经成功的外部写入。这不是理论上的担忧。这是当今大多数 Agent 技术栈的默认行为,也是团队最终编写临时对账脚本或在崩溃后手动修复状态的原因。

真正重要的两个属性

Agent 的持久化执行建立在两个属性之上。两者在分布式系统中都已得到充分理解。但两者在 Agent 框架中都还不是标准做法。

确定性重放

给定相同的已记录决策和工具结果,运行必须从同一点恢复。这意味着每一个非确定性输入——模型输出、工具响应、时钟读数、随机种子——都被记录在仅追加日志中。重启时,运行时重放日志,重新执行工作流中的确定性部分,并跳过已记录的副作用。运行会从进程死亡时正在执行的那一步继续。

这与对对话做 checkpoint 不是一回事。对话缓冲区是有损摘要。重放日志则精确记录了运行决定了什么、观察到了什么。当某一步依赖于一个已无法再获取的工具结果,或者模型在第七步的输出决定了第八步走哪个分支时,这种差异就很重要。

幂等副作用

每一个外部写入都必须携带一个键,使重复尝试成为 no-op。如果运行调用支付 API,该调用会包含一个由 run ID 和 step ID 派生的 idempotency key。如果 API 两次收到同一个键,它会返回原始结果,而不是再次扣款。如果运行发送邮件,发送操作会被键控,使重试不会产生重复。如果运行写入数据库,该写入是基于 run 和 step 键控的 upsert。

幂等性不是 Agent 的属性。它是 Agent 与其接触的每一个外部系统之间契约的属性。这个契约必须被设计、记录并测试。它无法从 prompt 中推断出来。

Detailed view of a metallic pipe system with valves in an industrial setting.

这会如何改变 Agent 设计

一旦你接受确定性重放和幂等副作用作为需求,若干设计决策就会改变。

工具分类

Tool call 必须被分为三类:

  • 纯函数:无外部副作用,可安全重复。读取文件、计算哈希、格式化字符串。
  • 幂等:有外部副作用,使用相同键可安全重复。Upsert、键控 API 调用、幂等消息发送。
  • 不可重复:有外部副作用,无法安全重复。发送一次性通知、执行交易、在没有软删除路径的情况下删除资源。

第三类在重放时需要人工确认。如果运行在尝试了不可重复副作用之后、但在确认之前死亡,运行时无法自行决定是否重试。它必须暴露这种歧义并等待。这是真实成本:它给恢复增加了一个 human-in-the-loop 步骤,也意味着有些运行没有人工介入就无法收敛。另一种选择——静默重试——更糟。

状态存放在持久化存储中

Agent 状态属于具有显式 schema 的持久化存储,而不是对话缓冲区。schema 应描述运行:其输入、当前步骤、已完成步骤的结果、待处理步骤,以及到目前为止消耗的预算。对话是该状态之上的一个视图,而不是状态本身。

这有一个实际后果:模型可以替换,prompt 可以重写,而运行仍然可以恢复。状态不与模型的上下文窗口耦合。

预算附加到运行上

预算执行附加到持久化运行,而不是进程或会话。恢复后的运行会继续对同一个上限计数。如果运行在崩溃前已消耗其 token 预算的 60%,它会以剩余 40% 恢复。这防止了崩溃-重启循环因每次重启都重置计数器而静默放大成本这一失败模式。

机制很简单:预算是持久化运行记录中的一个字段,在每一步递减,并在尝试该步骤的副作用之前持久化。权衡是预算检查会给每一步增加一次写入,从而增加延

Close-up of a professional audio mixer showcasing control dials and sliders used in sound mixing.

持久化执行改变了 Agent 系统的测试方式。

崩溃注入作为标准测试

最有用的测试是崩溃注入:在每一步杀死 worker,并断言运行收敛到相同的终止状态。这不是单元测试。它是针对运行状态机的属性测试。它能捕获真正重要的 bug:不幂等的步骤、在副作用之前未持久化的状态、偏离原始执行的重放路径。

崩溃注入测试框架需要一种在特定步骤杀死 worker 的方式。这意味着运行时必须将步骤边界暴露为 hook。构建这些 hook 是工作量。跳过它们意味着失败模式只会在生产环境中出现。

重放日志作为评估 fixture

重放日志是真实轨迹的记录:输入、决策、工具结果、副作用。该记录可用作评估 fixture。团队可以针对新模型重新运行轨迹,而无需重新执行副作用。模型的决策会与已记录的决策进行比较,并分析差异。

这是一个重大转变。今天,在 Agent 任务上评估新模型通常意味着端到端重新运行任务,这意味着重新执行副作用,这意味着要么使用沙箱,要么接受成本。重放日志让团队能够将决策质量与副作用执行分离。权衡是重放日志的质量取决于记录质量:如果原始运行以日志未捕获的方式具有非确定性,重放就会因与模型无关的原因而偏离。

先构建什么

顺序很重要。从重放日志开始:决策、工具结果和步骤边界的仅追加记录。然后让每一个外部写入都幂等,使用由 run 和 step 派生的键。然后对工具分类,并为不可重复类添加人工确认。然后把预算附加到运行记录。然后构建崩溃注入测试框架。

每一步都独立有用。仅重放日志就能让调试成为可能。仅幂等写入就能防止重复副作用。完整系统则让长时运行的 Agent 可运维。

Prompt 循环不是困难的部分。困难的部分是让运行能够在启动它的进程消亡后仍然存活的基础设施。

封面照片:panumas nikhomkhai / Pexels。

分享XLinkedIn永久链接6 分钟阅读

相关阅读

这能回答的问题

What is deterministic replay in an agent run?

Deterministic replay means the runtime records every non-deterministic input — model output, tool response, clock reading, random seed — in an append-only log. On restart, it replays the log, re-executes the deterministic parts, and skips recorded side effects, so the run resumes at the same step it was in when the process died.

Why is idempotency necessary for agent tool calls?

Without idempotency, a retry after a crash duplicates every external write that already succeeded. Idempotency keys derived from the run ID and step ID make a repeated attempt a no-op, so the runtime can safely retry without reconciling state manually.

How does durable execution change agent evaluation?

Crash-injection becomes a standard test: kill the worker at each step and assert the run converges to the same terminal state. Replay logs also double as evaluation fixtures, letting teams re-run a trajectory against a new model without re-executing side effects.

What is the trade-off of attaching budget to the durable run?

Budget checks add a write to every step, which increases latency and storage cost. For runs with many small steps, that overhead is measurable. The alternative — losing budget state on crash and silently multiplying cost across restarts — is worse.