
书名:《Agent Runtime 工程化:生产级 AI Agent 架构、运行时与工程实践》
作者:陈堂会
平台:极客日志(https://zeeklog.com)
X:@Megick_com
联系邮箱:[email protected]
章节:第八章 Checkpoint、Resume、Rollback
第八章 Checkpoint、Resume、Rollback
长任务一定会中断。问题是:失败后怎么继续,而不是重来?
Agent Runtime 一旦进入真实工程任务,就会遇到长运行:改多个文件、跑多轮测试、安装依赖、查文档、等待用户确认。浏览器刷新、进程崩溃、网络超时、模型失败、用户临时离开,都可能打断 run。如果没有 checkpoint,Agent 只能从头再来;从头再来又可能重复执行副作用。
可恢复执行系统要做的事很朴素:把'运行到哪里了'保存下来,而且能验证、能恢复、能回滚。难点也在这里。保存聊天记录不等于保存运行状态,保存文件 diff 也不等于知道哪些工具已经执行。

图 8-1:可恢复执行不是简单保存聊天记录。它必须同时保存对话状态、工具执行状态、文件变更和审批记录。
8.1 checkpoint 保存的是一致性
一个 checkpoint 不只是'某个时刻的数据包'。它保存的是几个系统状态之间的一致性:消息历史、run state、工具账本、文件变更、审批记录、trace 位置。恢复时,这几样必须对得上。
一个 checkpoint 至少包含:
- session id、run id、step id;
- message history 或其可重建引用;
- 当前计划和 stop condition 状态;
- tool call 计划、执行结果、是否有副作用;
- 审批记录;
- 文件系统变更摘要;
- token/cost/step 预算;
- provider response id 或 continuation metadata;
- trace 位置。
示例:
type Checkpoint = {
id: string;
runId: string;
stepId: string;
createdAt: string;
messagesRef: string;
runState: RunStateSnapshot;
toolLedger: ToolLedgerEntry[];
fileSnapshot: FileSnapshot;
approvalsRef: string;
budget: BudgetState;
traceRef: string;
};
注意 toolLedger。它记录哪些工具已经执行、是否完成、是否产生副作用、是否可重放。没有 ledger,resume 时很容易重复执行'发邮件''删文件''提交订单'这类不可重复动作。
8.2 tool ledger 是恢复执行的账本
工具账本应在工具执行前后都写入。执行前记录计划,执行后记录结果。这样即使进程崩在中间,恢复时也能判断'这个工具是否可能已经产生副作用'。
type ToolLedgerEntry = {
callId: string;
stepId: string;
toolName: string;
inputHash: string;
status: "planned" | "approved" | "started" | "succeeded" | "failed" | "uncertain";
riskLevel: RiskLevel;
idempotency: "idempotent" | "conditional" | "non_idempotent";
replayPolicy: "auto" | "ask" | "never";
sideEffects: string[];
startedAt?: string;
finishedAt?: string;
resultRef?: string;
};
状态 uncertain 很重要。比如 runtime 发起了外部 API 请求,进程在等待响应时崩溃。恢复时你不知道请求是否成功。此时不能自动重放,只能查询外部系统状态、请求用户确认,或执行补偿逻辑。
对本地文件修改,也要记录 base hash 和 patch hash。这样恢复时才能知道 patch 是否已经应用过,是否被用户手动改动过。
8.3 checkpoint 的保存时机
保存 checkpoint 的时机建议固定下来,不要临时发挥。
| 时机 | 目的 |
|---|---|
| run 开始 | 建立初始状态 |
| 每个 step 请求模型前 | 保存模型输入前状态 |
| 工具计划生成后 | 保存待执行意图 |
| 高风险工具审批后 | 保存用户决策 |
| 工具执行开始时 | 标记可能出现 uncertain |
| 工具执行完成后 | 保存结果和副作用 |
| 文件写入后 | 保存 patch、hash 和工作区状态 |
| run 结束 | 保存最终状态 |
最容易漏的是'工具执行开始时'。如果只在执行后写 checkpoint,进程崩溃就会留下空白。恢复系统必须知道:这个工具到底没开始、开始但未确认完成、还是已经完成。
8.4 patch-based checkpoint
对 coding agent 来说,文件系统状态是第一等公民。最轻量的做法是 patch-based checkpoint:每次写入前后记录 base hash 和 diff。
type FilePatchEntry = {
path: string;
beforeHash: string;
afterHash: string;
patchRef: string;
toolCallId: string;
createdAt: string;
};
优点:
- 存储小;
- 容易审计;
- 适合代码修改;
- 可与用户 review 流程结合。
缺点:
- 二进制文件处理麻烦;
- patch 冲突需要解决;
- 外部副作用无法覆盖;
- 工作区已有用户修改时要小心。
Runtime 在写文件前应记录 base hash,写完后记录 diff。回滚时先确认当前文件是否仍然匹配预期;如果用户又改过文件,不要强行覆盖,应转入冲突处理。
8.5 git-based checkpoint
另一种做法是借助 git。每个 step 或关键写入点创建临时 commit、stash、worktree 或 patch 文件。
git-based 的优点是成熟、可审计、适合代码项目。缺点也明显:不能假设所有 workspace 都是 git 仓库,也不能随意污染用户历史。实践中更推荐保存 patch 和 metadata,而不是自动创建用户可见 commit。需要 commit 时,应让用户确认。
一个折中方案是:
- 检测是否为 git 仓库;
- 读取当前
HEAD和 dirty 状态; - checkpoint 保存 patch 文件,不自动 commit;
- 如果用户要求提交,再创建 commit;
- 回滚前检查 dirty changes 是否包含用户改动。
这里要尊重用户工作区。Agent 写的 diff 和用户原有 diff 要能区分。否则'回滚 Agent 的改动'会误伤用户正在写的代码。
8.6 crash recovery 流程
恢复流程要保守:
- 找到最新完整 checkpoint;
- 校验 checkpoint 文件完整性;
- 校验 workspace 状态;
- 恢复 message history 和 run state;
- 检查最后一个 tool call 是否已完成;
- 对不可重放工具禁止自动重放;
- 给模型注入恢复说明;
- 从下一个安全 step 继续。
恢复说明不要写成含糊的'继续之前任务'。应该明确告诉模型:
系统从 checkpoint 恢复。
Checkpoint: ckpt_20260814_004
上一次已完成 step_8。
工具 run_command(call_15) 执行 npm test,失败,错误摘要如下……
不要重复执行 apply_patch(call_13) 和 run_command(call_15)。
请根据已有观察规划下一步。
这段恢复说明会进入 Context Builder 的高优先级区域。模型需要知道'继续',而不是从头再来。
8.7 幂等性决定能不能重放
幂等性是恢复执行的核心词。一个工具如果重复执行不会造成额外副作用,就更容易恢复。例如读文件、搜索、纯计算通常幂等。写文件如果基于相同 base hash 应用同一个 patch,也可以设计成条件幂等。发邮件、支付、删除远程资源通常不幂等。
工具定义中应声明:
type ToolDefinition = {
name: string;
idempotency: "idempotent" | "conditional" | "non_idempotent";
replayPolicy: "auto" | "ask" | "never";
};
恢复时可以按这个表处理:
| 幂等性 | 示例 | 恢复策略 |
|---|---|---|
| idempotent | read_file、search_code | 可自动重放 |
| conditional | apply_patch、write_file | 校验 base hash 或 patch hash 后决定 |
| non_idempotent | send_email、create_issue、支付 | 不自动重放 |
幂等性不是工具名固定属性,也和输入有关。run_command("ls") 幂等,run_command("npm publish") 不幂等。命令工具尤其要在计划阶段重新分类。
8.8 rollback 不是一个 undo 按钮
用户说'回滚'时,可能有三种意思:
- 回滚文件到某个 checkpoint;
- 恢复对话状态,让 Agent 回到某个计划阶段;
- 补偿外部副作用,例如关闭刚创建的 issue。
Runtime 必须区分这三件事。文件 patch 可以回滚,对话状态可以恢复,外部副作用通常只能补偿,不能直接撤销。比如创建了 GitHub issue,可以关闭;发出的邮件不能收回;执行了数据库 migration,可能需要 down migration。
所以 UI 和 API 都应使用具体措辞:
type RecoveryAction =
| { type: "rollback_files"; checkpointId: string }
| { type: "restore_session"; checkpointId: string }
| { type: "compensate_external_action"; ledgerEntryId: string };
不要把一切都叫 undo。模糊的词会带来错误的用户预期,也会让实现偷懒。
8.9 冲突处理要保护用户改动
恢复和回滚时,最棘手的是用户在 Agent 中断后继续改了文件。此时 checkpoint 里的 base hash 和当前文件 hash 不一致。
不要强行覆盖。建议进入冲突状态:
type CheckpointConflict = {
path: string;
expectedHash: string;
actualHash: string;
agentPatchRef: string;
resolution: "pending" | "keep_user" | "apply_agent" | "manual_merge";
};
冲突提示应告诉用户三件事:哪个文件冲突,Agent 原本想改什么,当前文件和 checkpoint 不一致。对于代码项目,可以生成三方 diff:base、agent change、current file。
保护用户改动是底线。Agent 的恢复能力不能以覆盖用户工作为代价。
8.10 checkpoint 存储与清理
checkpoint 会增长。长任务如果每步都保存完整消息和文件快照,很快占用大量空间。工程上需要存储策略:
- 消息历史保存引用,正文可压缩;
- 工具长输出保存 artifact 引用;
- 文件变更保存 patch,不重复保存完整文件;
- 只保留关键 checkpoint,清理中间临时点;
- 对包含敏感信息的 checkpoint 做加密或脱敏;
- checkpoint index 单独保存,方便快速查找。
一个目录可以这样设计:
.agent-runtime/
├── checkpoints/
│ ├── ckpt_001.json
│ └── ckpt_002.json
├── artifacts/
│ ├── tool_call_17.stdout.txt
│ └── patch_18.diff
├── ledger.jsonl
└── trace.jsonl
JSONL 适合追加写入。checkpoint JSON 适合快速恢复。artifact 适合保存大输出和 diff。
8.11 本章的实现路径
第一步,为第三章的 run state 增加 checkpoint store。先支持本地文件存储,不急着接数据库。
第二步,每个 step 前保存 pre-checkpoint,每个 step 后保存 post-checkpoint。
第三步,加入 tool ledger。工具执行前写 started,执行后写 succeeded 或 failed;进程崩溃后未完成项按 uncertain 处理。
第四步,为文件写入工具记录 base hash、after hash 和 patch ref。
第五步,实现 resume:读取最新完整 checkpoint,校验 workspace,生成恢复说明,禁止重放不可重复工具。
第六步,实现 rollback_files:只回滚 Agent 自己的 patch,遇到用户修改则进入冲突处理。
第七步,给恢复和回滚加 trace event。后面第九章会用这些事件做可观测分析。
8.12 验收标准
完成本章后,runtime 应能在崩溃后继续,而不是从头再来;能回滚 Agent 的文件改动,而不误伤用户改动;能解释哪些工具已执行、哪些不能重放、为什么从某个 checkpoint 恢复。
建议验收用例:
| 用例 | 期望结果 |
|---|---|
| Agent 修改文件后进程崩溃 | 重启后识别 patch 已应用,不重复写入 |
| 工具执行开始后崩溃 | ledger 标记 uncertain,不自动重放高风险工具 |
| 用户在崩溃后手动改同一文件 | resume 进入冲突处理 |
| 回滚到上一个 checkpoint | 只撤销 Agent patch,保留用户原有改动 |
| 非幂等 MCP 工具已执行 | resume 时禁止自动重放 |
| 恢复后模型继续任务 | 上下文包含 checkpoint、已执行工具和不可重放工具 |
做到这里,runtime 才从脚本接近系统。它能承认中断会发生,并为中断准备好证据、路径和边界。
系列文章目录
- 《Agent Runtime 工程化》完整目录
- 《Agent Runtime 工程化》第一章 Agent Runtime 全景
- 《Agent Runtime 工程化》第二章 TypeScript / Node Runtime 基础
- 《Agent Runtime 工程化》第三章 最小 Agent Loop
- 《Agent Runtime 工程化》第四章 工具系统设计
- 《Agent Runtime 工程化》第五章 上下文工程
- 《Agent Runtime 工程化》第六章 MCP 与 Provider 抽象
- 《Agent Runtime 工程化》第七章 权限与安全边界
- 《Agent Runtime 工程化》第八章 Checkpoint、Resume、Rollback
- 《Agent Runtime 工程化》第九章 可观测与 Trace
- 《Agent Runtime 工程化》第十章 Eval Harness 与 CI 回归
- 《Agent Runtime 工程化》第十一章 Agent Runtime 进阶学习路线
- 《Agent Runtime 工程化》第十二章 hermes-agent 产品级实战
- 《Agent Runtime 工程化》附录
