拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent 的状态 Schema 升级怎么办?

Agent 的状态 Schema 升级怎么办?

第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 confidence

event_version

表示历史 Event Payload 的版本。

例如:

FindingCreated.v1 FindingCreated.v2

checkpoint_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

应用层统一消费:

v2

13. 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 Contract

19. 最危险的是 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_state

Conversation 可以是:

Evidence / Context

但不能代替:

Authoritative Execution State

28. 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. 当前资料能证明到什么程度

原表已经明确给出:

  1. State 应显式建模;
  2. 区分 Conversation、业务 State、Artifact 与 External Side Effect;
  3. Checkpoint 保存 Version、Completed Steps、Idempotency Key 和引用;
  4. Schema Upgrade 使用版本化 Migrator;
  5. 保持向后读取;
  6. 恢复前先对账外部系统;
  7. 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. 来源

  1. LangChain / LangGraph Documentation,Backward Compatibility:旧 Checkpoint 会由最新部署代码恢复;新增必填字段、删除或改名 State Key、删除运行中线程依赖的 Node 都可能破坏兼容性,推荐 Optional、Deprecation 和 Add-Then-Remove。
  2. LangChain / LangGraph Documentation,Persistence:State 通过 Checkpoint 持久化,可从最后成功步骤恢复;Pending Writes 可以避免已成功节点在部分失败后被无谓重跑。
  3. Microsoft Azure Architecture Center,Event Sourcing Pattern:历史 Event 适合作为不可变事实来源;Schema 演化可采用 Tolerant Deserialization、Event Versioning 和 Upcasting。
  4. Protocol Buffers Documentation,Updating a Message Type:Schema 演化必须保持 Wire Compatibility;已使用字段编号不能随意改变或复用,删除字段后应保留其编号。
  5. Kubernetes Documentation,Storage Version Migration:展示多 Schema Version 共存、转换以及将存量对象逐步迁移到新 Storage Version 的生产模式。
  6. AWS Durable Execution SDK,Idempotency and Retries:Retry 不自动提供端到端 Exactly-Once;存在外部副作用时应根据操作性质采用 Idempotency Token 或更严格执行语义。
  7. AWS Prescriptive Guidance,Transactional Outbox Pattern:处理业务状态更新与事件/外部系统写入之间的 Dual-Write 一致性,并强调消费者应能够幂等处理重复消息。
  8. Microsoft Azure Architecture Center,Compensating Transaction Pattern:长事务需要记录已完成步骤和对应补偿动作;重试步骤应尽可能设计为 Idempotent,并允许失败后从已记录进度继续恢复。
返回列表