人类之所以能在多次对话中保持连贯,靠的是记忆。同样,一个真正可用的 Agent 也不能每次开口都从零开始——它必须记得用户是谁、项目在做什么、过程中又做过哪些关键决策。Agent Memory(智能体记忆),就是为了回答这些问题而生。
要设计一套 Memory 系统,第一步不是动手写代码,而是想清楚:让 Agent 拥有记忆,难点究竟在哪里?
下面这些思考,是我以 Claude Code 为主要参考,结合官方文档和源码梳理出来的几个核心挑战。需要说明的是,不同 Agent 在记忆机制上的实现差异巨大,例如 OpenClaw 与 Claude Code 走的就是两条完全不同的路线。
Claude Code 提供了两套记忆机制:
CLAUDE.md):本质上是一个持久化的 Prompt 文件,启动时会自动加载并注入上下文,可在团队内共享。Auto Memory 并不会在每次会话中都触发保存,而是依据"这条信息在未来对话中是否仍然有用"来决定记什么、不记什么。其核心理念只有一句话:只保存不可推导(Non-Derivable)的信息。
代码模式、架构关系、文件路径、Git 历史这类可以通过 grep 或 git log 实时获取的信息,一律不进入记忆。在这个原则下,Claude Code 把所有值得保留的内容严格收敛为以下四类。
记录用户的角色、目标、职责和知识水平,逐步建立起对用户的理解,让 Agent 能针对不同专业背景的用户调整协作方式——和资深工程师对话,与刚入门的新人对话,本就不该是一种语气。
何时保存:当了解到用户的角色、偏好或知识背景时。
如何使用:在需要根据用户画像调整解释深度和协作方式时。
示例:我写了十年 Go,但这是我第一次碰这个仓库的 React 部分。

记录用户对 Agent 行为的指导,纠正与确认都要记。如果只保存纠正,Agent 会逐渐变得过于谨慎,反而偏离了那些已经被验证可行的做法。这类记忆,是 Agent 在未来对话中保持行为一致性的基础。
何时保存:用户纠正 Agent 的做法时("不是那样"、"别这么做"、"停止 X"),或者明确确认某个非显而易见的做法可行时("对就是这样"、"完美,继续这么做")。
如何使用:下次遇到类似场景,直接按记忆执行,而不是等用户重复一遍。
结构建议:规则本身 + Why(原因) + How to apply(适用场景)。
示例:定义变量不要用 var,全部使用 const 和 let,因为 var 会污染全局作用域。

记录项目的非代码状态——决策、截止日期、正在推进的工作。它帮助 Agent 理解用户当前工作背后的更大背景与动机,而不仅仅是"在改某个文件"。
何时保存:了解到谁在做什么、为什么做、什么时候要交付时。这类状态变化很快,需要持续更新。
如何使用:用它去理解用户请求背后的细节与微妙之处,预判跨成员的协作冲突,给出更有依据的建议。
结构建议:事实或决策 + Why(动机) + How to apply(对建议的影响)。
示例:现在项目首页加载速度太慢,老板在周会上被性能报告点名了,下周一前必须把首页 LCP 降到 2.5s 以下,帮我梳理优化点。

有了这条记忆,后面其他同学开发首页需求时,都会主动把性能纳入考量。如果没有,Agent 就只是机械地"完成功能",根本不会想到性能这件事。
存储指向外部系统中关键信息的指针。这些信息不在代码仓库里,但对理解项目上下文不可或缺,例如 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,处境完全不同。它面对的不是项目,而是用户的整条长期生活流,例如:
这些记忆有几个本质特征:
memory/YYYY-MM-DD.md 这种格式存储每日会话日志,由 Memory Flush 机制自动生成,每次会话中值得记住的内容都会被追加进当天的文件。因此,OpenClaw 选择把记忆存进专门的数据库(SQLite),并配合向量搜索与关键词搜索完成信息检索。
Claude Code 的记忆检索分为两个环节:启动时全量注入和运行时按需加载。
每次 Claude Code 会话开始时,CLAUDE.md 与 Auto Memory 会同时启动加载,分两条线并行进行。
读取 CLAUDE.md
Claude 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.md
Claude 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 这个名字,灵感来自人脑的 REM(快速眼动睡眠)机制。白天,大脑吸收原始信息,将其暂存为短期记忆;在 REM 睡眠中,大脑再对这些记忆进行二次加工——强化重要内容、丢弃无关内容、把一切系统地归档进长期记忆。
类比之下,Auto Memory 就是白天工作的大脑,在工作中持续记笔记;而 Auto Dream 则是 REM 睡眠周期,在休息时间复盘所有积累的内容,去除过时信息,重新整理成干净、有条理的文件。
Auto Dream 遵循一个结构化的四阶段流程,每个阶段都有明确目的。它们共同把零散的会话记录,转化为系统化的项目知识。
定向
Claude 读取当前内存目录并清点现有内容。先打开 MEMORY.md(索引文件),扫描主题文件列表,构建出关于当前内存状态的"心理地图"。
信号采集
检索历史会话的 JSONL 记录,定向提取用户纠正、显式保存、反复出现的主题,以及重要决策节点。
整合
这是核心阶段。Claude Code 会把相对日期转换为绝对日期、删除被新事实推翻的条目、清理过时记忆、合并重复条目。
裁剪与重建索引
把 MEMORY.md 控制在 200 行以内,移除指向不存在文件的链接,为新加入的重要记忆补上索引,并解决索引与实际文件内容之间的矛盾。Auto Dream 不会在每次运行时都重写所有内容,而是做精准的局部调整。
OpenClaw 采用的是时间衰减机制,通过指数衰减模型实现"近期记忆优先"。
这是最常见的误区。一些用户会让 Agent 记下所有对话细节,导致 MEMORY.md 索引膨胀,记忆目录中堆满低价值文件。过多的记忆不仅会加重每次对话的上下文负担,还会让 Agent 被噪音干扰,反而忽略真正重要的内容。
像把 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。"