第172题:Agent 的状态 Schema 升级怎么办?
1. 核心回答
Agent 的状态 Schema 不能按照普通临时内存对象处理,因为生产系统里通常同时存在:
新代码 + 旧Checkpoint + 历史Event + 暂停中的长期任务 + 已经发生的外部副作用所以正确方案不是:
直接把State类改掉然后上线而是:
Versioned Schema → Backward-Compatible Read → Explicit Migrator → Invariant Validation → External Side-Effect Reconciliation → Safe Resume → Background Backfill → Drain Old Versions → Remove Compatibility Code我会给每个持久化状态显式保存:
schema_version checkpoint_version event_version graph/code_version恢复旧任务时:
读取旧Checkpoint ↓ 识别Schema版本 ↓ 迁移到当前内存Schema ↓ 校验业务不变量 ↓ 对账外部副作用 ↓ 判断哪些步骤已经Committed ↓ 只恢复未完成工作核心原则是:
Schema Migration 只能改变状态表示,不能导致已经发生的业务动作被错误地重新执行。
2. 为什么 Agent State 升级比普通数据库字段升级更复杂
普通 CRUD 服务通常主要关心:
Old Row → New Row但 Agent / Workflow 还保存执行位置。
例如:
{"schema_version":3,"current_node":"verify_vulnerability","completed_steps":["retrieve","run_sast"],"pending_action":"create_ticket"}这个状态不仅描述:
数据是什么还描述:
程序执行到哪里 下一步准备做什么 哪些副作用可能已经发生因此升级错误可能造成:
- Thread 无法恢复;
- 已完成节点重复执行;
- 外部 API 重复调用;
- 状态机跳转到错误节点;
- 新代码误解旧字段语义。
3. 需要区分四种 Version
不建议只有一个:
version = 2最好至少区分:
schema_version event_version checkpoint_version graph_version因为它们解决不同问题。
schema_version
表示 State 数据结构版本。
例如:
v1: risk_score v2: risk_score confidenceevent_version
表示历史 Event Payload 的版本。
例如:
FindingCreated.v1 FindingCreated.v2checkpoint_version
表示 Checkpoint 序列化和恢复协议版本。
graph_version
表示执行图、节点和状态转移逻辑版本。
这样发生兼容问题时才能定位:
到底是数据结构变了 还是事件变了 还是Graph执行语义变了4. 首先把 Schema Change 分类
我会把升级分成:
Backward-Compatible和:
Breaking两大类。
典型兼容变化:
新增Optional字段 新增有安全Default的字段 新增旧代码可以忽略的Metadata典型破坏变化:
字段删除 字段改名 字段类型改变 字段语义改变 Optional → Required 枚举值语义改变 Node Rename Node Removal两类不能使用完全相同的部署策略。
5. 新字段优先设计为 Optional
假设旧状态:
classStateV1:messages:listrisk_score:float新版本需要:
evidence_quality不要直接要求:
evidence_quality:float否则旧 Checkpoint 没有这个字段。
更安全的是:
evidence_quality:Optional[float]=None或者定义明确 Default。
于是:
Readv2(Statev1) Read_{v2}(State_{v1})Readv2(Statev1)
仍然能够成功。
这属于典型:
Additive Schema Evolution。
6. 字段删除不要一步完成
假设:
v1: risk_score准备改为:
v2: risk_level错误方案:
今天直接删除risk_score 明天全部代码只读risk_level只要还有一个旧 Checkpoint:
{"risk_score":0.91}恢复就可能失败。
推荐:
Phase 1 新增risk_level 保留risk_score Phase 2 Dual Read Dual Write / Conversion Phase 3 Backfill Phase 4 停止写risk_score Phase 5 确认旧Checkpoint已经Drain/Migrate Phase 6 删除risk_score即:
Expand–Migrate–Contract。
7. Rename 应该使用 Add-Then-Remove
例如:
risk_score → security_risk_score迁移期读取逻辑:
ifsecurity_risk_scoreisnotNone:score=security_risk_scoreelse:score=risk_score写入阶段逐步改成:
只写新字段旧任务清理以后,再删除:
risk_score这比直接 Rename 安全得多。
8. 类型变化也应该显式迁移
例如:
v1: risk_score: int 0~100变成:
v2: risk_score: float 0~1字段名完全相同,但语义已经不同。
如果新代码直接读取旧值:
80就可能解释成:
80.0而不是:
0.8因此必须执行:
$$
risk_{v2}
\frac{risk_{v1}}{100}
$$
并且通过:
schema_version决定使用哪一种解释。
9. 推荐显式 Migration Chain
例如:
v1 ↓ migrate_1_to_2 v2 ↓ migrate_2_to_3 v3 ↓ migrate_3_to_4 v4代码概念上:
whilestate.schema_version<CURRENT_VERSION:state=migrators[state.schema_version](state)这样:
v1 → v4仍然经过所有已验证迁移逻辑。
比维护:
v1_to_v4 v2_to_v4 v3_to_v4大量独立分支更容易测试。
10. Migration 必须满足确定性和幂等性
理想 Migrator 满足:
M(x)=y M(x)=yM(x)=y
并且重复执行:
M(y)=y M(y)=yM(y)=y
或者至少不会继续破坏数据。
Migration 不应该:
调用LLM决定字段 访问不稳定网络 随机生成业务状态 重新执行外部工具它应该尽量是:
Pure Data Transformation。
11. Migration 后必须重新验证 Invariant
反序列化成功不代表迁移正确。
例如 Agent State 可能要求:
completed_steps ∩ pending_steps = ∅或者:
current_node ∈ ValidNodes又或者:
status = completed ⇒ next_node = null所以 Migration 后应该执行:
ValidateInvariant(Snew) ValidateInvariant(S_{new})ValidateInvariant(Snew)
只有通过才能 Resume。
否则:
Quarantine → Manual Inspection比带着错误 State 继续执行安全。
12. Event 与 Checkpoint 不应该完全按同样方式迁移
如果系统使用 Event Log:
TaskCreated ToolCalled ToolSucceeded StateCommitted历史 Event 通常更适合作为:
Immutable Source of Truth。
不建议为了升级 Schema 直接把历史事实批量修改掉。
更常见的是:
Old Event ↓ Deserializer / Upcaster ↓ Current In-Memory Event例如:
ToolSucceeded.v1 ↓ upcast ↓ ToolSucceeded.v2历史数据仍保持:
v1应用层统一消费:
v213. Event Version 应该放进 Event Envelope
例如:
{"event_id":"...","event_type":"ToolSucceeded","event_version":2,"timestamp":"...","payload":{}}Consumer 根据:
event_type + event_version选择:
Deserializer / Upcaster而不是根据:
字段有没有出现猜数据属于哪个版本。
14. Checkpoint 可以采用 Lazy Migration
存量 Checkpoint 数量可能非常大。
没有必要部署前全部一次性迁移。
可以:
Load Checkpoint v2 ↓ migrate v2 → current ↓ Resume ↓ 下一次Checkpoint按current写入即:
Lazy Migration on Read。
优点:
- 部署快;
- 只迁移真正活跃的状态;
- 避免一次性大规模写入。
15. 同时可以做 Background Backfill
Lazy Migration 的问题是:
冷数据永远停留在旧版本所以后台可以逐批执行:
scan old checkpoints ↓ migrate ↓ validate ↓ write new storage version最终:
OldVersionCount→0 OldVersionCount\rightarrow0OldVersionCount→0
然后才能安全删除旧 Reader / Migrator。
这种模式可以理解为:
Lazy Migration + Eager Background Migration的组合。
16. 不要在旧数据未清完时删除旧 Reader
发布流程可以定义:
T0: 新代码能够读v1/v2,写v2 T1: 开始迁移v1 T2: 监控OldCheckpointCount T3: OldCheckpointCount = 0 且无旧Worker T4: 停止支持v1这比:
上线新代码 → 立即删除旧兼容逻辑安全得多。
17. Node Rename 比普通字段 Rename 更危险
假设 Checkpoint 中记录:
next_node = "security_review"新版本把节点改名:
review_security"旧任务恢复时:
security_review已经不存在。
于是无法确定从哪里 Resume。
因此节点 Rename 也应该采用:
新旧Node并存或显式映射:
security_review → review_security直到旧线程 Drain。
18. Graph Topology Change 必须检查运行中线程
例如:
v1: A → B → C改成:
v2: A → X → C如果旧 Checkpoint 正好停在:
B就必须回答:
它恢复后应该继续 B、进入 X,还是直接进入 C?
这不是数据 Schema 能单独解决的问题。
所以 Graph Upgrade 也应定义:
Resume Compatibility Contract19. 最危险的是 External Side Effect
假设 Agent 执行:
create_ticket()实际外部系统已经创建:
Ticket #123随后进程 Crash。
本地 Checkpoint 仍显示:
pending=create_ticket升级后如果恢复:
create_ticket()就可能产生:
Ticket #124这是典型:
Duplicate Side Effect。
20. 所以恢复前必须 Reconcile
正确恢复流程应该是:
Load Internal State ↓ Migrate Schema ↓ Read Side-Effect Record ↓ Query/Reconcile External System ↓ Determine: Committed / Not Committed / Unknown ↓ Resume即:
Resume≠ReplayEverything Resume \neq ReplayEverythingResume=ReplayEverything
而是:
$$
Resume
ReplayOnlyUncommittedWork
$$
21. 每个副作用步骤应该有 Operation ID
例如:
{"step_id":"create-ticket","operation_id":"task-37:create-ticket:v1","status":"started"}外部请求也携带:
operation_id如果调用重试:
operation_id保持不变。
外部系统如果支持 Idempotency Key,就可以识别:
这是同一次业务操作而不是创建第二份资源。
22. Retry 不等于 Exactly-Once
这是必须明确的工程边界。
网络可能出现:
Server已执行成功 ↓ Response丢失 ↓ Client认为失败 ↓ Retry所以:
“我只在错误时Retry”不能解决重复副作用。
对于有副作用的动作,应使用:
- Idempotency Key;
- Conditional Write;
- Unique Operation ID;
- Outbox;
- Compensation;
- External Reconciliation。
23. Checkpoint Commit 应该明确边界
一个 Step 可以抽象成:
PREPARED ↓ EXECUTING ↓ EXTERNAL_COMMITTED ↓ STATE_COMMITTED如果 Crash 发生在不同阶段,恢复逻辑不同。
例如:
PREPARED
安全执行。
EXECUTING
需要确认外部系统状态。
EXTERNAL_COMMITTED
不能重新执行副作用,应补提交内部状态。
STATE_COMMITTED
直接进入下一步。
24. External State 和 Internal State 双写需要特别处理
例如:
创建工单 + 更新Agent State无法天然放进同一个数据库事务。
可能出现:
Ticket成功 State失败或者:
State成功 Event发送失败可以根据架构采用:
- Transactional Outbox;
- Saga;
- Idempotency;
- Reconciliation Worker。
核心目标是:
让恢复过程能够判断真实世界究竟发生了什么。
25. 不可逆动作必须记录 Compensation 信息
例如:
Block IP如果允许撤销,则状态应记录:
action resource previous_state operation_id rollback_action升级失败需要回退时:
Compensating Action也必须能够被恢复和重试。
不能只回滚 Agent 数据库,却留下外部系统已经修改的状态。
26. 推荐完整 Checkpoint Envelope
例如:
{"checkpoint_id":"...","schema_version":4,"checkpoint_version":2,"graph_version":"2026.08.3","thread_id":"...","current_node":"verify","completed_steps":[],"pending_steps":[],"operations":[{"operation_id":"...","type":"create_ticket","status":"external_committed","external_ref":"ticket-123"}],"artifacts":[],"event_offset":182,"created_at":"..."}这样恢复程序有足够上下文进行判断。
27. Schema 不应该保存所有对话文本来代替业务状态
例如:
“从聊天记录里让LLM推断任务执行到了哪里”不适合作为恢复机制。
真正业务状态应该是显式字段:
status current_node completed_steps pending_actions artifact_refs operation_ids approval_stateConversation 可以是:
Evidence / Context但不能代替:
Authoritative Execution State28. Artifact 应通过引用保存
例如大型:
Code Report SARIF Patch Trace Retrieved Documents最好保存:
artifact_id version hash location而不是把所有内容直接复制进 Checkpoint。
Schema 升级时:
Artifact Metadata与:
Artifact Content也可以独立演化。
29. 发布过程建议使用 Expand–Migrate–Contract
完整上线过程:
1. Expand 新代码能同时读取Old/New Schema 2. Deploy 先发布兼容Reader/Migrator 3. Migrate Lazy + Background Backfill 4. Verify 迁移率、失败率、不变量检查 5. Drain 等待旧Worker/旧Checkpoint退出 6. Contract 删除Deprecated字段和旧代码这样可以支持 Rolling Deployment。
30. Rollback 也必须在设计中
假设:
v4代码已经写出:
v4 Checkpoint随后发现 bug,需要回滚:
v3代码必须提前回答:
v3 能不能读取 v4?
所以升级前需要定义:
Forward Compatibility Window至少在灰度阶段,新 Writer 不应立即生成旧版本完全无法读取的数据,或者必须保留:
Down-Migration / Compatibility Reader否则应用代码可回滚,数据却无法回滚。
31. Migration 需要 Shadow / Dry-Run
上线前可以读取真实旧 Checkpoint:
Old State ↓ Migrator ↓ New State ↓ Validate但不写回生产。
统计:
Migration Success Rate Unknown Version Count Invariant Failure Rate Missing Field Rate Unsupported Node Count确认安全以后再正式迁移。
32. 必须测试跨多个历史版本升级
不能只测:
v3 → v4真实生产中可能长期暂停着:
v1 v2 v3任务。
因此 Regression Matrix 至少为:
v1 → current v2 → current v3 → current current → current每条链都要测试。
33. 必须测试 Migration 中途 Crash
例如:
读取v2 ↓ 写v4 ↓ 进程Crash恢复后再次执行 Migration,不应该:
- 重复生成资源;
- 写出半迁移状态;
- 丢失历史事件。
因此 Migrator 写入最好:
atomic并通过:
checkpoint_id version compare-and-swap / optimistic concurrency避免并发覆盖。
34. 必须测试两个 Worker 版本共存
Rolling Upgrade 中可能出现:
Worker v3 + Worker v4同时运行。
需要确保:
v3不会错误消费v4-only状态 v4能够消费v3状态或者通过:
Worker Routing / Version Pinning隔离不同版本任务。
不能只在“所有实例同时瞬间升级”的理想条件下测试。
35. 推荐的 Migration Test Matrix
至少包括:
| 场景 | 预期 |
|---|---|
| 新增 Optional 字段 | 旧状态正常恢复 |
| 删除字段 | Deprecated 窗口正常 |
| Rename 字段 | Add-then-remove 正常 |
| 类型变化 | Migrator 正确转换 |
| 旧 Node Name | 能映射或安全拒绝 |
| 多历史版本 | 全部能升级 |
| Migration 中断 | 可重新执行 |
| 并发 Worker | 无重复状态提交 |
| 外部动作已成功、本地未提交 | 不重复执行 |
| 外部动作状态未知 | Reconcile / Human |
| Rollback 到旧代码 | 兼容窗口内可恢复 |
| 非法旧状态 | Quarantine,不继续执行 |
36. 关键监控指标
上线 Schema Migration 时至少观察:
Checkpoint Count by Version Migration Success Rate Migration Failure Rate Invariant Violation Rate Old-Version Read Rate Unsupported Version Count Resume Failure Rate Duplicate Side-Effect Count Reconciliation Failure Rate Manual Recovery Count特别是:
DuplicateSideEffectCount DuplicateSideEffectCountDuplicateSideEffectCount
应该作为高优先级事故指标。
37. 什么时候可以删除旧兼容逻辑
至少满足:
OldCheckpointCount=0 OldCheckpointCount=0OldCheckpointCount=0
并且:
没有旧Worker 没有旧Event Consumer 没有可恢复旧Snapshot Rollback Window结束 备份恢复流程已经验证之后才进入:
Contract Phase否则旧 Reader / Migrator 仍然有价值。
38. 什么结果会直接推翻“升级安全”的主张
以下任何一种都属于严重失败:
旧Checkpoint无法Resume已完成Step被重复执行创建重复工单/重复封禁/重复付款Migration后业务Invariant被破坏Rolling Upgrade期间新旧Worker互相破坏状态新版本上线后无法Rollback历史Event因为Schema变化无法Replay因此验收标准不能只是:
Database migration completed successfully真正的成功标准是:
旧任务仍能安全恢复,而且外部世界不会因为 Schema Migration 产生重复或错误副作用。
39. 当前资料能证明到什么程度
原表已经明确给出:
- State 应显式建模;
- 区分 Conversation、业务 State、Artifact 与 External Side Effect;
- Checkpoint 保存 Version、Completed Steps、Idempotency Key 和引用;
- Schema Upgrade 使用版本化 Migrator;
- 保持向后读取;
- 恢复前先对账外部系统;
- Replay 只执行未提交步骤。
当前资料没有提供真实:
Checkpoint Schema Migrator Implementation Historical Version Count Migration Failure Rate Resume Success Rate Duplicate Side-Effect Test Rollback Test所以不能声称:
“当前系统已经验证可以无损升级。”这些仍需要真实实现与故障注入测试。
40. 面试时可以压缩成下面这段
Agent 的 State Schema 升级不能只做一次数据库字段 Migration,因为生产里可能还有旧 Checkpoint、历史 Event、暂停中的线程以及已经发生的外部副作用。
我的做法首先是给 State、Checkpoint、Event 和 Graph 分别版本化。兼容变化,例如新增字段,优先设计成 Optional 或带安全 Default;字段 Rename、删除和类型语义变化则采用 Expand–Migrate–Contract,先同时支持新旧字段,经过 Dual Read、Backfill 和旧线程 Drain 后再删除旧结构。
恢复旧 Checkpoint 时,我不会直接让新代码消费,而是先读取schema_version,按 Migrator Chain 转成当前内存 Schema,再验证业务 Invariant。历史 Event 尽量保持 immutable,通过event_version + upcaster转成当前表示;大量旧 Checkpoint 可以采用 Lazy Migration on Read,同时后台逐渐迁移到最新 Storage Version。
最重要的是外部副作用。假设旧任务已经创建工单,但本地 Checkpoint 在提交前 Crash,那么升级以后不能重新执行create_ticket。所以每个副作用步骤都要保存 operation/idempotency key 和 commit 状态,恢复前先跟外部系统 Reconcile,只执行真正未完成的步骤。Retry 本身不等于 exactly-once。
部署采用 Expand → Migrate → Verify → Drain → Contract,并且要支持 Rolling Upgrade 和 Rollback。测试至少覆盖多历史版本、字段/节点 Rename、Migration 中断、新旧 Worker 并发、旧线程 Resume、外部副作用已发生但本地状态未提交等情况。
所以一句话回答:
Schema 升级的目标不是“把旧 JSON 变成新 JSON”,而是让任何历史 Checkpoint 在新代码下都能被确定地解释、验证并安全恢复,同时保证已经发生的外部副作用不会因为迁移或 Replay 被重复执行。
41. 来源
- LangChain / LangGraph Documentation,Backward Compatibility:旧 Checkpoint 会由最新部署代码恢复;新增必填字段、删除或改名 State Key、删除运行中线程依赖的 Node 都可能破坏兼容性,推荐 Optional、Deprecation 和 Add-Then-Remove。
- LangChain / LangGraph Documentation,Persistence:State 通过 Checkpoint 持久化,可从最后成功步骤恢复;Pending Writes 可以避免已成功节点在部分失败后被无谓重跑。
- Microsoft Azure Architecture Center,Event Sourcing Pattern:历史 Event 适合作为不可变事实来源;Schema 演化可采用 Tolerant Deserialization、Event Versioning 和 Upcasting。
- Protocol Buffers Documentation,Updating a Message Type:Schema 演化必须保持 Wire Compatibility;已使用字段编号不能随意改变或复用,删除字段后应保留其编号。
- Kubernetes Documentation,Storage Version Migration:展示多 Schema Version 共存、转换以及将存量对象逐步迁移到新 Storage Version 的生产模式。
- AWS Durable Execution SDK,Idempotency and Retries:Retry 不自动提供端到端 Exactly-Once;存在外部副作用时应根据操作性质采用 Idempotency Token 或更严格执行语义。
- AWS Prescriptive Guidance,Transactional Outbox Pattern:处理业务状态更新与事件/外部系统写入之间的 Dual-Write 一致性,并强调消费者应能够幂等处理重复消息。
- Microsoft Azure Architecture Center,Compensating Transaction Pattern:长事务需要记录已完成步骤和对应补偿动作;重试步骤应尽可能设计为 Idempotent,并允许失败后从已记录进度继续恢复。