Lab 01 · Loop 恢复:副作用只执行一次
在外部副作用已经提交、结果事件尚未落盘时注入退出,验证 operation_id、事件日志与恢复检查点。
本章任务
要回答的问题
进程死在副作用提交与结果落盘之间时,恢复怎样避免重复执行?
读完你能
- 识别工具执行前、执行后与验证前三个持久化边界
- 用 operation_id 查询已提交状态并修复缺失事件
- 证明重复恢复不会增加副作用次数
- 适合现在读
- 正在实现 Agent Loop、后台任务、支付、发信、发布或其他有副作用工具的工程师
- 先修知识
- 理解 JSONL 与 append-only event log;知道幂等键与至少一次投递的基本问题
- 实践产物
- 一份 Loop 恢复事件日志、效果账本和零重复副作用验收报告
- 证据边界
- 本实验用本地账本模拟外部系统;真实服务仍需提供幂等键、提交查询或补偿动作
在最危险的持久化边界杀进程
Section titled “在最危险的持久化边界杀进程”一次 publish_report 已经把结果发到团队知识库,但 Harness 还没来得及把 tool_result 写进事件日志,进程被杀。重启后只看对话历史,Agent 会再次调用工具。
本实验把事实拆成两份:
effect-ledger.json:模拟外部服务,记录副作用是否已提交;events.jsonl:Harness 自己的可重放时间线。
你要故意制造“两份事实短暂不一致”,然后证明恢复器会查外部账本、补写事件,而不是重复发布。
先读懂四个状态
Section titled “先读懂四个状态”| 状态 | 已知事实 | 恢复动作 |
|---|---|---|
planned | 模型决定调用工具,副作用还没发生 | 允许按同一 operation_id 执行一次 |
外部账本已有记录,日志没有 effect_committed | 副作用发生,Harness 在落盘前崩溃 | 查询提交状态,跳过执行,补写事件 |
result_persisted | 工具结果已经耐久化 | 不再执行工具,只继续验证 |
verified | 副作用数、结果和完成条件已核对 | 重复 Resume 应成为无操作 |
关键不是让所有文件永远同步,而是让恢复器知道谁是真相源,以及怎样从不一致状态收敛。
运行三次故障注入
Section titled “运行三次故障注入”从项目根目录运行:
python3 public/lab-assets/loop-recovery.py --reset --crash-at after_effect第一次会输出:
INJECTED_CRASH boundary=after_effect退出码是 75。此时外部效果账本已有记录,事件日志只有 planned。继续恢复:
python3 public/lab-assets/loop-recovery.pypython3 public/lab-assets/loop-recovery.py --report合格结果应包含:
PASS duplicate_effects=0再把 --crash-at 改成 before_effect 与 after_result,分别验证执行一次和只重跑 Verifier。
从事件日志重建发生过什么
Section titled “从事件日志重建发生过什么”打开 .agent-mechanics-lab/loop-recovery/events.jsonl。正常恢复后的顺序应为:
planned effect_committed # recovered_from_ledger=true result_persisted verifiedrecovered_from_ledger=true 是本实验最重要的证据。它说明恢复器不是根据最后一条模型消息猜测,而是查询了副作用真相源。
然后检查 effect-ledger.json:同一个 op_publish_report_v1 只能出现一次。连续执行恢复命令两次,事件数和副作用数都不应增加。
把实验迁移到真实 Tool Contract
Section titled “把实验迁移到真实 Tool Contract”本地 JSON 账本可以替换成:
- 支付 Provider 的 idempotency key 与 payment intent 查询;
- 邮件服务的 message id 与发送状态查询;
- GitHub / GitLab 的 deployment id;
- 数据库事务表中的唯一
operation_id; - 无法查询时的补偿动作与
needs_review状态。
不要只给工具函数加重试。工具契约至少要返回 operation_id、提交状态、可查询标识和结果持久化状态。
验收:恢复必须证明零重复副作用
Section titled “验收:恢复必须证明零重复副作用”提交报告时记录四个数字:
- 每个故障点的恢复耗时;
- 同一 operation_id 的副作用次数;
- 恢复后补写的事件数量;
- 需要人工介入的次数与原因。
通过标准是:三个边界都能解释,副作用次数始终为 1,重复 Resume 不改变状态。无法查询外部提交状态时,正确结果是 needs_review,不是静默重做。
脚本与源码入口
Section titled “脚本与源码入口”附录:复盘问题
Section titled “附录:复盘问题”展开实验复盘与加分项
- 为什么
events.jsonl不能单独证明副作用是否执行? operation_id应由模型、Harness 还是业务系统生成?- 外部服务只有“至少一次”接口时,怎样设计补偿?
- 把事件日志最后一行截断后,恢复器应该怎样处理?
- 加分项:为
effect-ledger.json增加并发写入测试,并解释文件锁与数据库唯一约束的差别。