LangGraph 1.0 checkpointing 接入 — 节点级恢复与静态站现实
LangGraph 1.0 checkpointing 接入 — 节点级恢复与静态站现实
日期: 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 参考):
| 字段 | 含义 |
|---|---|
config | 含 thread_id 与 checkpoint_id,定位「哪个会话的哪一帧」 |
values | 当前 channel 状态值(即图的 state) |
next | 下一步将执行的节点元组(空=已结束) |
tasks | 待执行任务(含 interrupt 信息) |
metadata | step 序号、来源、写入者等 |
线程模型:状态按 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;生产必用连接池。
复杂度/成本量化对照:
| 维度 | MemorySaver | SqliteSaver | PostgresSaver |
|---|---|---|---|
| 写延迟 | ~μ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 checkpointing | 2025-10 首个稳定版,durable execution 主线 |
| checkpoint 粒度 | super-step 边界存档,节点级恢复 | Pregel 模型固有,resume 重跑整节点 |
| 副作用纪律 | 副作用节点必须幂等或挪到 interrupt 后 | resume 重跑整节点会重复扣费/写库 |
| 生产后端 | 跳过 SqliteSaver,直上 PostgresSaver | SQLite 库级写锁高并发即瓶颈 |
| 静态站落地 | 自实现 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-labcheckpoint 教学面板:仿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)执行当周须复查最新补丁号。
参考资料
- 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 viacheckpoint_id(2026) - LangChain Docs — Durable execution:用 checkpointer 即获 durable execution;中断后可恢复(2026)
- 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)
- LangChain Reference —
BaseCheckpointSaver接口:.put/.putWrites/.getTuple/.list;MemorySaver/SqliteSaver/PostgresSaver/AsyncPostgresSaver;PostgresSaver.setup()+autocommit=True/row_factory=dict_row要求(2026) - 社区 2026-04(Medium「LangGraph From Zero to Production Part 2」等):开发用 MemorySaver、生产直上 PostgresSaver、跳过 SqliteSaver(库级写锁高并发瓶颈);连接池(2026-04)
- 本仓物证:
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 之外的候选复查。