为什么 Agent 需要记忆?
人类之所以能在多次对话中保持连贯,靠的是记忆。同样,一个真正可用的 Agent 也不能每次开口都从零开始——它必须记得用户是谁、项目在做什么、过程中又做过哪些关键决策。Agent Memory(智能体记忆),就是为了回答这些问题而生。
关于 Memory,我们到底要解决什么?
要设计一套 Memory 系统,第一步不是动手写代码,而是想清楚:让 Agent 拥有记忆,难点究竟在哪里?
下面这些思考,是我以 Claude Code 为主要参考,结合官方文档和源码梳理出来的几个核心挑战。需要说明的是,不同 Agent 在记忆机制上的实现差异巨大,例如 OpenClaw 与 Claude Code 走的就是两条完全不同的路线。
边界挑战:哪些数据值得被记住?
Claude Code 提供了两套记忆机制:
- 静态长期记忆(
CLAUDE.md):本质上是一个持久化的 Prompt 文件,启动时会自动加载并注入上下文,可在团队内共享。 - 动态长期记忆(Auto Memory):Claude 在工作过程中给自己留下的"工作笔记",包括构建命令、调试经验、架构决策、代码风格偏好、工作流习惯等。
Auto Memory 并不会在每次会话中都触发保存,而是依据"这条信息在未来对话中是否仍然有用"来决定记什么、不记什么。其核心理念只有一句话:只保存不可推导(Non-Derivable)的信息。
代码模式、架构关系、文件路径、Git 历史这类可以通过 grep 或 git log 实时获取的信息,一律不进入记忆。在这个原则下,Claude Code 把所有值得保留的内容严格收敛为以下四类。
User(用户画像)
记录用户的角色、目标、职责和知识水平,逐步建立起对用户的理解,让 Agent 能针对不同专业背景的用户调整协作方式——和资深工程师对话,与刚入门的新人对话,本就不该是一种语气。
-
何时保存:当了解到用户的角色、偏好或知识背景时。
-
如何使用:在需要根据用户画像调整解释深度和协作方式时。
-
示例:我写了十年 Go,但这是我第一次碰这个仓库的 React 部分。
Feedback(反馈指导)
记录用户对 Agent 行为的指导,纠正与确认都要记。如果只保存纠正,Agent 会逐渐变得过于谨慎,反而偏离了那些已经被验证可行的做法。这类记忆,是 Agent 在未来对话中保持行为一致性的基础。
-
何时保存:用户纠正 Agent 的做法时("不是那样"、"别这么做"、"停止 X"),或者明确确认某个非显而易见的做法可行时("对就是这样"、"完美,继续这么做")。
-
如何使用:下次遇到类似场景,直接按记忆执行,而不是等用户重复一遍。
-
结构建议:规则本身 + Why(原因) + How to apply(适用场景)。
-
示例:定义变量不要用
var,全部使用const和let,因为var会污染全局作用域。
Project(项目状态)
记录项目的非代码状态——决策、截止日期、正在推进的工作。它帮助 Agent 理解用户当前工作背后的更大背景与动机,而不仅仅是"在改某个文件"。
-
何时保存:了解到谁在做什么、为什么做、什么时候要交付时。这类状态变化很快,需要持续更新。
-
如何使用:用它去理解用户请求背后的细节与微妙之处,预判跨成员的协作冲突,给出更有依据的建议。
-
结构建议:事实或决策 + Why(动机) + How to apply(对建议的影响)。
-
示例:现在项目首页加载速度太慢,老板在周会上被性能报告点名了,下周一前必须把首页 LCP 降到 2.5s 以下,帮我梳理优化点。
有了这条记忆,后面其他同学开发首页需求时,都会主动把性能纳入考量。如果没有,Agent 就只是机械地"完成功能",根本不会想到性能这件事。
Reference(外部引用)
存储指向外部系统中关键信息的指针。这些信息不在代码仓库里,但对理解项目上下文不可或缺,例如 Sentry 监控、日志系统、内部文档站。
-
何时保存:当了解到某个外部系统的资源及其用途时。
-
如何使用:当用户引用外部系统,或需要查找外部信息时。
-
示例:对话页面报错时,可以去 dumate.n.baidu-int.com/log-viewer 查看日志,需要带上 log id。
为什么必须收敛到这四类?
如果 Claude Code 不做收敛,允许任意自定义类型,看似更灵活,实际上会带来几个致命缺陷。
-
写入失控
没有封闭的分类,Agent 就缺乏明确的"该存/不该存"判断标准。模型很容易把 Git 历史、代码架构、调试细节这些可推导信息一并塞进记忆,迅速堆积出大量低价值条目。
-
检索信噪比崩塌
记忆最终要注入上下文窗口,而上下文容量有硬上限。一旦类型膨胀、条目失控,真正有价值的记忆(用户偏好、项目决策)就会被噪音淹没,Agent 反而更难找到自己需要的信息。
-
分类模糊导致重复
自定义类型之间天然缺乏正交性,同一条信息可能被归入多个类型重复存储,进一步加剧膨胀。
存储挑战:记忆数据放在哪里?
Claude Code Auto Memory 所记住的,本质上是项目工程事实:构建命令、架构决策、API 约定、编码规范、用户偏好等。这类内容有几个非常关键的特征:
- 强项目绑定:记忆只属于当前代码仓库。
- 稳定的工程知识:规模通常缓慢增长,并不断被新的规则、决策或归纳结果覆盖,而不像聊天记录那样随时间线性膨胀。
- 强结构化:可以按主题清晰分类。
例如:
项目使用 pnpm
禁止使用 var,要用 const
日志去 dumate.n.baidu-int.com 查看
支付模块不能依赖 React Context
这些信息会更新,但不会像聊天记录一样无止境地累加。
因此,Claude Code 设计了一套基于本地文件系统的存储机制:每个项目在 ~/.claude/projects/<project>/memory/ 下拥有独立的记忆目录,<project> 是项目路径的一种抽象表示,例如:
-Users-chenxuejin-Documents-myprojects-baidu-ai-copilot-engine-cloud-front
-Users-chenxuejin-Documents-myprojects-react-rsbuild-project
-Users-chenxuejin-Documents-project-baidu-ai-copilot-engine-cloud-front
-Users-chenxuejin-Documents-project-baidu-qianfan-antd-kit
目录里有一个主入口 MEMORY.md 和若干按主题拆分的文件:
~/.claude/projects/<project>/memory/
├── MEMORY.md # 主索引
├── debugging.md # 调试规律记录
├── api-conventions.md # API 设计决策
└── ... # Claude 按需自行创建
MEMORY.md 充当记忆目录的索引,Claude Code 会在会话中按需读写其中的文件。
# Memory Index
- [用户背景](user_profile.md) — 十年 Go 经验,首次接触本仓库 React 部分,用 Go 类比解释前端概念
- [禁止使用 var](feedback_no_var.md) — 所有变量声明使用 const/let,禁止 var(全局变量/作用域污染问题)
- [日志系统](reference_log_viewer.md) — 对话页面报错时带 log id 去 dumate.n.baidu-int.com/log-viewer 查看日志
而 OpenClaw 这类面向个人生活的 Agent,处境完全不同。它面对的不是项目,而是用户的整条长期生活流,例如:
- 今天发过什么邮件
- 上周讨论过什么
- 三个月前订过哪趟航班
- 用户的长期偏好
- 某次失败经历
- 某个尚未完成的计划
这些记忆有几个本质特征:
- 跨任务:邮件、日历、聊天,无所不包。
- 跨项目:不存在仓库这种边界。
- 时序增长:每天持续追加。OpenClaw 会按
memory/YYYY-MM-DD.md这种格式存储每日会话日志,由 Memory Flush 机制自动生成,每次会话中值得记住的内容都会被追加进当天的文件。 - 非结构化:无法按固定目录组织。
因此,OpenClaw 选择把记忆存进专门的数据库(SQLite),并配合向量搜索与关键词搜索完成信息检索。
检索挑战:怎么找到需要的记忆?
Claude Code 的记忆检索分为两个环节:启动时全量注入和运行时按需加载。
启动时全量注入
每次 Claude Code 会话开始时,CLAUDE.md 与 Auto Memory 会同时启动加载,分两条线并行进行。
-
读取
CLAUDE.mdClaude Code 从当前工作目录出发,沿目录树向上逐层扫描,每一级目录都会检查
CLAUDE.md与CLAUDE.local.md是否存在。所有发现的文件会被拼接进上下文,而不是相互覆盖。举个具体例子:在
~/projects/myapp/src/下启动 Claude Code,它会依次加载:/CLAUDE.md(如果存在) ~/CLAUDE.md(如果存在) ~/.claude/CLAUDE.md(用户全局配置) ~/projects/CLAUDE.md(如果存在) ~/projects/myapp/CLAUDE.md(项目根配置) ~/projects/myapp/src/CLAUDE.md(当前目录配置) -
读取
MEMORY.mdClaude Code 会读取
MEMORY.md的前 200 行或前 25KB(以先到者为准),超出部分会被截断。这一限制是 Claude Code 通过真实用户数据统计得出的经验值。因为
MEMORY.md的内容会被注入大模型上下文,每一轮对话都会带上它。如果不加限制,记忆会随时间持续膨胀,最终把上下文撑爆。
运行时按需加载
预注入解决了"常用知识总是在场"的问题,但并不是所有记忆都值得每次会话都占用上下文 token。Claude Code 为此设计了两个按需加载的触发点。
-
按需读取
CLAUDE.md工作目录子目录下的
CLAUDE.md不会在会话启动时加载,只有当 Claude 在会话中读取该子目录下的文件时,才会触发对应CLAUDE.md的加载。例如,在一个包含 50 个子包的 monorepo 中,根目录的
CLAUDE.md存放全局规范,每个子包的CLAUDE.md则记录该包级别的约定。Claude 只有真正去碰packages/payment-service/里的代码时,才会把packages/payment-service/CLAUDE.md加载进来。 -
按需读取
MEMORY.md索引文件Auto Memory 的主题文件(如
debugging.md、api-conventions.md)在会话启动时不加载。Claude 会结合MEMORY.md中的索引描述与当前场景的实际需要,主动用文件工具去读取它们。
OpenClaw 采用的是混合检索模式:向量搜索擅长语义理解,关键词搜索擅长精准匹配。最终向量搜索的内容权重为 70%,关键词搜索为 30%。
时效性挑战:怎么让记忆不腐烂?
Claude Code 的自动记忆是一项突破——Agent 第一次能够自动沉淀项目信息。但在大约 20 多次使用之后,这些记录的质量会明显下滑,这是一个共性问题。
-
相互矛盾的条目
"我们用 Webpack 打包"的旧笔记,与后来迁移到 Vite 的新笔记并存,Claude 无法判断哪条仍然有效。
-
过时的调试记录
引用已被删除或重命名文件的调试方案,不仅无用,还会主动误导 Claude。
-
失去意义的相对日期
"昨天我们决定用 Tailwind 替换 CSS Modules" 这类表述,随着时间推移会彻底失去参考价值。
本应帮助 Claude 思考的笔记本,反而变成了干扰它判断的噪音。为了解决这个问题,Claude Code 提出了 Auto Dream 方案,目前作为一个尚未正式公布的功能被推送到 Claude Code 中。
什么是 Auto Dream?
Auto Dream 这个名字,灵感来自人脑的 REM(快速眼动睡眠)机制。白天,大脑吸收原始信息,将其暂存为短期记忆;在 REM 睡眠中,大脑再对这些记忆进行二次加工——强化重要内容、丢弃无关内容、把一切系统地归档进长期记忆。
类比之下,Auto Memory 就是白天工作的大脑,在工作中持续记笔记;而 Auto Dream 则是 REM 睡眠周期,在休息时间复盘所有积累的内容,去除过时信息,重新整理成干净、有条理的文件。
Auto Dream 的工作机制
Auto Dream 遵循一个结构化的四阶段流程,每个阶段都有明确目的。它们共同把零散的会话记录,转化为系统化的项目知识。
-
定向
Claude 读取当前内存目录并清点现有内容。先打开
MEMORY.md(索引文件),扫描主题文件列表,构建出关于当前内存状态的"心理地图"。 -
信号采集
检索历史会话的 JSONL 记录,定向提取用户纠正、显式保存、反复出现的主题,以及重要决策节点。
-
整合
这是核心阶段。Claude Code 会把相对日期转换为绝对日期、删除被新事实推翻的条目、清理过时记忆、合并重复条目。
-
裁剪与重建索引
把
MEMORY.md控制在 200 行以内,移除指向不存在文件的链接,为新加入的重要记忆补上索引,并解决索引与实际文件内容之间的矛盾。Auto Dream 不会在每次运行时都重写所有内容,而是做精准的局部调整。
OpenClaw 采用的是时间衰减机制,通过指数衰减模型实现"近期记忆优先"。
记忆管理的常见误区
误区一:记忆越多越好
这是最常见的误区。一些用户会让 Agent 记下所有对话细节,导致 MEMORY.md 索引膨胀,记忆目录中堆满低价值文件。过多的记忆不仅会加重每次对话的上下文负担,还会让 Agent 被噪音干扰,反而忽略真正重要的内容。
误区二:开了 Auto Dream 就高枕无忧
像把 Webpack 全面迁到 Vite、把 Redux 换成 Zustand 这种重大重构场景,用户往往以为 Auto Dream 会自动处理一切,结果旧的记忆条目仍然滞留,给 Agent 带来更多混乱——因为 Auto Dream 的触发条件是双重的(24 小时 + 5 个会话),整合根本没跑起来。
所以,凡是涉及架构级别的迁移,都应该立即手动触发:在会话里直接说 "consolidate my memory files",不要等系统自动触发。
误区三:忽视相对日期问题
用户说"昨天决定放弃 CSS Modules 改用 Tailwind"、"这周二我们把路由从 React Router 换成了 TanStack Router"——这些表述在写下当天看起来很清晰,但未来 Claude 再读时已完全失去参考价值。
因此,所有涉及时间的记忆都必须使用绝对日期,例如:"2026 年 3 月 2 日决定放弃 CSS Modules,改用 Tailwind。"