返回 AIPA 笔记
AIPA Day 34

LangGraph 1.0 checkpointing 接入 — 节点级恢复与静态站现实

LangGraph 1.0 checkpointing 接入 — 节点级恢复与静态站现实

2026-07-18
langgraphcheckpointingdurable-executionidempotency

日期: 2026-07-18 阶段: Phase 2 - AI-native 参考架构 标签: #langgraph #checkpointing #durable-execution #idempotency

核心问题

Day 33 的 ADR-001 把 AML Copilot 主路径定为单 agent + 工具调用。但「单 agent」不等于「无状态」——一次 AML 调查是多步、可中断、要 human-in-the-loop 审核的长流程(检索→typology 判定→SAR 起草→合规官签字)。中途崩了、或卡在人工审核点等三天,状态怎么不丢?这正是 durable execution(持久化执行) 要解决的,而 LangGraph 1.0(2025-10 首个稳定版)的 checkpointing 是当前主线方案。

今天精读三件事:(A) checkpointer 的机制——checkpoint 到底存在哪个粒度、StateSnapshot 里有什么、resume 时重跑节点还是续行;(B) 后端选型 MemorySaver/SqliteSaver/PostgresSaver 的真实权衡;(C) 一个被大多数教程跳过的现实——本仓库是 Cloudflare Pages 静态导出站,没有常驻后端,LangGraph 的 server-side checkpointer 落不了地,怎么办。结论不是「照搬官方 PostgresSaver」,而是认清「教学装置」与「真实后端」的分叉,给出诚实的浏览器内方案。

关键内容

A. checkpointer 机制:super-step 边界存档 + 节点级(非行级)恢复

LangGraph 的执行模型是 Pregel 风格的 super-step(超级步):图按节点推进,每个 super-step 是「一批可并行节点」的一次执行。checkpointer 的核心契约是——在每个 super-step 边界保存一个 StateSnapshot("a StateSnapshot is saved at every super-step",LangChain Docs 2026)。注意「边界」二字:checkpoint 取在节点之间,不在节点内部。

一个 StateSnapshot 携带(LangChain 参考):

字段含义
configthread_idcheckpoint_id,定位「哪个会话的哪一帧」
values当前 channel 状态值(即图的 state)
next下一步将执行的节点元组(空=已结束)
tasks待执行任务(含 interrupt 信息)
metadatastep 序号、来源、写入者等

线程模型:状态按 thread_id 组织,配置形如 {"configurable": {"thread_id": "case-4711"}}。同一 thread_id 的所有 checkpoint 构成一条可回放的历史;get_state(config) 取最新帧,get_state_history(config) 取全历史,传入某帧的 checkpoint_id 即可 time-travel(从历史某点分叉重跑)。

最关键、也最反直觉的机制——resume 的粒度是「节点」不是「代码行」

节点执行 = 原子超级步;崩溃/中断后 resume 会
           「重跑整个节点函数」,不是「从 interrupt() 的下一行续」。

         node_investigate(state):
             charge_api()        # ← 副作用:调用付费检索 API
             x = interrupt(...)  # ← 等人工审核,挂起,存 checkpoint
             write_db(x)         # resume 后从节点【顶部】重跑 → charge_api() 再跑一次!

官方明言:「resuming from an interrupt re-runs the entire node function, not just from the line of code immediately following the interrupt() call」(LangChain Docs 2026)。

反直觉洞察①(resume 重跑整个节点 → 副作用必须幂等,否则重复扣费/重复写库):天真的心智模型是「checkpoint 像游戏存档,从倒下的那一行原地复活」。错。LangGraph 在 super-step 边界存档、在节点顶部复活——节点内 interrupt() 之前的所有副作用(付费 API 调用、写库、发邮件)在 resume 时会重新执行一遍。官方红线:「Absolutely avoid placing code with side effects before the interrupt() call」。落到 AML:若「调用付费交易图谱 API」放在 human-review interrupt 之前,合规官审核三天后点「继续」,这个付费 API 会被再扣一次费,且若中间写过一条 SAR 草稿记录,会产生重复草稿——直接污染审计链。结论:任何副作用节点必须幂等(idempotency key 或先查后写),或把副作用挪到 interrupt 之后。 这不是 LangGraph 的 bug,是 Pregel 超级步模型的固有语义,与 Day 17 校准、Day 33 ADR 一样,是「机制决定纪律」。

B. 后端选型:MemorySaver / SqliteSaver / PostgresSaver 的真实权衡

checkpointer 是一个接口(JS 端 BaseCheckpointSaver),任何实现必须提供四个方法:.put(存一帧 checkpoint)、.putWrites(存节点中间写)、.getTuple(按 config 取一帧)、.list(按过滤列出 checkpoint)。官方提供三档实现,权衡如下:

后端持久性并发写适用关键坑
MemorySaver/InMemorySaver进程内,进程退出即丢单进程开发/测试/单元测试重启全清,不能做生产
SqliteSaver文件持久,跨重启库级写锁,一次一写本地/单机低并发100+ 并发会话即瓶颈,官方建议「跳过它直上 Postgres」
PostgresSaver/AsyncPostgresSaver持久 + 崩溃恢复多 worker 并发读写生产、水平扩展首用须调 .setup() 建表;手建连接须 autocommit=True + row_factory=dict_row(psycopg),否则 .setup() 不提交建表

社区 2026 共识很直接:开发用 MemorySaver,生产直上 PostgresSaver,跳过 SqliteSaver——因为 SQLite 库级写锁在高并发对话场景(多个案件同时调查)会成串行瓶颈(多源 2026-04)。AsyncPostgresSaver 配 FastAPI(异步框架),同步 PostgresSaver 配 Flask;生产必用连接池。

复杂度/成本量化对照:

维度MemorySaverSqliteSaverPostgresSaver
写延迟~μs(内存)~ms(fsync)~ms + 网络 RTT
并发写吞吐单进程1(库级锁串行)N(行级 + MVCC)
水平扩展✗(单文件)✓(多 server 共享库)
运维成本0低(一个文件)中(DB 实例 + 连接池 + setup)
崩溃恢复

C. 静态导出 repo 的现实落地:浏览器内 checkpoint 教学装置 vs 真实后端

这一节是本笔记最有价值、也最容易被官方教程掩盖的部分。本仓库 momoweb3 是 Cloudflare Pages 静态导出站(commit→auto-deploy),没有常驻 Node 后端。上面 A/B 节全部假设有 server-side 进程持有 checkpointer——这在静态站直接落空:

  • MemorySaver 需常驻进程(无);
  • SqliteSaver 需服务端文件系统(无);
  • PostgresSaver 需 DB 实例 + 后端连接(无,且会泄露密钥到前端)。

官方 LangGraph.js 不提供浏览器 checkpointer——MemorySaver 是 in-process server-side 设计,浏览器持久化要自己实现(WebSearch 确认 2026 仍无官方 browser saver)。所以本项目的诚实方案是两条分叉

方案分叉:
  (1) 真实后端 (未来若需):
      → Cloudflare Pages Functions / Workers 作为薄后端
      → checkpointer 后端用 D1 (Cloudflare SQLite) 或 Durable Objects
      → 等价 SqliteSaver 语义,承载真实 AML 长流程持久化
  (2) 静态站教学装置 (W5 落地):
      → 自实现 BrowserCheckpointSaver: BaseCheckpointSaver
         .put/.getTuple/.list 落 IndexedDB (或 localStorage for 小态)
      → 纯前端、零后端、刷新不丢、可 time-travel 回放
      → 作为 /agent-lab 的可视化教学装置 (参照 dsdb-lab 模式)

自实现浏览器 checkpointer 的核心伪代码(实现四方法接口、落 IndexedDB):

class BrowserCheckpointSaver implements BaseCheckpointSaver {
  // key = `${thread_id}:${checkpoint_id}`,value = StateSnapshot 序列化
  async put(config, checkpoint, metadata) {
    const key = `${config.thread_id}:${checkpoint.id}`
    await idb.set('checkpoints', key, { config, checkpoint, metadata })
    return { ...config, checkpoint_id: checkpoint.id }
  }
  async getTuple(config) {                    // 取最新或指定帧
    const id = config.checkpoint_id ?? await latestId(config.thread_id)
    return idb.get('checkpoints', `${config.thread_id}:${id}`)
  }
  async *list(config, filter) {               // 列历史 → time-travel UI
    for (const c of await idb.all(config.thread_id)) yield c
  }
  async putWrites(config, writes, taskId) { /* 存节点中间写 */ }
}

这套装置和 src/dsdb-lab/ 的 raft/saga 教学模式同构(state machine + store + simulator + narratives):让用户在浏览器里手动「崩溃」一个调查流程,看 checkpoint 如何在 super-step 边界存档、resume 时如何重跑节点——把 A 节的「节点级恢复 + 幂等红线」变成可点击的现场演示,比文字强百倍。

反直觉洞察②(静态站做 durable execution,约束反而逼出更好的教学产物):没有后端看似是缺陷,但它逼你自己实现 checkpointer 接口——而自己实现,恰恰是真正吃透「checkpoint 存什么、resume 怎么回放」的唯一途径。一个 import PostgresSaver 的人可能永远不知道 .getTuple/.list 的存在;一个为静态站手写 BrowserCheckpointSaver 的人,被迫读懂 super-step 语义、time-travel 的 checkpoint_id 寻址。约束(无后端)把「会用框架」逼成「懂框架机制」,且产出一个可演示的教学装置——对求职作品集,「我手写了 LangGraph 兼容的浏览器 checkpointer」远强于「我配了个 PostgresSaver」。

设计要点/决策表

要点决策理由
持久化框架LangGraph 1.0 checkpointing2025-10 首个稳定版,durable execution 主线
checkpoint 粒度super-step 边界存档,节点级恢复Pregel 模型固有,resume 重跑整节点
副作用纪律副作用节点必须幂等或挪到 interrupt 后resume 重跑整节点会重复扣费/写库
生产后端跳过 SqliteSaver,直上 PostgresSaverSQLite 库级写锁高并发即瓶颈
静态站落地自实现 BrowserCheckpointSaver(IndexedDB)无后端,官方无浏览器 saver
教学装置/agent-lab 可视化崩溃-恢复,仿 dsdb-lab把节点级恢复语义变可点击演示

对本项目的落地

  • 新建 src/agent/checkpoint/browserCheckpointSaver.ts:实现 C 节四方法接口(.put/.getTuple/.list/.putWrites),存储层用 IndexedDB(大态)或 localStorage(小态 fallback),序列化 StateSnapshot。头注标明「LangGraph BaseCheckpointSaver 浏览器实现,静态站专用,对应 ADR-001 单 agent 教学装置」。
  • AML 调查流程建模为 StateGraph:节点 = 检索/typology 判定/SAR 起草/human-review,human-review 节点用 interrupt() 挂起等合规官输入。严格执行 A 节幂等纪律:付费检索 API 调用绝不放在 human-review interrupt 之前;SAR 草稿写入用 caseId 作幂等键(先查后写),防 resume 重跑产生重复草稿污染审计链。
  • /agent-lab checkpoint 教学面板:仿 src/dsdb-lab/raft/raftMachine.ts + useRaftStore.ts + RaftSimulator.tsx + narratives.ts)建 src/agent/checkpoint-lab/,让用户手动中断一个调查流、观察 super-step 边界 checkpoint、点「resume」看节点重跑、用 time-travel 回放历史帧。narratives 里故意演示一次「副作用放错位置导致重复扣费」的反例,把洞察①做成现场踩坑。
  • 真实后端的未来切口(不预报为已建):若 AML Copilot 需真实长流程持久化,走 Cloudflare Pages Functions + D1(SQLite 语义)作薄后端,等价 SqliteSaver;此为计划项,W5 仅落浏览器教学装置,真实后端标「按 R1/R2 触发后建」(呼应 ADR-001 Revisit-When)。
  • 诚实标注:本笔记不谎称已接入生产 PostgresSaver;静态站现实是浏览器 checkpointer 教学装置,真实后端为条件触发的未来动作。LangGraph 版本(1.0,2025-10)执行当周须复查最新补丁号。

参考资料

  1. LangChain Docs — Persistence / Checkpointing:checkpointer 在每个 super-step 存 StateSnapshot;字段 config/values/next/tasks/metadata;thread_id 配置 {"configurable":{"thread_id":...}}get_state/get_state_history/time-travel via checkpoint_id(2026)
  2. LangChain Docs — Durable execution:用 checkpointer 即获 durable execution;中断后可恢复(2026)
  3. LangChain Docs / 社区(Vadim's blog, langgraph-cheatsheet)— resume 语义:「re-runs the entire node function, not just from the line after interrupt()」;「Absolutely avoid placing code with side effects before interrupt()」;幂等键策略(2026)
  4. LangChain Reference — BaseCheckpointSaver 接口:.put/.putWrites/.getTuple/.listMemorySaver/SqliteSaver/PostgresSaver/AsyncPostgresSaver;PostgresSaver .setup() + autocommit=True/row_factory=dict_row 要求(2026)
  5. 社区 2026-04(Medium「LangGraph From Zero to Production Part 2」等):开发用 MemorySaver、生产直上 PostgresSaver、跳过 SqliteSaver(库级写锁高并发瓶颈);连接池(2026-04)
  6. 本仓物证:src/dsdb-lab/raft/(state machine 教学装置模式:raftMachine/useRaftStore/RaftSimulator/narratives)、src/dsdb-lab/saga/docs/aipa/day33-adr-no-multiagent.md(单 agent 主路径)(2026-06)

SOTA 检查 (2026-06-11)

  • LangGraph 1.0(2025-10)是 2026-06 durable agent execution 的主线框架:checkpointing + thread_id + time-travel 为稳定 API;本日 WebSearch 未见取代它的主流方案。分层共识(2026-04):Temporal 管宏观工作流编排、LangGraph 管微观 agent 推理循环——二者互补非互斥,本项目静态站场景两者都不需常驻后端,故走自实现浏览器 checkpointer。
  • 「resume 重跑整节点 → 副作用须幂等」是 live 的踩坑点:2026 社区仍反复警告「别把付费 API/写库放 interrupt 前」,说明这是高频实战陷阱,非已被框架自动解决。本笔记幂等纪律对 AML 审计链是硬约束。
  • 静态站无官方浏览器 checkpointer 仍成立:2026-06 WebSearch 确认 LangGraph.js 官方 saver 均为 server-side(MemorySaver in-process / SqliteSaver / PostgresSaver),浏览器持久化须自实现 BaseCheckpointSaver——本项目两分叉方案(IndexedDB 教学装置 + 未来 Cloudflare D1 后端)的前提未变。
  • 过时认知警示:(1) 不可把「配了 checkpointer」等同「副作用安全」——节点级恢复语义要求显式幂等;(2) 不可在高并发生产用 SqliteSaver(库级写锁),2026 共识已转向直上 PostgresSaver;(3) LangGraph 0.x 教程的 API(如旧 MemorySaver 导入路径)可能与 1.0 稳定版有出入,执行当周以 1.0 官方文档为准。
  • 待跟踪:LangGraph 1.0 后续补丁对 checkpointer 接口的变更;Cloudflare D1/Durable Objects 作为 checkpointer 后端的可行性(若 R1/R2 触发真实后端);Temporal × OpenAI Agents SDK GA(2026-03)的 durable execution 范式是否更适合 AML 长流程,作为 LangGraph 之外的候选复查。