第 1 课:记忆之外还有状态
学习目标:
- 用一句话区分「记忆」(喂给模型的上下文)与「执行状态」(harness 攥着的运行现场),并各举一个例子
- 说出至少三条支撑「崩溃对长任务格外致命」的具体依据,并说清它们分别指向哪个环节
- 给定一份运行中的信息清单,能判断每一项属于记忆、执行状态还是磁盘产出,以及进程崩溃后它是否还在
前置要求:完成本系列前 8 门课,理解
stop_reason 驱动的 harness 循环(第 7 门课)与上下文管理(第 8 门课) | 下一课 第 2 课 >>
第 23 轮,进程被杀
设想你在第 7 门课写的那个 harness,正跑一个要转 40 轮的长任务:读一批文件、逐个分析、边跑边把结论追加进报告。跑到第 23 轮,进程被杀了——可能是一次部署更新,可能是服务器掉电,也可能只是你手滑按了 Ctrl+C。
你去看磁盘:前 22 轮生成的报告文件都还在;NOTES.md(第 8 门课装的那份任务进展摘要)里也记着「已经分析到第几个文件、结论是什么」。看起来损失不大,重启进程接着跑就是了。
但你重启后才发现,runAgent 里那个 messages 数组是空的——它得从 [{ role: "user", content: userInput }] 重新开始拼。turns 计数器归零了。tokensUsed 也归零了。如果崩溃恰好发生在第 23 轮「模型点名要调工具」和「工具结果塞回 messages」之间,那次工具调用——不管它当时是刚开始跑还是已经跑完——现在也没了痕迹。
任务不是从第 23 轮接着跑,是从第 1 轮重新开始。磁盘上的文件都在,但 harness 自己那份「跑到哪儿了」的记录,一点没留下。
记忆是什么,状态是什么
这门课要讲的东西,得先跟一个容易混的概念划清界限。
记忆,是喂给模型的上下文内容——本系列第 5 门课讲跨会话怎么把它存下来、第 8 门课讲每一轮具体喂给模型看什么。它回答的问题是「模型看见了什么」。NOTES.md 就是记忆的一种载体:它已经写在磁盘上,下一轮、下一次会话,都可以被读回来塞进 prompt。
执行状态,是 harness 进程自己攥着的那份运行现场——messages 数组、turns 和 tokensUsed 这类计数器、还没落账的工具调用。它回答的问题是「harness 记得跑到哪儿了」。
这两者的关键差别不在「内容是什么」,而在「此刻它活在哪儿」。记忆可以已经落盘(NOTES.md 就摆在文件系统里,进程死不死都不影响它还在);执行状态默认只活在进程的内存里——let messages = [...] 这行代码创建出来的东西,没有人专门把它写到磁盘之前,进程一退出就随内存一起被回收。上一节里 messages、turns、tokensUsed、悬空的工具调用,没有一样是自动落盘的。
这份 state,本课就是要教你怎么把它变成能落盘、能恢复的东西。
为什么这对长任务是命门
先把账算清楚:Agent 可以长时间运行,跨很多次工具调用维持状态1——这正是第 23 轮那份 messages 越滚越大的原因。可这也意味着 Agent 是有状态的,而错误会复利1:状态越攒越多,一旦哪个环节出岔子,代价不是线性的,是叠加的。
再加一条:没有有效缓解手段的话,一次很小的系统故障,对 Agent 来说都可能是灾难性的1。「小」和「灾难性」这两个词摆在一起,说的正是第 23 轮这种情况——进程被杀本身只是个寻常的运维事件,可它把 22 轮的执行状态一次性清零,代价就被放大了。
出了错之后,不能干脆从头重启:重启是昂贵的,也会让用户沮丧1。所以要构建的是能从出错的地方接着跑的系统1——这也是为什么「记忆之外还有状态」不是一个学院派的概念区分,而是决定长任务能不能扛住中断的地基。
任务越长,这笔账越重:模型可能要连续跑很多轮,而你对它每一步决策的信任是有限度的2;自主运行的本质,天然带着更高的成本和误差累积的风险2。转的轮数越多,攒下的执行状态越厚,一次中断能抹掉的东西也越多。
崩溃不是稀罕事
第 23 轮那次「进程被杀」,听起来像个小概率的意外,但对一个要跑几十轮的长任务来说,中途被打断其实算不上稀奇。就连最正常不过的一次部署更新,都可能撞上运行中的 Agent——所以才需要专门用彩虹部署,把流量从旧版本逐步切到新版本,来避免打断正在运行的 Agent1,原因是:只要一部署,Agent 可能正处在它整个流程的任意一步1。
换句话说,连负责运维的人自己都默认「Agent 随时可能被打断」是会发生的事,要专门设计机制去规避它。你的 harness 没有理由假设自己会更幸运。
解药预告:本课的路线图
第 23 轮丢掉的那份执行状态,解法分五步,也是接下来五课的顺序:
- 检查点(第 2 课)——把
messages、turns、tokensUsed、悬空的工具调用这些执行状态,定期序列化写到磁盘上的一个检查点文件里。
- 断点恢复(第 3 课)——进程重启后,从检查点文件里把这份状态读回来,重建
messages,让循环从断的地方接着转,而不是从第 1 轮重来。
- 副作用与幂等(第 4 课)——恢复时最麻烦的不是「状态没了」,是「有些工具调用可能已经真的执行过了」:哪些工具重跑一次没关系,哪些必须防止重复执行。
- 回退与分叉(第 5 课)——检查点不只是用来救灾的,它还能让你回退到早先的现场重试,或者从某个节点分叉出另一条尝试路线。
- 实战(第 6 课)——把前面几课的机制,装进第 7 门课那个 harness 里,亲手跑一次「中途被杀,重启后接着跑到完成」的长任务。
有一份以「harness 工程」为主线的社区开源路线图,把这条路线概括成一句话:给每一步都建检查点,这样才能恢复、回退、分叉3——这只是社区文档对持久化这个组件职责的一句框架性概括,不是本课要逐字照搬的规范,但它点出的顺序,和上面五步基本对得上。
分寸:不是所有任务都需要这套
不是每个 Agent 都要背上检查点这套机制。加复杂度这件事,值得只在它确实能明显改善结果的时候才考虑2——一个几轮工具调用就能收尾的任务,messages 数组统共没几条,进程死了直接重跑,代价也就是重新问一次,犯不上专门设计一套落盘与恢复的机制。
真正用得上本课这套东西的,是像开头第 23 轮那样的场景:任务要跑几十轮、耗时可能是几分钟到几小时、中途累积了大量还没落盘的执行状态。判断要不要引入检查点,先问自己一句:如果现在被杀,重跑一遍的代价你能不能接受?接受不了,才轮到接下来几课登场。
小结
- Agent 可以长时间运行、跨很多次工具调用维持状态,也正因此是有状态的、错误会复利1——状态攒得越多,一次故障能抹掉的东西也越多。
- 没有有效缓解手段时,一次很小的系统故障对 Agent 可能是灾难性的1;出错后不能从头重启,因为重启昂贵、也让用户沮丧,所以要构建能从出错处恢复的系统1。
- 崩溃不是稀罕事——连正常的部署更新都要专门用彩虹部署来避免打断运行中的 Agent,因为部署时 Agent 可能正处在流程的任意一步1。
- 自主运行天然带着更高成本和误差累积的风险2,任务越长,这笔账越重,也越需要一套能扛住中断的机制。
- 不是所有任务都要背上这套机制——加复杂度只在它确实能明显改善结果时才考虑2,几轮就能收尾的任务,直接重跑的代价通常可以接受。
- 记忆(喂给模型的上下文)和执行状态(harness 攥着的运行现场)是两回事:记忆可能已经落盘,执行状态默认只活在进程内存里——接下来几课,就是把这份执行状态也变成能落盘、能恢复的东西。
>> 第 2 课:检查点:把执行现场落盘