这篇解决什么困惑
一个 Agent 调用工具之后,服务器刚好重启了。新进程应该重新搜索,还是直接进入下一步?如果搜索已经产生了外部副作用,盲目重试可能重复扣费、重复写入,或者把两个不同结果混在一起。
因此,Checkpoint 的问题不是“有没有保存一份日志”,而是:进程失去之后,系统能否知道自己处在哪个安全边界,并以同一个运行身份继续。
关键结论
当前项目把 Checkpoint 设计成受约束的运行状态,而不是 Trace 的替代品:
- Checkpoint 回答“下一次从哪里恢复”;
- Trace 回答“之前发生了什么”;
- 外部调用结果只有在经过租约、版本和状态校验后,才推进 Checkpoint;
- 恢复依赖 Checkpoint,不从 Trace 反推业务状态。
这让“重启恢复”从一个模糊的重试动作,变成了可以测试的状态转换。
一个运行究竟保存什么
项目的持久化状态分成两层。agent_run 保存运行级信息,例如运行 ID、当前所有者、租约截止时间和终态;agent_checkpoint 保存当前阶段、阶段版本,以及经过验证的内部引用。
它不会把 Prompt、完整证据正文、API Key 或任意外部文本塞进 Checkpoint。Checkpoint 只保存恢复所需的最小事实,例如“等待工具结果”“工具结果已经落盘”“可以进行下一次判断”,以及被边界校验过的 Claim ID。
可以把它想象成火车站的闸机记录:它不保存整段旅程的录像,只记录乘客已经通过哪一道闸机,以及下一道闸机是否允许进入。
恢复链路
创建 Run + READY_TO_DECIDE Checkpoint
→ 模型产生决策
→ CAS 推进到 DECISION_RECORDED
→ 调用受限工具
→ 工具结果与 Checkpoint 原子落盘
→ 进程崩溃
→ 新进程等待租约过期并接管
→ 读取 Checkpoint
→ 跳过已落盘的工具结果
→ 继续 Decide / Verify / Stop
这里有两个关键动作。第一,所有推进都带有期望版本号,只有版本仍然匹配时才能写入;第二,运行者必须持有有效租约,旧进程即使“醒过来”也不能继续写。
为什么需要 CAS 和租约
只用数据库的一行状态还不够。假设旧进程卡顿,新进程接管了运行;如果旧进程随后恢复,它可能把过期结果覆盖到新状态上。
所以每次修改同时检查:
- 当前操作者是不是租约所有者;
- Checkpoint 版本是不是调用者看到的那个版本;
- 当前阶段是否允许这次转换。
任何一项不满足,写入都会失败关闭。系统不会为了“尽量完成”而偷偷修复冲突,也不会让 Trace 单独看起来像成功。
工具到底会不会重复调用
外部调用无法和本地数据库事务真正处在同一个原子边界里。调用可能已经发出,但进程在收到结果前崩溃;这时系统不可能凭空知道对方是否执行成功。
当前设计的处理方式是显式承认不确定性:恢复时把遗留的调用标为 UNCERTAIN,使用同一个逻辑调用 ID 重放,并记录尝试次数。对于证据搜索这种只读工具,重放是可接受的;对于有副作用的工具,未来需要幂等键、人工批准或更严格的执行策略。
这也是“避免重复调用”更准确的说法:系统能避免已经确认落盘的调用再次执行;对崩溃窗口内的不确定调用,它不会假装知道答案,而是把不确定性显式纳入协议。
Checkpoint 与 Trace 的边界
早期实现把 AgentStepTrace 放在内存返回值里,看起来可以看到每一步,但重启后什么都没有了。持久化后,两者的职责被明确拆开:
| 问题 | Checkpoint | Trace |
|---|---|---|
| 恢复从哪里继续 | 是 | 否 |
| 记录历史调用顺序 | 否 | 是 |
| 允许推断业务状态 | 是,且只读当前状态 | 否 |
| 保存完整 Prompt/证据 | 否 | 否 |
| 发生冲突时自动修复 | 否 | 否 |
这个边界很重要。Trace 是观察面,不是第二个事实源;否则一旦 Trace 延迟、重复或缺失,恢复就会变成猜测。
对应代码与测试证据
恢复主流程由 CheckpointedEvidenceAgentRunner 协调,持久化端口是 AgentCheckpointStore,PostgreSQL 实现负责租约和 CAS。Flyway V6 建立 Run 与 Checkpoint 表。
集成测试模拟了“工具结果已经提交,进程随后崩溃”的场景。新 Runner 在租约到期后接管,恢复至完成;整个运行的 Evidence Search 调用总数保持为 1。测试还覆盖了过期租约接管、陈旧版本写入、非法阶段转换、过大的观察 ID 和禁止进入 Checkpoint 的敏感内容。
这证明的是当前证据搜索工具和当前崩溃窗口下的恢复行为,不等于所有外部工具都天然具备 exactly-once 语义。
仍然没有解决什么
持久化 Checkpoint 解决了“从哪里继续”,没有解决所有生产问题:
- 实际 Token/费用还没有按供应商 usage 完整扣减;
- 一般模型或工具异常的分类恢复、退避和重规划仍待补齐;
- 对外运行、查询、取消和恢复 API 尚未开放;
- 有副作用的工具还需要幂等协议和更严格的审批边界。
因此,Checkpoint 是 Harness 的一部分,不是“加一张表就生产可用”。它的价值在于把一次不可控的重启,变成一组明确的状态、权限和失败证据。
更新时间:2026-08-28