返回 AIPA 笔记
AIPA Day 107

多会话运行时 II — durable 会话与跨会话恢复

多会话运行时 II — durable 会话与跨会话恢复

2026-09-29
durable-sessioncheckpointcross-session-resume

日期: 2026-09-29 阶段: Phase 4 - 自建 Agent 平台×求职冲刺 标签: #durable-session #checkpoint #cross-session-resume

核心问题

Day 106 解决了会话之间「不串扰」。今天解决会话自己的「不丢失」——durable 会话

  1. 会话怎么跨进程/跨天存活? AML 调查不是一次 request 跑完的——分析师查到一半下班,第二天接着查;HITL 卡在「等合规官批准 SAR」要等数小时。会话状态必须在 compute 被回收后持久化,再来时断点续跑
  2. 怎么复用 P2 的 checkpointMachine? P2 的 checkpointMachine.ts 已经实现了「每步落 checkpoint + 崩溃后从最近 checkpoint 续跑 + 幂等重放」——但它是单跑、内存内的。今天把它升到会话级、可跨会话恢复:checkpoint 以 (tenantId, sessionId) 寻址持久化,kill 掉「进程」后从存储读回 checkpoint 续跑。
  3. 托管平台怎么做? 对照 Google Agent Engine Sessions + Memory Bank(2025-12-16 GA),看「短期会话历史」与「长期跨会话记忆」是两层不同的东西——这是自建时最易混淆的设计点。

对 AML:durable 会话是可审计性的载体——每一步状态快照都是调查链路的证据,监管要求「每决策可追溯」(Day 98 复查的 FIS 口径),而可追溯的前提是状态被持久化、可重放、不因崩溃丢失。

关键内容

A. 会话持久化状态机:从单跑 checkpoint 到 durable 会话

P2 的 CheckpointMachine<S> 核心语义(checkpointMachine.ts):每步是纯幂等 reducer,每步成功后落一个 Checkpoint<S> = { meta: {completedSteps, lastStepId}, state, version },崩溃时已落盘 checkpoint 不回滚resumeFrom(cp) 从外部传入的 checkpoint 续跑。注释里已经点明它的局限:

"真实持久化需要后端(LangGraph 把 checkpoint 写进 Postgres/Redis/DynamoDB 等 durable store,并用 thread_id 寻址);这里 checkpoint 只在内存里,进程/标签页一关即失。"

Day 107 要补的正是这个「thread_id 寻址 + durable store」。设计上不改 CheckpointMachine,而是在它外面包一层会话持久化适配器——这是关键架构决策:让纯状态机保持纯,持久化是正交关注点。

// 会话持久化适配器:把 CheckpointMachine 的内存 checkpoint
// 以 (tenantId, sessionId) 为 thread key 落到 durable store,支持跨会话恢复
class DurableSession<S> {
  constructor(
    private readonly key: SessionKey,        // (tenantId, sessionId),Day 106
    private readonly store: CheckpointStore,  // 持久化后端(教学装置=localStorage 模拟)
    private readonly steps: Step<S>[],
    private readonly initial: S,
  ) {}

  // 推进一步并持久化——崩溃发生在任意位置,下次 resume() 都能续上
  step(): RunResult<S> {
    const cp = this.store.read(this.key) ?? bootstrapCheckpoint(this.initial)
    const machine = createCheckpointMachine(this.steps, this.initial)
    const result = machine.resumeFrom(cp)        // 从持久化 checkpoint 续跑
    this.store.write(this.key, result.checkpoint) // 落盘新 checkpoint(durable 写)
    return result
  }

  // 模拟"进程重启":丢掉一切内存态,只靠 store 里的 checkpoint 恢复
  static resumeAfterCrash<S>(key, store, steps, initial): DurableSession<S> {
    const cp = store.read(key)
    assert(cp !== null, `no durable checkpoint for ${key.sessionId}`)
    // 注意:不重放已完成步骤——completedSteps 之前的步骤不再执行(幂等续跑)
    return new DurableSession(key, store, steps, initial)
  }
}

关键不变量(继承自 P2,会话级强化):

  1. 幂等续跑resumeFromcompletedSteps 开始,已完成步骤不重做。若崩溃发生在「reducer 算完但 checkpoint 没落盘」(P2 的 phase: 'after'),恢复后会重做该步——所以 reducer 必须幂等(at-least-once 下重放不双记账)。AML 场景:重做「拉取制裁名单匹配」无副作用 OK,但「提交 SAR 到监管系统」必须幂等键去重。
  2. 单调版本号:每落一次 checkpoint,version++。可断言「恢复点确实前进了」,也是乐观并发控制的基础(防两个 tab 同时写同一会话覆盖彼此)。
  3. thread key = (tenantId, sessionId):与 Day 106 命名空间同键,持久化天然继承隔离。

B. 对照 Google Agent Engine:Sessions ≠ Memory Bank

Google Vertex AI Agent Engine 于 2025-12-16Sessions 与 Memory Bank 一起 GA(Ivan Nardini/Google Cloud 公告:「Sessions & Memory Bank are now GA」+ 7 个新区域 + 降价)。最重要的设计洞察:它把「durable 会话」拆成了两个正交的东西

维度Agent Engine SessionsAgent Engine Memory Bank
存的是单会话内的时序对话历史(短期)跨会话的长期事实/偏好(长期)
生命周期一个 session 内跨多个 session、跨天/跨周
寻址session_idscope(如 user_id
读写时机每轮对话追加后台异步抽取 + 检索注入
类比 P2checkpoint(状态快照)比 checkpoint 更高层(提炼出的知识)
ADK 接口VertexAiSessionServiceVertexAiMemoryBankService

Memory Bank 的机制(Google Cloud Blog,基于 ACL 2025 接收论文 arXiv 2503.08026)三步:

  1. 抽取:「Using Gemini models, Memory Bank can analyze a user's conversation history... to extract key facts, preferences, and context」——异步后台跑,不阻塞会话主链路。
  2. 整合(consolidation):新信息进来时「Memory Bank (using Gemini) can consolidate it with existing memories, resolving contradictions and keeping the memories up to date」——去重 + 矛盾消解,不是简单 append。
  3. 检索注入:新会话开始时「retrieve... a simple retrieval of all facts or a more advanced similarity search (using embeddings)」——按相关度注入,避免「lost in the middle / context rot」。

反直觉洞察①(durable 会话不等于「把对话历史全存下来」):自建时最容易犯的错——把整个会话历史当 durable state 一股脑持久化,然后新会话全量塞回上下文。这恰恰触发 "lost in the middle" 和 "context rot",长会话质量反而崩。Google 的拆分给的答案:短期对话历史(Sessions)与长期提炼记忆(Memory Bank)是两套存储、两种生命周期。durable 的是「状态机 checkpoint」,不是「原始对话流」;跨会话带回的应是提炼后的事实,不是原始 transcript。

这对自建的指导:P2 的 checkpoint 对应 Sessions 层(单会话的可恢复状态);而 P2 的 src/agent/memory/pinnedFactsStore.ts / summarizer.ts / contextBuilder.ts)对应 Memory Bank 层(提炼 + 跨会话)。Day 107 主攻 Sessions 层的 durable,Memory Bank 层 P2 已有骨架,今天只点明分层。

C. kill→恢复演示:durable 的「可证伪」验证

durable 是个强断言——「无论何时崩,恢复后结果不变」。要让它可证伪(作品集能演示),必须设计一个 kill→恢复演示,状态机如下:

[durable 会话 kill→恢复演示]

   会话 5 步:fetch→sanction→risk→narrative→submit
                      │
   ┌──────────────────┼──────────────────────────────┐
   ▼                  ▼                                ▼
 step1 ✓           step2 ✓        ◄── 在 step3 "before" 注入 kill
 落 cp(v1)         落 cp(v2)           (reducer 未执行)
                      │
                      ▼  【模拟进程死亡:丢弃所有内存态】
              store 里只剩 cp(v2)
                      │
                      ▼  resumeAfterCrash(key, store)
              从 cp(v2) 读回 → completedSteps=2
                      │
                      ▼  续跑 step3→4→5
              executed = [step3, step4, step5]  ◄── step1/2 不重做
                      │
                      ▼
        最终 state === 无崩溃直跑的 state  ◄── durable 正确性断言

对照「崩在 phase:'after'」(reducer 算完、checkpoint 没落盘):恢复后 executed多一个重做的 step3[step3, step3', step4, step5]),但因 reducer 幂等,最终 state 仍相同。这两个用例一起证明 durable 的两种崩溃位置都安全——这正是 P2 CrashSpecbefore/after 双相设计的会话级复用。

量化对照(自建教学装置 vs 三家托管的 durable 会话能力):

能力自建教学装置Foundry SessionsAgent Engine SessionsAgentCore Memory
单会话状态持久化localStorage 模拟$HOME/files,15min 回收持久托管,GA 2025-12短期 events
跨进程恢复resumeFrom(读回 cp)stateful resumesession 跨天恢复
跨会话长期记忆pinnedFactsStore(P2)Memory Bank(GA)长期 records(episodic GA)
幂等重放保证✅(reducer 纯函数)平台托管平台托管平台托管
最长会话/保留无限(内存/storage)30 天删除持久可配
成本0计费(Day 108)$0.0864/vCPU-h + $0.25/千 events$0.25/千 events

D. 跨会话恢复的一致性陷阱:并发写与版本冲突

durable 会话开了「同一会话可被多次恢复」的口子,就引入了并发写风险:分析师在两个浏览器 tab 打开同一 sessionId,各自推进、各自写 checkpoint,后写的覆盖先写的——丢更新(lost update)。P2 的单调 version 字段在这里发挥第二个作用(除了「断言前进」)——做乐观并发控制

写 checkpoint 前:read 当前 store 里的 version
  if (store.version !== myBaseVersion) → 冲突!拒绝写,要求重新 resume
  else → CAS 写入 version+1

这是 P2 day34/day36(LangGraph checkpoint / durable resume)埋下的伏笔的会话级兑现。AML 场景的合规含义:同一调查不能被两条分叉的状态污染——否则审计链路出现「同一 caseId 两个矛盾的 SAR 草稿」,监管无法判定哪个是真。乐观锁保证「同一会话的状态线性化」。

反直觉洞察②(durable 让「恢复」变多,反而需要更强的并发控制):直觉上 durable=更可靠。但「可恢复」意味着「可被多次、多处恢复」,并发面反而变大了。不加版本号乐观锁的 durable 会话,比不可恢复的单跑更容易出数据不一致。durable 与一致性是一对必须同时设计的孪生关注点,不能只做前者。

设计要点/决策表

要点决策理由
持久化方式适配器包裹 CheckpointMachine,不改状态机本身持久化是正交关注点,保持纯状态机可测
thread key(tenantId, sessionId),同 Day 106 命名空间持久化继承隔离,零额外隔离逻辑
短期 vs 长期checkpoint=Sessions 层;pinnedFacts/summarizer=Memory Bank 层对标 Agent Engine 双层,避免全量 transcript 回灌
幂等续跑reducer 纯幂等;提交类操作加幂等键at-least-once 重放不双记账(继承 P2)
并发控制单调 version + 乐观锁(CAS)防多 tab 恢复致丢更新/分叉污染
演示可证伪kill→恢复演示,断言 state 与直跑相同durable 是强断言,必须能演示证伪

对本项目的落地

  • 新建 src/agent/durable/durableSession.ts:实现 A 节 DurableSession<S>CheckpointStore 接口(read/write(key, cp),教学装置版用内存 Map / localStorage 模拟 durable store)。复用 src/agent/durable/checkpointMachine.tscreateCheckpointMachine / resumeFrom / RunResult,一行不改状态机,只在外面包持久化 + 乐观锁(用 Checkpoint.version 做 CAS)。thread key 用 Day 106 的 SessionKey
  • 新建 src/agent/durable/__tests__/durableSession.test.ts:实现 C 节 kill→恢复演示——5 步会话,在 step3 注入 injectCrash(2, 'before'),丢弃内存态,resumeAfterCrash 读回 store 续跑,断言 (1) executed 不含 step1/step2(不重做)、(2) 最终 state === 无崩溃直跑 state;再加一个 phase:'after' 用例断言重做但幂等。D 节并发用例:两个 session 实例基于同一 baseVersion 写,断言后写被乐观锁拒绝。
  • DurableExecutionPanel 升级预留src/components/agent-arch/DurableExecutionPanel.tsx(P2 已建,演示单跑 durable)扩一个「跨会话恢复」模式——展示 store 里的 checkpoint 列表、kill 按钮、resume 按钮,让作品集能点击演示 durable 会话恢复,而非只跑单次崩溃恢复。
  • 诚实标注durableSession.ts 头注——本模块 store 为内存/localStorage 模拟,真 durable 需 Postgres/Redis/DynamoDB(同 checkpointMachine 头注口径);Sessions/Memory Bank 双层对标 Agent Engine(2025-12-16 GA),本装置只实现 Sessions 层 durable,Memory Bank 层见 P2 src/agent/memory/。金额纪律:会话态含金额一律整数分,提交类 reducer 须带幂等键。

参考资料

  1. Google Cloud / Ivan Nardini — Vertex AI Agent Engine GA 公告:Sessions & Memory Bank now GA(2025-12-16);7 新区域;降价;Code Execution 一并 GA(X/公告 2025-12)
  2. Google Cloud Blog — Vertex AI Memory Bank in public preview:Gemini 异步抽取 facts/preferences;consolidation 消解矛盾;similarity search(embeddings)检索注入;按 scope(user_id)组织;防 lost-in-the-middle/context rot(基于 ACL 2025 论文 arXiv 2503.08026)
  3. Vertex AI Agent Engine 文档 — Sessions(VertexAiSessionService)/ Memory Bank(VertexAiMemoryBankService):短期对话历史 vs 长期跨会话记忆;ADK 集成;$0.0864/vCPU-h + $0.25/千 events(2026-01-28 起计费)
  4. arXiv 2503.08026 — Memory Bank 底层方法(topic-based 记忆学习与召回,ACL 2025 接收)
  5. 本仓库 src/agent/durable/checkpointMachine.ts(resumeFrom/CrashSpec before·after/version 单调/deepClone)、src/agent/memory/{pinnedFactsStore,summarizer,contextBuilder}.ts(Memory Bank 层对标)(2026-06)

SOTA 检查 (2026-06-11)

  • 「短期会话 + 长期记忆」双层是 2026-06 托管平台共识:Agent Engine(Sessions+Memory Bank,GA 2025-12-16)、AgentCore(短期 events + 长期 records,episodic memory GA)、Foundry(会话 $HOME 持久 + Memory 层 preview)三家都把 durable 拆成两层。自建照此分层是当前最佳实践,本日 WebSearch 未见反例。
  • Memory Bank 的 LLM 抽取/整合是活跃方向:用 Gemini 异步抽取 + 矛盾消解(而非规则去重)是 2025-2026 新范式(ACL 2025 论文打底);竞品 AgentCore 的 episodic memory、mem0/Zep 等开源方案口径类似。说明「记忆」正从「存 transcript」演进到「LLM 提炼的结构化事实」,自建 summarizer.ts 方向正确。
  • Agent Engine 计费起点 2026-01-28:Sessions/Memory Bank GA 时免费至 2026-01-28,之后 $0.25/千 events——本笔记成本对照基于 2026-06 口径,执行当周须重新确认价格(Day 108 详展)。
  • 过时认知警示:「durable = 把会话历史全持久化再全量回灌」过时——会触发 context rot;正确做法是 checkpoint(状态)+ 提炼记忆(事实)分层。另:「durable=更可靠,无需额外一致性设计」过时——D 节证明可恢复放大了并发面,必须配乐观锁。
  • 待跟踪:Foundry Memory 层 2026-06 仍 preview(vs Agent Engine/AgentCore 已 GA),其 durable 会话与长期记忆的 GA 时间待定,转 GA 后回填 C 节量化对照表。