直接回答

AI Agent 状态持久化,是让被中断的工作流在不丢失已授权进度、也不重复危险副作用的前提下继续运行。它不只是保存消息,而是定义恢复契约、原子持久化状态转换、隔离非确定性调用、让外部副作用可幂等或可对账、安全迁移旧状态,并用崩溃测试证明恢复行为。

Checkpoint 只保存某个边界上的数据。它不会自动重启 Worker,不保证 Exactly-once,不验证被记住的事实,也不能证明业务动作正确。

状态持久化不等于长期记忆

执行状态和长期记忆跨越的是不同边界。

数据类别 范围 示例 权威来源
Workflow State 单次运行 当前节点、重试次数、待审批 工作流运行时
Checkpoint 可恢复快照 接受 Tool Result 后的状态 持久化层
Effect Ledger 外部动作状态 支付 API 已接受退款请求 应用协议
Long-term Memory 跨运行 已确认偏好或历史情景 受治理记忆服务
Business Record 领域事实 订单状态、账户余额 System of Record
Artifact 大型不可变输出 报告、图片、源码归档 对象/制品存储
Audit Evidence 取证证据 Actor、策略版本、状态转换 追加式审计存储

不要从 Checkpoint 恢复一条过期订单状态后继续把它当作当前事实。快照可以保存订单 ID 和上次观察到的版本;恢复后必须重新向权威系统核验可变事实。

AI Agent 记忆指南解释什么信息可以成为长期记忆,Agent 记忆与删除权负责保留与删除;本文只讨论执行中断与安全续跑。

先定义恢复契约

只有团队明确“恢复成功”是什么,持久化设计才可测试。

契约字段 工程问题
Run Identity 哪个 Tenant、Workflow、Run 和 Release 拥有状态?
RPO 最多允许丢失多少已接受进度?
RTO 多久内必须重新进入可处理状态?
Checkpoint Boundary 哪个转换前后提交状态?
Effect Semantics Tool Call 能否重试、去重、补偿,还是只能对账?
Resume Authority 哪个 Worker 或 Operator 有权恢复?
State Compatibility 哪些代码和 Schema 版本能读取快照?
Retention 何时清理 Checkpoint、History 与 Artifact?
Terminal States 哪些结果必须停止自动恢复?
flowchart LR A["带 Run ID 的触发器"] --> B["加载已授权状态"] B --> C["校验 Workflow 与 Schema 版本"] C --> D["选择下一个合法转换"] D --> E["执行纯计算或受控副作用"] E --> F["提交状态与证据"] F --> G{"已进入终态?"} G -- "否" --> D G -- "是" --> H["关闭 Run 并保留证据"]

“从中断点继续”仍然不够明确。合格契约必须说明:是在不确定副作用之前恢复,还是在已确认副作用之后继续,或进入需要观察和人工处理的对账状态。

把 Agent 建模为版本化状态机

应持久化显式状态与转换,不要序列化整个进程堆。

json
{
  "tenant_id": "tenant_42",
  "workflow_id": "refund-review",
  "workflow_version": "3.2.0",
  "run_id": "run_01J...",
  "state_version": 17,
  "status": "waiting_for_approval",
  "next_transition": "execute_refund",
  "input_refs": ["case:8472"],
  "completed_steps": ["load_case", "policy_check", "draft_decision"],
  "pending_effects": [],
  "approval": {
    "required_role": "refund_manager",
    "request_id": "approval_91"
  },
  "budgets": {
    "steps_remaining": 4,
    "cost_remaining": 0.72
  },
  "checkpoint_schema": 2,
  "created_at": "2026-08-09T10:20:00Z"
}

大型输出只存引用,不能在每个快照里重复 Blob。排除凭据和临时 Client Object。应保存解释或复现决定所需的 Policy、Prompt、Model、Tool 和代码 Release ID,但不能假设旧供应商或模型会永久可调用。

每个 Checkpoint 都存在崩溃窗口

外部副作用会形成结果不确定边界。

sequenceDiagram participant W as Worker participant S as State Store participant T as External Tool W->>S: 提交 Intent 与 Idempotency Key W->>T: 用同一 Key 执行副作用 T-->>W: 返回 Accepted 与 Effect ID W->>S: 提交 Effect ID 与下一状态

逐一考虑故障点:

  1. Intent 提交前崩溃:恢复时看不到已授权副作用,可以重新规划。
  2. Intent 提交后、Tool Call 前崩溃:恢复时使用同一 Key 执行。
  3. Tool 已执行、响应返回前崩溃:结果不明确,应按 Key 查询或进入对账。
  4. 已收到响应、状态提交前崩溃:重试必须返回同一结果或识别既有 Effect。
  5. 状态提交后崩溃:恢复后进入下一状态,不再重复 Effect。

因此,Checkpoint 无法独自保证 Exactly-once。Durable Execution 产品可以协调 Event History 与重试,但应用层副作用仍需明确协议。

选择 Checkpoint 确认语义

只定义保存频率还不够,Runtime 必须说明一次写入相对于下一步骤何时才算持久化。当前 LangGraph 文档提供三种 Durability Mode:

模式 持久化边界 适用场景
sync 下一步骤开始前完成当前步骤持久化 无法接受丢失最近转换的高影响工作流
async 下一步骤运行时异步持久化 可以安全重放最近一步,且需要降低 Checkpoint 延迟
exit Graph 退出时才持久化 不要求中途恢复的短时、可丢弃任务

这些名称属于具体框架,但决策本身具有通用性。应把选定模式写进 Release Manifest,在 RPO 中说明可能丢失的窗口,并分别在确认前后注入进程崩溃。sync 可以缩小 Checkpoint 丢失窗口,但仍无法消除外部副作用周围的崩溃间隙。

让副作用可幂等或可对账

每个重要 Tool Call 都需要 Effect Protocol。

text
effect_key = tenant_id + workflow_id + run_id + logical_step

协议至少持久化:

  • 稳定 Effect Key;
  • 规范化 Request Hash;
  • 目标系统与 Operation;
  • planned、executing、confirmed、rejected、ambiguous、compensated 状态;
  • Provider Receipt 或 Resource ID;
  • Attempt Count 与 Last Error;
  • 对账和补偿指令。
副作用 优先策略
创建支付或退款 Provider Idempotency Key + Receipt 查询
发送邮件 Outbox Row + 发送服务 Message ID
更新自有数据库 状态与 Outbox 在同一事务提交
无去重能力的第三方 API Read-after-write 对账或人工门禁
不可逆物理动作 前置条件、审批、窄命令、动作后验证

不要重试所有异常。认证失败、策略拒绝、无效请求、预算耗尽和不可逆副作用结果不明,通常都应进入不同状态,而非重发请求。

在 Snapshot、Event History 与混合模式之间选择

Snapshot 与 Event History 优化的是不同恢复成本。

模型 优势 成本与风险
最新 Snapshot 简单、恢复快 历史有限;部分写入需要事务保护
版本化 Snapshot 支持 Time Travel 与回滚 存储增长、Schema 迁移
追加式 Transition Log 审计与确定性重建 回放成本、事件演进、副作用隔离
Snapshot + Tail Log 恢复快且保留证据 组件更多,需要一致性检查

Event Sourcing 不会天然更好。只有编排逻辑能相对于已记录事件确定性执行时,Replay 才成立。模型调用、时钟、随机数、网络响应和 Tool Effect 必须被记录,或隔离到受控 Activity;否则新一轮 Replay 可能走向不同分支。

Temporal 文档通过 Event History 与确定性 Replay 描述 Durable Workflow;LangGraph 把 Checkpoint 定义为线程级 Graph State Snapshot,把 Store 定义为跨线程应用数据。二者是不同契约,不是可以互换的产品标签。

Temporal 的 LangGraph Plugin 于 2026 年 7 月进入 Public Preview,它把 Graph Node 映射为 Temporal Activity,让独立 Durable Runtime 在 Worker 丢失后重新调度工作。这进一步说明“保存状态”和“拥有执行恢复责任”属于两个层次。它不证明所有 LangGraph 部署都需要 Temporal;采用前仍要核验 Preview 状态、支持 API、Payload Limit、Retry Policy 与 Migration Behavior。

按契约选择存储,而非照抄架构图

Redis + 关系数据库 + 向量数据库不是成熟度模型。

要求 候选能力
原子提交 Checkpoint 与 Effect Intent 事务型关系或文档数据库
条件状态转换 Compare-and-swap、Row Version 或事务锁
大型不可变 Artifact 带 Checksum 的对象存储
低时延热读 只有数据库不满足 SLO 时才加 Cache
语义候选召回 Vector Index,可位于主数据库内
确定性 Event Replay Durable Workflow / Event History Engine
搜索与取证分析 从权威记录派生的 Audit/Event Index

单一数据库可以是正确起点。当前数据库集成能够同时保存 JSON Checkpoint、Namespace 长期记录与 Vector Index。拆分存储会引入复制延迟、部分失败、权限重复、备份协调和删除传播;只有实测收益超过这些成本时才应加新服务。

WAL 是数据库持久化机制,不是 Agent 架构。PostgreSQL 用 Write-ahead Log 恢复已提交的数据库变化,但它不知道支付 API 是否接受请求,也不知道 Agent 应从哪个语义步骤继续。

控制并发与所有权

除非工作流显式支持并行分支,同一 Run 在任一时刻只能由一个 Owner 推进状态。

使用:

  • 基于 state_version 的乐观并发控制;
  • 带 Fencing Token 的 Worker Lease;
  • 并行结果的 Branch ID 与确定性 Reducer;
  • Effect Key 唯一约束;
  • 按 Tenant 与 Run 限定的授权;
  • 普通重试不能重新打开的单调终态。
sql
UPDATE agent_runs
SET state = :next_state,
    state_version = state_version + 1
WHERE tenant_id = :tenant_id
  AND run_id = :run_id
  AND state_version = :expected_version
  AND status NOT IN ('completed', 'cancelled', 'failed');

更新零行意味着 Worker 已经输掉竞争,或 Run 已关闭。它必须重新读取,不能覆盖较新的状态。

多 Agent 系统也不应共享一个可变 Memory Object。每个分支拥有明确的输入、输出和所有权,通过 Reducer 或能识别冲突写入的 Coordinator 合并。

让旧状态跨 Release 迁移

长任务可能比创建它的代码版本存活更久,因此每个 Checkpoint 都必须保存 Workflow 与 Schema 版本。

发布契约应定义:

  1. 支持读取哪些旧 Schema;
  2. 如何通过纯函数、已测试的 Migration 升级;
  3. Transition 重命名或删除后的兼容规则;
  4. 引用的 Tool 或 Model 已下线时如何处理;
  5. 无法安全迁移时进入哪个 Quarantine 状态;
  6. 新版本写入的 Checkpoint 能否被旧版本回滚读取。

禁止从不可信 Checkpoint 反序列化任意类型。使用受限 Schema,校验字段和大小,必要时验证完整性,并把已存 Model/Tool 文本继续视为不可信数据。

可运行的恢复决策

下面的 Go 标准库程序不执行 Tool,而是先验证持久化契约,再根据证据选择唯一安全的恢复动作:

go
package main

import (
	"errors"
	"fmt"
)

type EffectStatus string
type RecoveryAction string

const (
	effectNone      EffectStatus = "none"
	effectPlanned   EffectStatus = "planned"
	effectConfirmed EffectStatus = "confirmed"
	effectAmbiguous EffectStatus = "ambiguous"

	actionExecute   RecoveryAction = "execute"
	actionAdvance   RecoveryAction = "advance"
	actionReconcile RecoveryAction = "reconcile"
	actionStop      RecoveryAction = "stop"
)

type Checkpoint struct {
	Status          string
	EffectStatus    EffectStatus
	EffectKey       string
	Attempts        int
	MaxAttempts     int
	SchemaVersion   int
	WorkflowRelease string
	DurabilityMode  string
}

func recoveryAction(checkpoint Checkpoint) (RecoveryAction, error) {
	if checkpoint.SchemaVersion < 1 || checkpoint.WorkflowRelease == "" {
		return "", errors.New("checkpoint identity is incomplete")
	}
	if checkpoint.Attempts < 0 || checkpoint.MaxAttempts < 1 {
		return "", errors.New("invalid attempt budget")
	}
	if checkpoint.DurabilityMode != "sync" &&
		checkpoint.DurabilityMode != "async" &&
		checkpoint.DurabilityMode != "exit" {
		return "", errors.New("unsupported durability mode")
	}
	switch checkpoint.Status {
	case "completed", "cancelled", "failed":
		return actionStop, nil
	case "running":
	default:
		return "", errors.New("unknown workflow status")
	}
	switch checkpoint.EffectStatus {
	case effectConfirmed:
		return actionAdvance, nil
	case effectAmbiguous:
		if checkpoint.EffectKey == "" {
			return actionStop, nil
		}
		return actionReconcile, nil
	case effectPlanned:
		if checkpoint.EffectKey == "" || checkpoint.Attempts >= checkpoint.MaxAttempts {
			return actionStop, nil
		}
		return actionExecute, nil
	case effectNone:
		return actionStop, nil
	default:
		return "", errors.New("unknown effect status")
	}
}

func mustAction(checkpoint Checkpoint, want RecoveryAction) {
	got, err := recoveryAction(checkpoint)
	if err != nil || got != want {
		panic(fmt.Sprintf("got=%q err=%v want=%q", got, err, want))
	}
}

func fixture(effect EffectStatus, key string) Checkpoint {
	return Checkpoint{
		Status:          "running",
		EffectStatus:    effect,
		EffectKey:       key,
		Attempts:        1,
		MaxAttempts:     3,
		SchemaVersion:   2,
		WorkflowRelease: "[email protected]",
		DurabilityMode:  "sync",
	}
}

func main() {
	mustAction(fixture(effectPlanned, "run-7:send"), actionExecute)
	mustAction(fixture(effectAmbiguous, "run-7:send"), actionReconcile)
	mustAction(fixture(effectConfirmed, "run-7:send"), actionAdvance)
	mustAction(fixture(effectPlanned, ""), actionStop)
	fmt.Println("execute=true reconcile=true advance=true missing_key_stopped=true")
}

执行 go run recovery_gate.go,预期输出:

text
execute=true reconcile=true advance=true missing_key_stopped=true

关键不是“自动继续”,而是明确拒绝:结果不明时必须对账,缺少 Effect Identity 时停止,已确认副作用则推进而不是再次执行。

把恢复作为产品属性测试

Happy Path 不能证明持久化可靠,应建立故障矩阵。

注入位置 必须满足的断言
Model Call 前 不声称已有进度,Retry Budget 未损坏
Model 响应后、Commit 前 可重新计算响应,不重复 Effect
Tool Request 前 Intent 与 Key 可恢复
Tool Effect 后、Receipt 前 Run 进入 Reconciliation
Receipt 后、状态 Commit 前 去重后获得同一 Effect ID
Checkpoint Write 期间 上一个已提交版本仍可读取
Schema Migration 期间 成功迁移或进入 Quarantine
等待审批期间 Worker 丢失后审批身份与状态仍存在
两个 Worker 同时恢复 Fencing/Version 只允许一次转换
Cancel 与 Retry 竞争 Cancel 终态不能被重新打开

持续观测:

  • 按故障点切分的恢复成功率;
  • Recovery Time 与 Progress Loss;
  • 重复或未追踪 Effect 数;
  • 需要人工处理的 Ambiguous Effect;
  • 过期或不兼容 Checkpoint 数;
  • Checkpoint 写入时延与大小;
  • Replay 长度与 Migration 失败;
  • Retention 与 Deletion 完成率。

Agent 重启后即使生成了合理答案,只要重复退款或忽略取消,就属于恢复失败。

框架映射,而不是框架锁定

概念 LangGraph 示例 Durable Workflow 示例
Run Identity thread_id 与 Checkpoint ID Workflow ID 与 Run ID
State Persistence Checkpointer Event History + Workflow State
Cross-run Data Store 外部应用 Store
Resume 从持久化 Thread State 调用 Replay History 后继续
Side Effect Node/Tool 仍需应用协议 Managed Activity 仍需业务幂等

框架文档会变化。Recovery Contract、State Schema、Effect Key 和 Failure Test 应由应用自己管理,使替换后端不会改变业务语义。

常见失败模式

  • 只保存 Message Log:对话存在,Pending Effect 和审批状态丢失。
  • Checkpoint-after-effect Gap:外部动作成功,崩溃后又执行一次。
  • 整体对象序列化:Secret、Client 和不兼容运行时类型进入存储。
  • Snapshot 充当业务事实:恢复的旧数据绕过 System of Record。
  • 全局 Session Key:Tenant、用户或并行 Run 相互覆盖。
  • Last-write-wins Recovery:旧 Worker 覆盖新 Checkpoint。
  • History 无上限:没有 Retention 或 Compaction。
  • Replay Drift:新代码以不同方式解释旧 Transition。
  • Cache 充当权威存储:Cache Eviction 直接变成数据丢失。
  • Vector Store 混淆:把语义检索误认为 Workflow Durability。

生产检查清单

  • [ ] 每个 Run 包含 Tenant、Workflow、Release、Run 和 State Version。
  • [ ] 终态、预算、审批和 Pending Effect 都是显式字段。
  • [ ] Checkpoint 原子提交,并执行条件版本更新。
  • [ ] 每个外部 Effect 都可幂等、可补偿或可对账。
  • [ ] 恢复后重新核验可变业务事实。
  • [ ] State Schema 具备兼容与 Quarantine 路径。
  • [ ] 安全校验和反序列化持久化内容。
  • [ ] Retention 覆盖 Checkpoint、Log、Artifact、Index 与 Backup。
  • [ ] Crash Test 覆盖 Effect 与 Commit 边界两侧。
  • [ ] Dashboard 暴露 Ambiguous Effect 与 Stuck Run。

相关资源

一手资料