被问 Agent Checkpoint,别只答"存聊天记录"
很多人第一反应是"Checkpoint 不就是把对话历史存下来,崩了以后接着聊"。
大白话
Checkpoint 就是 Agent 的游戏存档。
Agent 在跑一个任务的时候,会在关键节点把整个运行状态做成快照存下来。之后不管是服务重启、进程崩溃、人工审批卡住,还是请求被负载均衡甩到另一台机器,它都不用从头重跑,直接把快照拉起来,从断点继续。
它解决的是两个层面的问题:
一是时间维度。内存这东西是易失的,进程一挂,所有状态全没了。跑一个长任务跑到一半崩了,没有存档就只能从头再来。
二是空间维度。生产环境是分布式多实例部署,同一个会话可能被路由到不同的机器上。状态如果绑死在某一台机器的内存里,换个机器就找不到了。
一个快照里至少要装下这些:
thread_id:会话的唯一标识,用来定位是哪一份快照;state:完整的执行状态,注意不是聊天记录,包括工具调用上下文、中间结果、当前计划、还没执行的动作;人工审批的暂停标记——Agent 在等高危操作审批的时候,这个状态得能挂住;
TTL过期时间,控制快照活多久,防止存到天荒地老。
所以核心判断其实就一句:恢复的时候,Agent 的状态必须来自持久化的快照,而不是内存里现拼的。
为什么光存聊天记录不够
这是很多人第一次踩坑的地方:觉得把 messages 数组往数据库或者jsonl里一放,Checkpoint 就算实现了。
但对话历史只是输入和输出的日志,它丢掉了执行过程的运行时状态。举个例子你就明白了:
Agent 已经完成了三个子任务,正在等人工审批下一步的高危操作。这时候对话里记录的只有一问一答,可任务计划、中间计算结果、之前调工具返回的数据、变量上下文,全都在内存里。只存聊天记录,恢复之后 Agent 根本不知道自己进行到哪了,只能重新规划一遍。
再比如:Agent 循环调工具,中间保存了临时变量、分页查询的游标、API 会话凭证,这些根本不进对话消息。还有工具调用的幂等标记、事务补偿的记录,这些是执行层面的元数据,也不会写进聊天记录。
一句话总结这两者关系:对话历史是观测日志,快照是完整运行时状态。日志能用来复盘,但没法直接拿来断点续跑。日志是给人看的,快照是给程序恢复用的,两者不是一个东西。
业界几个产品是这么设计的
Devin:按步骤打快照,还能穿越时间
Devin 的思路是按任务步骤来,每执行完一步工具调用就生成一个 checkpoint。快照里包含沙盒文件系统的状态、终端会话、代码改动、中间编译结果。
它有个挺有意思的能力叫 Time Travel(时间旅行):可以跳回任务任意一个历史快照,用来回滚、调试,或者换一个分支思路重试。相当于游戏里读档试不同打法。
另外它对副作用操作很谨慎,有副作用的动作会先在隔离的沙盒环境里做状态快照,避免污染真实环境。
Claude Code 和 Codex:轻量、增量、省存储
这两个的取向偏轻量,不太会每次全量快照。做法基本是:
用增量状态更新而不是全量快照,控制存储开销;
区分只读和写操作:只读查询不用频繁落盘,高危写操作或者要人工审批的节点才强制触发 checkpoint;
状态和提示词解耦。这样恢复的时候不用把全部历史塞回 Prompt,也避免了超长上下文把窗口撑爆。
能借鉴的共同点
这几家并不是固定定时保存,而是事件驱动打快照:工具调用完成、进入人工审批、执行高危动作之前,这些节点才触发。快照本身也分层——基础会话状态一层、工具执行元数据一层、环境状态一层。再配一个 TTL 定期清理过期快照,防止存储无限膨胀。
真正的难点
到这里还没到最难的地方。上面说的都是怎么把状态存下来、恢复出来,但有一类情况,快照救不了——就是那种执行了就收不回来的操作。
发出去的短信、邮件,支付扣掉的款,下的物流单,推送给设备的指令,这些一旦执行,没有撤销键,也没法像数据库事务那样 rollback。
工程上处理这类问题,用的是补偿式事务(Saga)的思路,分三步:
第一步,前置审批 + 先打快照。 高危副作用操作执行之前,强制保存 checkpoint,并且进入人工审批节点。没有审批通过,绝不执行外部写操作。把"能不能做"这个决定权交给人来把关。
第二步,补偿函数(Compensate)。 每一个不可逆动作,都配一个方向相反的操作。比如 Agent 扣了用户积分,这是不可逆的,补偿函数就是把积分加回来。如果后面流程失败了,就执行补偿,抵消已经产生的副作用。
这里要提前说明白:补偿不一定能 100% 还原现场,它只保证业务层面的数据一致,不是底层原子回滚。这两个要求差得远。
第三步,操作幂等化。 所有工具调用都带上一个全局唯一的 requestId。恢复重试的时候,先查这个 requestId 是不是已经执行成功了。同一个工具动作绝不重复执行,这样恢复的时候不会出现重复扣款、重复发消息。
典型场景:API 调用超时,Agent 崩溃重启,加载 checkpoint 后重试查询接口。因为幂等校验,不会重复提交订单,用户几乎无感。这个细节在面试里讲出来,很能说明你真的处理过线上问题。
没有万能的存储选型
不同场景取舍不一样,别指望一套方案通吃:
本地开发:内存 + 本地文件,快,但进程一重启数据就没了,只够调试用;
小规模分布式:Redis,适合短生命周期的会话,能在多个实例间共享状态。要注意的是大状态序列化的开销、内存占用,还有持久化要打开;
单机生产:SQLite,零运维,适合单机长任务,弱点是并发能力有限;
企业大规模生产:对象存储 + 数据库索引。数据库存 checkpoint 的元信息(threadId、时间、标签),大体积的状态快照序列化后丢到对象存储里。各管各的。
落到架构上,是三层
把前面这些串起来,一套完整的工程实现是三层:
状态快照层:捕获完整执行上下文,包括计划、变量、工具元数据,不只是对话消息;
重放恢复层:加载快照,恢复 Agent 状态,基于幂等机制继续执行。做得好,线上可以秒级恢复;
事务补偿层:对不可逆的外部操作绑定补偿函数,处理执行失败后的业务一致性。
怎么组织回答
按自己的理解程度来,答到哪一层取决于你准备得多深。
基础版:Checkpoint 是 Agent 执行过程中保存的状态快照,崩溃后加载快照、断点续跑。
进阶版:它解决时间(内存易失)和空间(多实例路由)两个维度的状态持久化。快照以 threadId 索引,包含完整执行 state,而不是只有对话历史;在等待人工审批等关键节点落盘,配 TTL 管理生命周期,支持多实例路由。
高阶版:在前面的基础上,说清楚要区分只读和副作用操作;不可逆操作不能直接回滚,用前置审批 + Saga 补偿 + 工具调用幂等;有能力的再提一下 Time Travel,支持回溯历史快照做调试和分支尝试;同时要面对存储膨胀、序列化、敏感数据落盘的安全问题。
Agent Checkpoint 的核心,是捕获一份能真正恢复的完整运行时上下文。
轻量任务,简单实现就够了。
企业级的难点集中在四件事上:幂等控制、不可逆操作的补偿事务、快照的存储成本、人工审批的断点。
Devin、Claude Code 这几家做法的本质,就是事件驱动快照、沙盒隔离、时间旅行回溯。
把这几条讲清楚,就已经超过大多数人了。
留给大家的思考:
快照太大、存储爆炸怎么办?——增量快照、状态裁剪、TTL 清理;
快照里有敏感信息怎么办?——加密存储、最小化落盘;
多个 checkpoint 版本同时存在,冲突怎么办?——版本号,每次快照生成一个版本 ID。


