这不是一项新增加的功能,而是在实际运行中遇到的问题。我们在使用codex,Claude code等等Agent时,我们可以清楚的感受到。如果遭遇电脑意外宕机/系统崩溃,那之前跑的任务就"卡死"了,就要重新再执行一遍,这就非常浪费时间,token等。所以,checkpoint断点恢复,就是为了解决这种问题,留存断点信息,再继续执行任务。这是一个Agent能持续进程任务的重要能力,往往很多时候没有最优的设计,都是不断迭代,优化,设计的过程。
这个设计并不陌生,在LLM中也有相关的内容------断点续训。
但checkpoint的设计要与git worktree,snapshot做区分,并不能划等号。
1. 恢复靠什么,保存什么?
先思考这个问题 > Agent要拿到什么信息,才能继续恢复状态去执行原任务?
其实在之前维护Agent时就已经有一些设计了。在memory中,最简单的方式就是持久化保存,无论是长文压缩的summary,还是各种类型记忆的保存;在LangGraph中我们也会定义state类,记录节点流转,工具使用,token消耗,todo规划,task依赖等信息。所以,对于持久化保存的内容,即使程序崩溃,它仍能保存,但对于内存信息,程序一旦中断,就会消失。我们要保存的,Agent所依靠的,就是这些内容。
a. 状态
Agent State,包含message,todo,task,plan,context。当前Agent执行所包含的内容
execution State,包含当前的tool_call,running_tasks/todos/plans/子agent。状态类变化的内容
enviroment State,包含当前目录,git_commite,worktree。外都环境。
b. 上下文
怎么去组装/恢复context?
比起保存一大段prompt,利用结构化输出更有利于信息提取。模型上,减少注意力稀疏所导致的信息缺失;Agent上,利用关键字段拼接,表达不长又明确。总的来说,前期可持久化的文件就直接引用,特别注意context设计,context应由各字段结合而成(当前的task/todo/plan...,执行的agent,有关联的file...),不应去保存一大段prompt,重要的是截断时的work_state。
c. 其他的设计(参考)
light模式
1.用什么恢复
所以,light模式很大程度上,依赖
- TODO.md ------ 当前计划、待办、验收标准、验证命令
- NOTEPAD.md ------ Agent 自己记的长期笔记、发现、风险
- HISTORY_SUMMARY.md ------ 上下文压缩后留下的历史摘要
checkpoint.json保存了许多字段,涉及(恢复模式,工作环境,当前任务,上下节点,重试次数,摘要),既有人可读的md,又有机器友好的结构化字段。同时,字段要有上限。
2.恢复时的内容,summary设计,恢复后加入"继续任务"前缀(去重)
使用light模式恢复时,我们不还原消息历史。那么模型凭什么知道之前干了什么?靠 _copy_summary_fields()把这批字段直接塞回 state,以及拼出一段 context_summary。summary是上下文的替代品,只服务于light,有上限。
------------------>>>
根据graph各节点来去拼接,每个node_summary, planner_todo, searchagent_sources, error, verifier_command
这就是为什么字段这么多:它要覆盖整条图的每一个交接面。少一个字段,恢复后模型就会在那个环节"失忆"------比如不存 verification_commands,恢复后 verifier 就不知道该跑什么命令。
last_error 单独值得说一下:它服务于"上一次因为什么失败了"这个场景。中断恢复最怕的不是"不知道 做到哪",而是"不知道上次为什么卡住",然后原样再撞一次。
strict模式
用state.json 反序列化后的原样对象,state.json由loop中的另一个current_state去维护,更新状态后去合并进入。message进行归并追加,其余值进行覆盖。
event.json记录关键节点变化,写入时间信息。 "关键"的说明:在LangGraph中,记录每个节点更新;tool失败/需要人工审批时 节点更新保证"可重放",工具失败与审批保证"可解释"。生命周期点不进来,是因为 status 已经说了;普通 custom event不进来,是因为它们的内容本来就在 state.json 里------重复记只是让文件更大,不会让恢复更准。
2. 什么时候保存?
直接上结论:保存关键节点,关键变化内容。
发生"不可逆状态变化"之后,tool执行前后,目录/文件变化时。
状态更新后,todo/task的状态发送变化时,子agent完成任务状态改变时。
context压缩时,重建context时。
agent暂停,人工审批时。
某些长耗时任务/安装,如bash command...
agent完成某个阶段性任务时,work完后下一步review。
整个任务完成后
不要无脑保存
事件驱动保存,而不是执行某step时,在某些关键节点处。也有别的方案会通过记录Agent的执行step来保存,我们这里采用的是Event ------> State ------> Checkpoint。在关键事件触发时,对于Agent来说,很多state会更新,我们要保存的是这些变化的内容/新的内容,而不是关键节点。
Checkpoint 应该有 parent_id。对某个输出不满意,可以重新去在上一checkpoint再跑一边/改动。
checkpoint不应该无限保存,还可以做 Compaction,(cp_1...cp_100)------> cp_11
3. 代码上的设计
持久化保存.CHECKPOINT/,会有很多目录/文件创建,这里挑重点说明。
`events.jsonl` --- 事实源(append-only)
一行一个 JSON 事件,追加写、每条 flush。记录"发生了什么变化",是整份设计的**唯一事实来源(source of truth)。
事件样例:
python
```jsonl
{"seq":41,"ts":"2026-09-12 10:31:07","type":"tool_done","actor":"lead","name":"edit_file","tool":"edit_file","files":["encoder/agent.py"],"status":"ok","output":"...","workspace":{"cwd":"D:/EnCoder","branch":"main"}}
{"seq":42,"ts":"2026-09-12 10:31:09","type":"todo_changed","actor":"lead","data":{"todo_id":"t2","from":"pending","to":"in_progress"}}
{"seq":43,"ts":"2026-09-12 10:31:14","type":"compress","actor":"system","data":{"layer":2,"tokens_before":91234,"tokens_after":41022}}
```
`cp_0001.json` --- 状态快照(物化视图)
某一刻的完整可恢复状态,是 `events.jsonl` 的一次**物化视图**(materialized view)。恢复时只读这一个文件,不需要重放引擎。
python
```json
{
"meta": {
"id": "cp_0007", "parent_id": "cp_0006",
"created_at": "2026-09-12 10:31:14",
"trigger": "compress", "label": "压缩前的 work_state",
"seq": 43, "compacted": false, "replaces": []
},
"state": {
"agent": { "messages": [], "todos": [], "tasks": [], "context": {} },
"execution": { "pending_tool_call": null, "running": [], "pending_approval": null },
"env": { "cwd": "", "git_head": "", "branch": "", "dirty": [], "worktrees": [] }
}
}
```
总结来说,event会对每个关键节点记录内容,cp_id用于真正checkpoint恢复,提供起点。但cp_id不是肆无忌惮地保存,存太多,占用多。
如果是only_read操作不会去存,上下cp_id近似不会存,短时内的节点变化(超过5个在一起存,操作时长>1min再存),最后还有cp_compaction。