AI agent Session Schema Migration 与 Backward Compatibility:会话存久了结构总会变,怎么迁移而不丢恢复能力

HTMLPAGE 团队
16 分钟阅读

Session store 真正上线后,旧字段、旧事件和旧 reader 都会成为现实包袱。本文讲清 session schema migration、backward compatibility、lazy read 与双写迁移,避免旧 run 一醒来就读不动。

#AI agent #Session Schema #Backward Compatibility #工程实践

很多团队在做 AI agent session store 时,最初都会觉得 schema migration 不是眼前问题。第一版先把 event log 和 session state 跑起来,后面字段不够再加、结构不顺再改,好像没什么大不了。可只要系统一旦进入真实使用,旧 session 就会变成一种不能回避的现实:上周的 run 还没彻底结束,上个月的草稿可能还要继续编辑,半年之前的审计记录还要能解释为什么当时会走那条路径。

这时 schema 变化就不再只是开发便利,而会直接决定恢复能力和可信度。因为只要某个旧 session 在被 wake、被 replay、被导出时读不动,你就不是“有一点兼容成本”,而是系统在向用户承认:这份历史状态虽然我们保留着,但其实已经不再真正可用。

这也是 session schema migration 和普通数据库迁移最大的差别。你迁的不是一张静态表,而是一段仍有可能被重新解释、重新执行、重新审计的运行历史。

建议结合 AI agent Session Store 设计AI agent Context Compaction、Reset 与 Memory WriteAI agent Harness 崩溃恢复与 Wake 流程AI agent 工具 Schema 演进兼容 一起看。

Session schema 和 tool schema,看起来像一回事,其实不是一回事

对象变化内容最容易出问题的地方
Tool Schema参数名、必填字段、返回结构新工具调用读不懂旧参数
Session Schemaevent 类型、状态字段、checkpoint 结构、policy 引用旧 run 无法 wake、resume 或 replay
Derived Memory Schema摘要、长期记忆、召回键旧记忆继续被错误召回

Tool schema 主要影响单次调用能不能继续跑,session schema 影响的是“这段历史还能不能被系统重新理解”。后者的代价往往更大,因为它直接牵涉恢复和审计。

最稳的迁移方式,通常不是一次性重写,而是 reader 先兼容

很多团队一想到迁移,第一反应就是把旧 session 全部改写成新结构。这种方式在小规模时可行,但只要历史数据量上来,风险就很高:

  • 改写窗口长
  • 回滚困难
  • 一旦某种边缘旧事件没被覆盖,旧 run 会在最需要的时候才爆出来

更稳的顺序通常是:

  1. 新 reader 先支持读旧结构
  2. 新 writer 开始写新结构或双写过渡结构
  3. 后台再慢慢把高价值旧 session 迁到新格式
  4. 等确认没有重要流量再退役旧 reader

这套顺序的价值在于,它把“先能读”放在了“先整齐”前面。对 session store 来说,这个优先级通常是对的。

Lazy read、双写和离线重写,各适合不同场景

常见迁移策略大概有三种:

  • Lazy read / on-read upgrade:读旧 session 时即时映射成新视图。适合旧流量不高、但必须保持可读的情况。
  • Dual write:一段过渡期内同时写旧格式和新格式。适合关键链路不能冒险,但代价是写路径复杂度上升。
  • Offline rewrite:后台批量改写旧数据。适合结构差异很大、且可以严格控批次的场景。

没有哪一种永远最好。真正的选择标准通常是:旧 session 会不会在未来几周内被频繁唤醒,如果会,就尽量优先 reader 兼容;如果旧格式已经明显拖住了当前写路径,再考虑离线重写。

Migration 最怕碰到 compaction 和 wake flow 交叉

这类事故很隐蔽。团队为了节省上下文和存储,先对旧 session 做了一轮 compaction,把一些被判断为“历史细节”的字段压缩掉。后面又做了 schema migration,把 wake flow 改成依赖新的 checkpoint envelope。问题在于:

  • 旧 session 的某个 resumeAnchor 字段被 compaction 吸收进 summary
  • 新 wake reader 假设这个字段会显式存在
  • session 本身还在,event log 也还在
  • 真正恢复时,系统发现读得懂历史事件,却找不到继续执行所需的精确锚点

这种故障比直接报错还糟,因为它会让系统表面上“看起来还有历史”,实际上却已经失去真正的恢复能力。

一个典型事故:字段改名很顺手,旧 run 全部睡死

某团队在重构 session store 时,把 resumeCursor 改成了更清晰的 wakePointer,并顺手把几个相关字段合并进新的 checkpoint 对象。单测都过了,新 run 也没问题。两周后,支持团队尝试恢复一批长时间等待外部回调的旧任务,结果全部失败。原因很直接:

  • 旧 event log 里只有 resumeCursor
  • 新 reader 只认识 wakePointer
  • 没有兼容映射,也没有 migration bridge

更棘手的是,这些任务不是“再跑一次就行”的类型,它们已经绑定了外部 side effect 和审批上下文。也就是说,字段改名本身不算大事,但它把一整批真实任务的恢复路径切断了。

最后修复不是补一条 ad hoc 脚本,而是确立了 session schema 的最小兼容原则:任何能影响 wake、resume、review 或 audit 的字段,在 reader 层必须至少兼容两个大版本。这条规则听上去保守,却直接降低了后续改结构时的心理负担,因为团队终于知道哪些东西绝不能“想改就改”。

真正该先做的,是把“能不能读旧 session”当成发布门槛

如果你正在改 session store,最值得先补的不是一套更漂亮的新结构,而是一个最小兼容检查:

  • 旧 session 能不能被新 reader 打开
  • 旧 session 能不能被正常 wake
  • 旧 session 的关键审计字段是否还可见
  • compaction 后的旧记录是否仍保留恢复所需锚点

只要这几项还没有被当成发布门槛,session schema migration 就总会看起来像一个“以后再说”的工程细节。但对 AI agent 来说,历史状态能不能继续被理解,本身就是系统可靠性的一部分。

先让旧 session 能读,再谈新结构多优雅。因为用户真正关心的,从来不是你现在的 schema 有多整洁,而是这套系统会不会在未来某一天突然不认识昨天的自己。

延伸阅读: