拆解 Claude Code Memory:AI Agent 如何真正“记住”项目

0 阅读13分钟

为什么 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 历史这类可以通过 grepgit log 实时获取的信息,一律不进入记忆。在这个原则下,Claude Code 把所有值得保留的内容严格收敛为以下四类。

User(用户画像)

记录用户的角色、目标、职责和知识水平,逐步建立起对用户的理解,让 Agent 能针对不同专业背景的用户调整协作方式——和资深工程师对话,与刚入门的新人对话,本就不该是一种语气。

  • 何时保存:当了解到用户的角色、偏好或知识背景时。

  • 如何使用:在需要根据用户画像调整解释深度和协作方式时。

  • 示例:我写了十年 Go,但这是我第一次碰这个仓库的 React 部分。

    image.png

Feedback(反馈指导)

记录用户对 Agent 行为的指导,纠正与确认都要记。如果只保存纠正,Agent 会逐渐变得过于谨慎,反而偏离了那些已经被验证可行的做法。这类记忆,是 Agent 在未来对话中保持行为一致性的基础。

  • 何时保存:用户纠正 Agent 的做法时("不是那样"、"别这么做"、"停止 X"),或者明确确认某个非显而易见的做法可行时("对就是这样"、"完美,继续这么做")。

  • 如何使用:下次遇到类似场景,直接按记忆执行,而不是等用户重复一遍。

  • 结构建议:规则本身 + Why(原因) + How to apply(适用场景)。

  • 示例:定义变量不要用 var,全部使用 constlet,因为 var 会污染全局作用域。

    image.png

Project(项目状态)

记录项目的非代码状态——决策、截止日期、正在推进的工作。它帮助 Agent 理解用户当前工作背后的更大背景与动机,而不仅仅是"在改某个文件"。

  • 何时保存:了解到谁在做什么、为什么做、什么时候要交付时。这类状态变化很快,需要持续更新。

  • 如何使用:用它去理解用户请求背后的细节与微妙之处,预判跨成员的协作冲突,给出更有依据的建议。

  • 结构建议:事实或决策 + Why(动机) + How to apply(对建议的影响)。

  • 示例:现在项目首页加载速度太慢,老板在周会上被性能报告点名了,下周一前必须把首页 LCP 降到 2.5s 以下,帮我梳理优化点。

    image.png

    有了这条记忆,后面其他同学开发首页需求时,都会主动把性能纳入考量。如果没有,Agent 就只是机械地"完成功能",根本不会想到性能这件事。

Reference(外部引用)

存储指向外部系统中关键信息的指针。这些信息不在代码仓库里,但对理解项目上下文不可或缺,例如 Sentry 监控、日志系统、内部文档站。

  • 何时保存:当了解到某个外部系统的资源及其用途时。

  • 如何使用:当用户引用外部系统,或需要查找外部信息时。

  • 示例:对话页面报错时,可以去 dumate.n.baidu-int.com/log-viewer 查看日志,需要带上 log id。

    image.png

为什么必须收敛到这四类?

如果 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.md

    Claude Code 从当前工作目录出发,沿目录树向上逐层扫描,每一级目录都会检查 CLAUDE.mdCLAUDE.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.mdapi-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。"

参考资料