记忆与状态管理
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 文件和项目索引实现长期记忆。每次打开项目时读取索引,理解代码结构。
共同的模式是:把需要持久化的信息移到模型的上下文窗口之外,在需要时再检索回来。
记忆系统设计的权衡
没有完美的记忆方案,每种方式都有权衡:
| 方面 | 纯上下文 | 摘要压缩 | 向量检索 | 文件系统 |
|---|---|---|---|---|
| 实现复杂度 | 低 | 中 | 高 | 低 |
| 信息保真度 | 高 | 中 | 取决于检索 | 高 |
| 跨会话持久 | 否 | 否 | 是 | 是 |
| 容量 | 受窗口限制 | 受窗口限制 | 几乎无限 | 几乎无限 |
| 延迟 | 无 | 有(需要生成摘要) | 有(需要检索) | 有(需要读取) |
实用建议:先用最简单的方式(纯上下文),遇到问题再加复杂度。 大多数场景下,一个足够大的上下文窗口加上简单的摘要就够用了。
要点总结
- Agent 有三种记忆:短期(上下文窗口)、工作记忆(任务中间状态)、长期(跨会话持久化)。
- 工作记忆防止模型在长任务中迷失,可以通过 scratchpad、状态对象等方式实现。
- 长期记忆的难点是检索而不是存储——存了信息却找不到等于没存。
- 上下文管理有三种策略:截断、摘要压缩、滑动窗口 + 关键帧,各有优劣。
- 从简单开始——纯上下文对大多数场景已经够用,遇到瓶颈再考虑更复杂的方案。