检查点与回滚


文档摘要

检查点与回滚 本节摘要:持久执行(第 12 节)让崩溃的 Agent 可恢复;propose-then-commit(第 15 节)让已批准动作可审计。本节把两者接起来:当一个已批准动作执行到一半、崩溃、再恢复时,会发生什么?回滚何时跑、对着什么状态跑?答案是:每一次图状态转移(graph-state transition)都持久化;worker 崩溃时它的租约(lease)到期,另一个 worker 从最新检查点接手;Cloudflare Durable Objects 能跨数小时到数周保存状态。真正奏效的组合是四件套——幂等键(防重复执行)+ 前置条件检查(状态仍与批准时一致)+ 动作后验证(副作用真的发生)+ 验证失败即回滚。

检查点与回滚

本节摘要:持久执行(第 12 节)让崩溃的 Agent 可恢复;propose-then-commit(第 15 节)让已批准动作可审计。本节把两者接起来:当一个已批准动作执行到一半、崩溃、再恢复时,会发生什么?回滚何时跑、对着什么状态跑?答案是:每一次**图状态转移(graph-state transition)都持久化;worker 崩溃时它的租约(lease)**到期,另一个 worker 从最新检查点接手;Cloudflare Durable Objects 能跨数小时到数周保存状态。真正奏效的组合是四件套——幂等键(防重复执行)+ 前置条件检查(状态仍与批准时一致)+ 动作后验证(副作用真的发生)+ 验证失败即回滚。最尖锐的失败模式是「双执行(double-execute)」:提交开始、执行、返回 200、工作流在持久化「已提交」状态前崩溃,恢复时看到「已批准未提交」于是再执行一次,副作用触发两次。缓解是「先标记意图在途、再用幂等键执行、动作后验证成功才标已提交」的状态写入顺序。EU AI Act 第 14 条把这一切落地为操作要求:检查点必须可被审计者查询、回滚必须演练过、审计轨迹必须在部署后存活、验证失败必须告警而非静默记录。本节是工程课:状态的写入顺序决定系统是可靠还是双执行。

对应原课程:Phase 15 · Lesson 16 · checkpoints-rollback(原英文 phases/15-autonomous-systems/16-checkpoints-rollback/docs/en.md)。前置:第 12 节(持久执行)、第 15 节(propose-then-commit)。

学习目标

阅读完本节,你应当能够:

  1. 解释图状态转移为什么必须每一次都持久化,而不是只在特定提交点。
  2. 描述租约恢复:worker 崩溃后,租约如何到期、另一个 worker 如何从最新检查点精确接手。
  3. 说明为什么幂等键不够还要前置条件检查——用「转账时余额已变」的例子推导出透支风险。
  4. 区分三类回滚(带内回滚 / 补偿事务 / 带外回滚),并为「无回滚」动作要求更强 HITL。
  5. 重述双执行失败模式的五个步骤,并给出「先标记在途、后标记完成」的缓解顺序。
  6. 把 EU AI Act 第 14 条翻译成四条操作要求(可查询检查点、演练过回滚、审计轨迹抗部署、验证失败告警)。

一、问题与直觉

持久执行让崩溃的 Agent 可恢复;propose-then-commit 让已批准动作可审计。本节把它们接起来:当一个已批准动作执行到一半、崩溃、再恢复时,会发生什么?回滚何时跑、对着什么状态跑?

真实系统接法不同:

  • LangGraph 把每一次图状态转移检查点到 PostgreSQL。worker 崩溃时租约释放,另一个 worker 在最新检查点恢复。工作流在 interrupt() 上暂停,而 interrupt() 本身也持久化。
  • Cloudflare Durable Objects 跨数小时到数周保存每键状态。把计算与已批准动作的存储同置(co-locate)
  • Microsoft Agent Framework 在工作流 API 里暴露 Checkpoint 原语;重放加幂等覆盖重试。

每种情况里,真正奏效的组合都一样:幂等键(防双执行)+ 前置条件检查(状态仍与批准时一致)+ 动作后验证(副作用真的发生)+ 验证失败即回滚

⚠️ 核心张力:双执行是这片最常见的产线事故,而它的根因几乎总是状态写入顺序错了——先执行再标记,或先标记完成再执行,都会在「执行与标记之间的崩溃窗口」里翻车。

每一次转移都持久化

图状态转移是任何把工作流从一个命名状态移到另一个命名状态的步骤。朴素实现只在特定提交点持久化;产线实现每次转移都持久化。代价(多几次写)相对于可靠性收益(重放能落在任意点、租约恢复精确)很小。

租约恢复

worker 崩溃时,工作流没有丢——租约(「这个 worker 正在执行这次运行」的短期声明)只是到期。另一个 worker 拿起最新检查点并恢复。租约机制是让产线系统在滚动部署中存活而不丢在途工作的关键。

幂等 + 前置条件

光有幂等不够。设想:一个工作流被批准为「当余额 > 1000 时从 A 转 100 到 B」。工作流提交,执行到一半崩溃,恢复。如果只查幂等键,执行恢复,转账跑一次(正确)。但设想崩溃与恢复之间,A 的余额被另一个工作流降到了 500。幂等检查仍过;前置条件不过。没有前置条件检查,我们就发了一次透支。

每个后果性动作两样都要:

  • 幂等键:防双执行。
  • 前置条件检查:确认状态仍与批准时一致。

动作后验证

「工具返回 200」不是验证。真正的验证是重新读目标状态并确认副作用真的发生了。模式:

  • 数据库更新:UPDATE ... RETURNING *,断言返回行匹配意图状态。
  • 邮件发送:提交后到已发件箱查消息 ID。
  • 文件写入:读回文件并算哈希。
  • API 调用:对目标资源做一次后续 GET

验证失败,工作流处于已知坏状态,回滚介入。

回滚计划

propose-then-commit(第 15 节)里每个后果性动作都带一份回滚计划。类型:

  • 带内回滚(in-band):直接逆转副作用(INSERTDELETE、发送后发更正邮件)。
  • 补偿事务(compensating transaction):一个新动作中和原动作(标准 SAGA 模式)。
  • 带外回滚(out-of-band):告警人工、暂停工作流、把坏状态留着调查。

空回滚(「我们撤销不了」)必须在提议里命名。没有回滚的动作,在提交时需要更强 HITL(第 15 节质询-应答)。

EU AI Act 第 14 条的操作读法

第 14 条要求高风险系统的「有效人工监督」。操作上,实现者把它读成:

  • 检查点可被审计者查询
  • 回滚演练过(端到端至少测过一次)。
  • 审计轨迹抗部署(检查点后端不是易失的)。
  • 验证失败要告警,不能静默记录。

一个在提交到一半崩溃、恢复、完成了副作用却没有验证+回滚路径的工作流,过不了第 14 条的测试

最尖锐的失败:双执行

这片最常见的产线事故:

  1. 动作已批准,幂等键 k。
  2. 提交开始,执行,返回 200。
  3. 工作流在持久化「已提交」状态之前崩溃。
  4. 工作流恢复;看到「已批准未提交」;重新执行。
  5. 副作用触发两次。

缓解:在执行持久化一个「在途(intent in-flight)」意图,用幂等键执行,只在动作后验证成功后才标记「已提交」。如果动作触发了而状态写入失败,你知道要去验证(必要时重触发);如果状态写入成功而动作失败,你验证并通过恢复路径精确触发一次。

二、从零实现:带检查点的工作流

原课程 code/main.py 用标准库实现一台带幂等、前置条件、验证、回滚的检查点化工作流。驱动器模拟四个场景:干净运行、崩溃后重试(幂等抓住)、前置条件失败(工作流不发即中止)、验证失败(回滚触发)。下面给出关键骨架。

「先标记在途、后执行、验证后才完成」的正确顺序

def commit_safely(proposal, store, execute, read_back, rollback): key = proposal["idempotency_key"] rec = store.get(key) # 已完成 → 直接返回(幂等) if rec and rec["state"] == "committed": return rec # 1. 先标记在途(防崩溃窗口里的双执行) store.put(key, {**proposal, "state": "in_flight"}) # 2. 前置条件:状态仍与批准时一致 if not precondition_holds(proposal): store.put(key, {**proposal, "state": "aborted"}) return None # 余额已变 → 不发, 避免透支 # 3. 用幂等键执行 execute(proposal["action"], idempotency_key=key) # 4. 动作后验证:副作用真的发生了吗? if not read_back(proposal["action"]): rollback(proposal["rollback_plan"]) # 验证失败 → 回滚 + 告警 store.put(key, {**proposal, "state": "rolled_back"}) alert("verify 失败, 已回滚") return None # 5. 验证成功才标记完成 store.put(key, {**proposal, "state": "committed"}) return proposal

设计要点:状态写入顺序是这台机器的灵魂。「在途」标记把崩溃窗口从「执行与完成标记之间」缩到「在途标记写入的瞬间」——即使在那里崩溃,恢复时也能凭幂等键 + 验证精确触发一次。

租约:worker 崩溃的恢复机制

class Lease: """worker 对一次运行的短期声明;崩溃时到期, 另一 worker 接手。""" def __init__(self, run_id, ttl_seconds=30): self.run_id, self.ttl = run_id, ttl_seconds self.expires_at = now() + ttl_seconds def renew(self, worker): if worker.alive(): # 滚动部署中 worker 被杀 → 不续 self.expires_at = now() + self.ttl def held_by(self, worker): return now() < self.expires_at and worker.alive()

租约到期,最新检查点被另一个 worker 拿起。这是产线在滚动部署中不丢在途工作的关键。

三、框架对比:三种耐用基板的接法

框架 检查点机制 租约/恢复 适用场景
LangGraph 每次图状态转移 → PostgreSQL worker 崩溃租约释放, 另一 worker 在最新检查点恢复 长程多步工作流, 需 interrupt() 暂停
Cloudflare Durable Objects 每键状态跨小时到周 计算与存储同置, 边缘就近恢复 低延迟、长寿命的每键状态
Microsoft Agent Framework 工作流 API 的 Checkpoint 原语 重放 + 幂等覆盖重试 企业工作流, 与 Azure 状态服务集成
裸 stdlib JSON 文件(教学) 进程内模拟租约 教学演示状态写入顺序

关键观察:三家都实现了「每次转移持久化 + 租约恢复 + 幂等」的同一组合。差异在耐用基板(PostgreSQL vs Durable Object vs 托管状态服务)与暴露面(API 形状)。选型看的是基板的可用性与一致性保证,而非概念——概念是共有的。

💡 三家的检查点后端都不是易失的。这是 EU AI Act 第 14 条「审计轨迹抗部署」的工程落地:检查点后端必须能在应用重新部署后存活,否则审计能力在一次部署后就消失。

四、可复用产物

原课程 outputs/skill-rollback-rehearsal.md 为一条拟议工作流设计回滚演练测试,并审计检查点后端是否符合审计轨迹持久要求。它把「列出每个后果性动作 → 为每个分类回滚类型 → 设计端到端崩溃-恢复-回滚测试 → 审计检查点后端抗部署性」串成可重复清单。

code/main.py 是零依赖状态机,把 JSON 存储换成 PostgreSQL/Redis/Durable Object 即可贴近产线;状态写入顺序、幂等、前置条件、验证、回滚逻辑均无需改动。

五、练习

  1. code/main.py:验证四个场景。对于「提交到一半崩溃」,确认动作跨重试精确触发一次

  2. 改坏状态写入顺序:把「先标记在途」改成「先执行再标记完成」,重跑崩溃场景,数有多少重复动作触发——直观感受双执行。

  3. 为具体产线动作设计回滚(如「往 Slack 频道发帖」):分类为带内、补偿、带外,说明选择理由。

  4. 审计一个你熟悉的工作流:列出每个状态转移,每个标上持久性要求(持久化/不持久化),数出你当前没持久化的那些。

  5. 演练回滚测试:设计一个端到端测试——跑真实工作流、崩溃它、确认回滚路径触发。测试断言什么?

本节要点回顾

  1. 本节把持久执行(12 节)与 propose-then-commit(15 节)接起来:已批准动作执行到一半崩溃再恢复时该怎么办。
  2. 每次图状态转移都持久化, 不只特定提交点——代价小、可靠性收益大,重放能落在任意点。
  3. 租约恢复:worker 崩溃租约到期, 另一 worker 从最新检查点精确接手, 这是滚动部署不丢在途工作的关键。
  4. 幂等键不够, 还要前置条件检查:余额在崩溃窗口里被别处改了, 幂等仍过但前置条件不过, 否则就发透支。
  5. 「工具返回 200」不是验证:要重新读目标状态确认副作用发生(UPDATE...RETURNING、查已发件箱、读回文件哈希、后续 GET)。
  6. 三类回滚:带内(直接逆转)、补偿事务(SAGA 中和)、带外(告警人工);无回滚动作必须在提议里命名并要求更强 HITL。
  7. 双执行是最常见产线事故:执行后、标记前崩溃 → 恢复看到未提交 → 再执行。
  8. 缓解是状态写入顺序:先标记在途、用幂等键执行、动作后验证成功才标记完成——把崩溃窗口缩到最小。
  9. EU AI Act 第 14 条四条操作要求:可查询检查点、演练过回滚、审计轨迹抗部署、验证失败告警——缺验证+回滚路径过不了审查。

下一节,我们从「工程层防线」上升到「模型层防线」——Constitutional AI 与规则覆盖(Constitutional AI and Rule Overrides),看如何用一部不可被 Agent 编辑的「宪法」把硬性禁止嵌进模型行为本身。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U