记忆与状态管理

Agent 的记忆问题

当你和 ChatGPT 对话时,你可能注意到:它能记住这次对话说过的话,但不记得上周的对话内容。关掉窗口再打开,一切从零开始。

对于简单的问答来说这没什么。但对于 Agent,记忆是一个核心问题:

  • 一个编程 Agent 需要记住它已经读过哪些文件、尝试过什么方案
  • 一个客服 Agent 需要记住用户之前反馈过什么问题
  • 一个研究 Agent 在一个长期项目中需要记住之前的调研结果

没有记忆的 Agent 就像一个每天失忆的员工——每次都要从头开始。

三种记忆

我们可以把 Agent 的记忆分为三类,类比人类的记忆系统:

短期记忆:上下文窗口

这是最直接的记忆——当前对话中的所有消息。模型能看到的一切都在这个窗口里。

[系统提示] + [用户消息1] + [助手回复1] + [工具调用1] + [工具结果1] + ...

特点:

  • 即时可用:模型直接看到,不需要额外检索
  • 有容量上限:受上下文窗口限制(4K - 1M tokens)
  • 会话结束即消失:不跨会话保留

对于单次任务,短期记忆通常够用。一个修 bug 的 Agent 在一次对话中完成所有工作,所有中间结果都在上下文里。

工作记忆:任务中间状态

当任务复杂到短期记忆不够用时,Agent 需要一个"草稿本"来记录中间状态。

当前计划:
- [✓] 读取项目结构
- [✓] 找到相关文件
- [→] 修改 src/api.ts
- [ ] 更新测试
- [ ] 运行测试验证

已发现的关键信息:
- 项目使用 Express 框架
- 数据库是 PostgreSQL
- API 版本是 v2

工作记忆可以实现为:

  • 系统提示中的动态区域:每步更新当前状态
  • 专用的 scratchpad 工具:Agent 可以主动写入和读取笔记
  • 结构化的状态对象:在代码层面维护一个任务状态

工作记忆的关键价值是帮模型不迷失。在 20 步的推理链中,如果没有明确的状态记录,模型可能忘记自己在做什么、已经做过什么。

长期记忆:跨会话持久化

当 Agent 需要跨多次对话保留信息时,就需要长期记忆。

常见的实现方式:

向量数据库存储:把重要信息编码为向量,存到 Pinecone、Chroma 等向量数据库中。下次需要时通过语义搜索检索。

会话 1: Agent 了解到用户偏好 TypeScript,项目使用 monorepo 结构
        → 存入向量数据库

会话 2: Agent 检索到上次的信息
        → 直接用 TypeScript 写代码,遵循 monorepo 结构

文件系统存储:直接把记忆写入文件。简单粗暴但有效。

Claude Code 的 CLAUDE.md 就是一种长期记忆——
它把项目约定、技术栈信息写在文件里,每次启动都会读取。

数据库存储:结构化地存储用户偏好、历史决策、项目信息等。

长期记忆的难点不是存储,而是检索——怎么在合适的时机找到合适的记忆。存了一千条信息,但如果检索不到需要的那条,等于没存。

上下文窗口管理

即使现在的模型有 100K-1M 的上下文窗口,在复杂任务中也可能不够用。一个 Agent 执行了 50 次工具调用,每次返回几百行内容,上下文很快就会被填满。

几种常见的管理策略:

截断(Truncation)

最简单的方式——超过窗口大小时,丢弃最早的消息。

保留: [系统提示] + ... + [最近 N 轮对话]
丢弃: [早期对话内容]

问题在于:被丢弃的内容可能包含关键信息。模型可能会重复做已经做过的事。

摘要压缩(Summarization)

把较早的对话压缩成摘要,用更少的 token 保留关键信息:

原始(2000 tokens):
  [20 轮关于项目结构的探索和讨论]

压缩后(200 tokens):
  "项目是一个 Next.js 应用,使用 TypeScript,数据库是 PostgreSQL,
   主要 API 在 src/api/ 目录下,已确认 bug 出现在 user 路由中。"

这是一个空间和信息保真度的权衡——摘要可能丢失细节。

滑动窗口 + 关键帧

保留最近的 N 轮对话,加上几个"关键帧"(重要的里程碑信息):

[系统提示]
[关键帧: 项目结构和技术栈信息]
[关键帧: 已确认的 bug 原因]
[最近 10 轮对话]

这结合了截断和摘要的优点——既有详细的近期上下文,又保留了重要的历史信息。

实际产品是怎么做的

Claude Code:使用文件系统作为"外部记忆"。CLAUDE.md 文件存储项目级的长期记忆。自动上下文压缩会在对话变长时摘要早期内容。

ChatGPT:提供 Memory 功能,显式地存储用户偏好和事实。用户可以查看和删除这些记忆。

Cursor:通过 .cursorrules 文件和项目索引实现长期记忆。每次打开项目时读取索引,理解代码结构。

共同的模式是:把需要持久化的信息移到模型的上下文窗口之外,在需要时再检索回来。

记忆系统设计的权衡

没有完美的记忆方案,每种方式都有权衡:

方面纯上下文摘要压缩向量检索文件系统
实现复杂度
信息保真度取决于检索
跨会话持久
容量受窗口限制受窗口限制几乎无限几乎无限
延迟有(需要生成摘要)有(需要检索)有(需要读取)

实用建议:先用最简单的方式(纯上下文),遇到问题再加复杂度。 大多数场景下,一个足够大的上下文窗口加上简单的摘要就够用了。

要点总结

  1. Agent 有三种记忆:短期(上下文窗口)、工作记忆(任务中间状态)、长期(跨会话持久化)。
  2. 工作记忆防止模型在长任务中迷失,可以通过 scratchpad、状态对象等方式实现。
  3. 长期记忆的难点是检索而不是存储——存了信息却找不到等于没存。
  4. 上下文管理有三种策略:截断、摘要压缩、滑动窗口 + 关键帧,各有优劣。
  5. 从简单开始——纯上下文对大多数场景已经够用,遇到瓶颈再考虑更复杂的方案。