Agent 的长时间运行任务:一个 Agent 任务跑了 3 个小时——状态保存与恢复

想象一个场景:你写了个 Agent,让它去搜集一周的竞品资料并整理成报告。它启动后唰唰地跑,你看到它在浏览器里一个个点开网页,做着笔记,写着初稿。你安心去睡了。三个小时后你醒来,发现任务停了——不是完成,而是崩了。原因可能只是某个网站返回了 403,或者一次 API 超时。更糟的是,你翻遍它的工作目录,除了一个空的项目文件夹,什么都没留下。它好像什么都没做过。

AI technology illustration

这件事困扰了我很久。我最早以为,让 Agent 跑长任务就跟让厨师做一桌菜一样,安排好顺序就会自动推进。但真正动手写过 Agent 之后才发现,"自动"背后藏着一个大坑:Agent 的所有记忆都在大模型的上下文窗口里,而上下文窗口是易失的、有长度限制的,并且每次调用模型都是独立的——模型根本不记得自己上一轮说过什么。程序一崩,上下文一清,Agent 就成了失忆症患者。

一个 Agent 的状态,比我以为的多得多

要讲清楚"状态保存",先得拆开看:一个运行中的 Agent 到底有什么状态?在我看来至少四层:

  • 对话历史与模型内部推理——上下文窗口里的所有 token,包括它读完的网页、写下的中间推理;
  • 内部规划结构——它把大目标拆成的小步骤,哪些已完成,哪些待执行;
  • 中间产物——跑了半天攒下来的数据、写好的草稿;
  • 外部环境痕迹——创建过的文件、调用过的 API、发出去的邮件。

这些状态的载体完全不同:有的在内存里,有的在文件系统里,还有的躺在一台远端服务器上。所以,把进程的内存 dump 下来远远不够,状态的关键部分恰恰是模型上下文中那些看不见的推理过程。

我试过"存对话",结果行不通

我最初的想法很简单:把对话历史存下来不就行了?下次从历史继续聊。但我很快发现这行不通。一是上下文长度有限,三小时的日志塞进去,立刻撑爆窗口;二是模型对很长的上下文存在注意力衰减,最早的细节早就被"遗忘"了。所以单纯地存对话不是状态保存,那是给模型喂病历。

三条路:检查点、记忆库与事件溯源

真正可行的方案,大体上分三条路线。

第一条:检查点(Checkpointing)。每隔几步,把当前的"状态快照"存到磁盘。恢复时,载入快照,从最后一步继续。LangGraph 就是典型的例子,它的持久化机制允许你在图上配置一个 checkpointer,每走一步就存一份状态。状态中还会定义归并逻辑,防止无限膨胀。官方文档把这一整套概念讲得很清楚:LangGraph Persistence Concepts。

但检查点有个隐蔽的成本:Agent 的状态里如果塞满了大段文本,每次保存都相当于把整个工作区打包一次。所以 LangGraph 会引导你显式地声明哪些信息要留、哪些要丢。这个"显式化"的过程,其实是把 Agent 的模糊记忆变成程序的可序列化数据。

第二条:记忆库(Memory Store)。本质上就是给 Agent 装一个外部记忆——把对话历史里的关键信息提取成摘要或向量,存进向量数据库。恢复时,检索出与当前目标相关的记忆,拼接到新的上下文里。这很像人类的长期记忆:白天的工作细节存入海马体,睡眠时整理,第二天从关键要点中恢复能力。用在这里,就是"压缩摘要 + 向量检索"。

第三条:事件溯源(Event Sourcing)。不保存最终状态,而是保存"发生了什么":每一步的动作、每个工具调用的参数和返回结果。要恢复时,从头重放这些事件,重新构建出最终状态。检查点是"存照片",事件溯源是"存录像"。录像的好处是能精确回溯到任意时刻,坏处是重放要花时间,而且如果 Agent 中间调过真实世界的 API,重放就可能造成重复副作用——比如把同一封邮件发了两次。

方案 本质 优势 风险
检查点 定期存快照 恢复快,实现直观 快照可能很大,丢失两次快照之间的变更
记忆库 压缩 + 检索 省上下文,模拟人类长期记忆 摘要是有损压缩,关键细节可能丢失
事件溯源 记录事件并重放 可精确回滚,审计友好 重放耗时,副作用必须幂等

这三者并不互斥。一个设计良好的 Agent 会组合使用:用记忆库管理长期知识,用检查点保存执行进度,用事件溯源记录重要动作,再通过幂等设计保证恢复安全。但现在的 Agent 框架大多只提供了其中一种,而且往往没有做深。比如很多框架的"保存状态"只是把对话历史导出成 JSON,根本谈不上恢复。

恢复的真正难题是副作用

恢复环节里最容易被忽略的陷阱是副作用。我一开始天真地以为,只要保存了状态,加载回来就能无缝续跑。但忽略了一个问题:Agent 可能正处于一个动作"已经发生但尚未记录"的中间状态。它给外部接口发了消息,但还没来得及把"已发送"写进状态,这时崩溃了。从检查点恢复,它会把这一次调用再执行一遍。这是分布式系统里的经典问题,解药是幂等性:每个操作分配唯一 ID,目标系统记住已经处理过哪些 ID,重复请求自动忽略。Agent 要跑得远,就必须像支付系统一样设计自己的外呼操作。

Agent 是不确定性的长事务

写到这儿,我突然意识到:长时间运行的 Agent,本质上就是一个"长事务"。而长事务的可靠性,业界的分布式系统早就研究透了。比如 Temporal,一个专门编排长任务的工作流引擎。它的核心思想是:把工作流代码变成可重放的事件序列,每次执行都会记录事件,崩溃后从准确的位置重放。这不就是 Agent 需要的东西吗?

但 Temporal 能直接拿来管 Agent 吗?很难。因为传统工作流里的每个决策都是确定性的——同一个输入永远得到同一个输出。而 Agent 的决策来自大模型,同一个状态恢复后,模型可能给出不同的下一步。这让重放变得不可靠:重放事件时,模型调用的结果可能和原来不一样。为了保持确定性,有些框架会选择把模型调用结果缓存下来,重放时直接读缓存,而不是重新调用模型。比如给每个模型请求算个哈希,把响应存起来,恢复时命中缓存就走缓存。这个思路能把 Agent 的回放变成确定性执行。

不过,缓存也有边界:如果 Agent 崩溃是因为它面对的网站变了,或者外部 API 返回了新的数据,那么旧缓存反而会让它活在过去。所以,一个健壮的长运行 Agent,需要把状态保存和实时感知结合起来。

我的实践:把状态变成可以落盘的数据

我后来在实践中形成了一套自己的习惯:别把宝押在"框架自动保存"上,而是从一开始就把状态设计成可序列化的。具体来说,每次调用模型之前,我都先把当前的意图、上下文摘要、已完成的步骤列表压缩成一个 JSON 对象;每完成一步,就更新这个 JSON 并落盘。任务恢复时,扫描这个 JSON,找到第一个未完成步骤,从那里重新发起。这相当于给 Agent 画一张"存档地图"。配合数据库和对象存储,比任何隐式上下文都可靠。这套思路帮我在一个实际的数据采集项目里,把原先 8 小时才能跑完的爬虫任务,做成了可以随时停、随时续的断点式任务。

现在的 Agent 框架,还没有一个真正解决这件事

老实说,当前 Agent 的长期运行能力还处在非常初级的阶段。大多数框架的默认行为仍然是"崩溃即归零"。LangGraph 的检查点、AutoGPT 的文件保存,都是在试着把 Agent 拉回传统软件的确定性世界,但心智模型还没统一。AutoGPT 早期会把整个对话历史写进一个 JSON 文件,简单粗暴,文件越来越大,恢复时也没有真正的差量恢复。这不怪框架,因为 Agent 的"状态"本身就是一个模糊概念,它取决于模型怎么理解自己此刻的位置。

所以,如果你正在构建一个需要连续跑几小时的 Agent,我给你的最终建议是:不要把状态保存在模型的脑子里,而要把它搬到一个你能控制的地方。用明确的数据结构描述"我现在在哪、做过什么、接下来要干什么"。哪怕代码丑一点,恢复起来也是可预期的。等到各家框架真正把状态管理做成标准组件,我们再考虑甩手。在那之前,自己动手做断点续传,是每一个 Agent 应用上线前的必修课。

原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/1040.html

赞 (0)
上一篇 2026年9月22日
下一篇 2026年9月23日

相关推荐