Appearance
昨天保存的 Run,今天的新代码还能恢复吗:State Schema Versioning 与 Migration
前置:哪些信息能用于恢复:Memory、RAG、Event Log 与 Crash Recovery
核心问题:昨天按旧 schema 保存的 Run,今天用新代码恢复时怎样识别版本、迁移状态并拒绝不兼容数据?
一个常被忽略的长任务故障
周五晚上 Run 在这里暂停:
json
{
"schema_version": 3,
"status": "WAITING_APPROVAL",
"tool_call": {"name": "deploy", "args": {"env": "prod"}}
}周一服务升级后,deploy Tool 的参数变成:
text
environment + region + change_ticket如果新代码直接把旧 checkpoint deserialize 后继续,风险比“读取失败”更大:它可能成功读取,但用新的语义解释旧状态。
所以 durable execution 必须同时回答:状态保存了什么版本?当前 Runtime 是否有资格继续它?
区分四种 Version
不要只放一个模糊的 version:
text
state_schema_version → Run/Node/Checkpoint 数据结构
plan_schema_version → StructuredPlan / Graph contract
tool_schema_version → 已批准/待执行 Tool 参数契约
runtime_build/version → 解释这些状态的代码版本RunManifest 记录“当时运行了什么”;state/event schema version 负责“今天还能不能正确读”。两者用途不同。
Resume 前做 Compatibility Check
恢复入口不要直接:
python
state = StateVCurrent(**json.loads(raw))
run(state)而应该先经过:
text
load raw checkpoint
→ inspect schema/version + manifest
→ compatible? direct decode
→ migratable? upcast/migrate
→ incompatible? reject / fork / manual intervention
→ revalidate pending external actions
→ resume特别是 pending approval/tool call:用户批准的是旧参数。即使状态结构可以迁移,也不能擅自把旧 approval 映射成一个语义更宽的新 Tool 调用。
Snapshot Migration 与 Event Upcasting
Snapshot / Checkpoint
可以把 v2 snapshot 明确迁移为 v3:补默认字段、重命名字段、拆分结构。Migration 应是 deterministic 且可测试的纯转换。
Event Log
历史 event 不建议原地改写。更常见的是读取时做 upcast:
text
TOOL_REQUESTED v1
→ upcaster
→ TOOL_REQUESTED v2 domain shape
→ reducer如果新语义无法无损映射,就明确标记 unsupported,而不是猜。
部署兼容窗口
只要系统允许 Run 跨小时/跨天存在,rolling deploy 就会出现“旧 Run + 新 Worker”组合。生产策略至少要选择一种:
- Backward-compatible window:新版本能读最近 N 个 state/event 版本;
- Version-pinned worker:旧 Run 继续路由给兼容 worker;
- Explicit migration:部署前迁移 paused state;
- Fail closed / fork:不兼容时拒绝直接 resume,创建人工确认的新分支。
选择取决于任务风险和系统复杂度,但不能假设所有 paused Run 都会在发布前结束。
Failure Modes
“JSON 能反序列化”就认为可恢复
结构兼容不代表业务语义兼容,尤其是待执行 Tool、approval 和 idempotency key。
处理: resume compatibility 是显式 policy;高风险 pending action 版本变化时 fail closed 并重新审批。
直接修改历史 Event payload
审计、重放和旧 trace 会失去稳定含义。
处理: append-only 历史保持原样,通过 event version + upcaster 读取。
如何验证
建立真实旧版本 fixtures:
| Fixture | 新版本期望 |
|---|---|
| v2 checkpoint 可无损迁移 | migrate 后状态等价 |
| v1 event 可 upcast | reducer 得到预期 state |
| Tool schema 已扩大权限 | resume 被拒绝并要求重新审批 |
| 完全未知 schema_version | fail closed,不默默填默认值 |
自测
- 旧 checkpoint 缺少新版本必填字段。Resume 时是静默填默认值还是执行显式 migration?怎样判断安全?
- Schema migration 改变了状态语义但版本号没变,会让 replay/resume 出现什么隐蔽问题?