很多团队在做 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 Write、AI agent Harness 崩溃恢复与 Wake 流程 和 AI agent 工具 Schema 演进兼容 一起看。
Session schema 和 tool schema,看起来像一回事,其实不是一回事
| 对象 | 变化内容 | 最容易出问题的地方 |
|---|---|---|
| Tool Schema | 参数名、必填字段、返回结构 | 新工具调用读不懂旧参数 |
| Session Schema | event 类型、状态字段、checkpoint 结构、policy 引用 | 旧 run 无法 wake、resume 或 replay |
| Derived Memory Schema | 摘要、长期记忆、召回键 | 旧记忆继续被错误召回 |
Tool schema 主要影响单次调用能不能继续跑,session schema 影响的是“这段历史还能不能被系统重新理解”。后者的代价往往更大,因为它直接牵涉恢复和审计。
最稳的迁移方式,通常不是一次性重写,而是 reader 先兼容
很多团队一想到迁移,第一反应就是把旧 session 全部改写成新结构。这种方式在小规模时可行,但只要历史数据量上来,风险就很高:
- 改写窗口长
- 回滚困难
- 一旦某种边缘旧事件没被覆盖,旧 run 会在最需要的时候才爆出来
更稳的顺序通常是:
- 新 reader 先支持读旧结构
- 新 writer 开始写新结构或双写过渡结构
- 后台再慢慢把高价值旧 session 迁到新格式
- 等确认没有重要流量再退役旧 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 有多整洁,而是这套系统会不会在未来某一天突然不认识昨天的自己。
延伸阅读:


