两种都叫「状态」的东西
Agent 运行时中的检查点通常记录一次运行的机械过程:图里的哪个节点执行了、调用了哪个工具、工具返回了什么、哪条边还在等待。这是执行状态。它足以回答「机器走到哪儿了」这个问题。
它不足以回答「机器想做什么,依据什么条件去做」。后者是意图状态:当前所理解的目标、这次运行接受的约束、已经承诺的假设,以及尚未履行的义务。执行状态是一个游标。意图状态是一份契约。
持久执行引擎擅长处理游标。它们重放一份步骤日志,跳过已完成的工作,在第一个未完成的步骤处交还控制权。对于一个含义在定义时就已固定的工作流来说,这是正确的行为。Agent 不是这样。它的含义在每一步都根据它能看到的 history 重新构建,而 history 并不等同于产生它的意图。只恢复游标而不恢复契约,运行不会崩溃。它会流畅地继续下去,朝着错误的方向,带着中断前那种同样自信的语气。
基于过时目标重新规划
一次运行在早期接受了一条约束:对生产数据集只做只读操作,或者把范围限制在单个租户内。运行中途,操作者扩大了范围,约束在编排器的配置中被更新。进程在下一步之前死掉了。恢复时,检查点里只有步骤索引和工具输出,没有当时生效的那条约束。Agent 从残留的 history 和当前配置中重新推导计划,而两者并不一致。transcript 里没有任何标记指出这道接缝。由此产生的计划内部自洽,对外却是错的。
重复的副作用
经典情形:最后一次工具调用成功了,但确认信息丢失了。一个在第一个未完成步骤处恢复的持久执行引擎会重新发起这次调用。如果这次调用是 charge_card、send_email 或 create_ticket,这次运行现在就有了两次。这不是推理失败,而是检查点契约失败。日志记录的是调用的意图,而不是效果的事实,而只有当效果本身携带一个下游系统能识别的键时,这两者才能被区分开。
假设漂移

给定完全相同的历史,Agent 并不总会重新推导出完全相同的计划。采样、上下文截断和模型版本变化都会让分布发生偏移。一次重放历史并要求模型继续的恢复,会产生一个在给定历史下看似合理的计划,而不是当时正在进行中的那个计划。假设漂移是过时目标失效的安静版本:配置没有变,变的只是重建过程。运行看起来健康,却在偏离。
一份可用的检查点契约
解决办法是把检查点当作意图的序列化,执行状态只是其中若干字段之一。一份能被安全恢复的检查点应当原子地、一并携带:
- 目标,以这次运行当前所承诺的形式,而不是它启动时的形式。
- 已接受的约束,每一条都附带它在运行中被接受的时间点,以及断言它的来源。
- 工具结果,以幂等键为索引,而不是以调用顺序为索引。
- 未了结的义务:这次运行承诺要做但尚未做的事。
- 一个 schema 版本,以便由旧版运行时写下的检查点能被识别出来。
让这套机制在实践中真正奏效的,是一个从稳定输入而非墙钟时间或序号推导出来的、以运行为作用域的幂等键:
key = hash(run_id, step_name, canonical_args)
每一个会产生副作用的工具调用都携带这个键。下游系统,或者它前面的一层薄适配器,会记录已经执行过的键,并在重放时返回先前的结果,而不是再次执行该效果。这个键必须在重试之间保持稳定,并在不同意图之间保持唯一;从步骤名和规范化参数而非计数器推导它,正是让重放在一次会重排或跳过步骤的重启之后仍然安全的原因。
第二个机制是拒绝。检查点携带一个 schema 版本,运行时拒绝跨不兼容版本恢复,而不是尝试尽力而为的迁移。意图的迁移就是一次重新