Appearance
持久化记忆系统
每次和 AI 对话,默认都是从零开始。你在上一个会话里告诉它项目用 TypeScript、喜欢函数式风格、不要生成注释——下次打开新对话,它什么都不记得了。
持久化记忆系统解决的是这个问题:让 AI 在多次对话之间积累上下文,不用每次重新介绍项目背景。
本章目标
- 理解 AI 对话的「记忆」是如何工作的,以及它的根本限制
- 掌握 CLAUDE.md 四层优先级体系的设计思路
- 了解自动记忆提取机制是怎么运作的
- 能判断哪些内容适合写入持久化记忆,哪些不适合
对话记忆的根本限制
LLM 本身没有跨对话的记忆能力。每次 API 调用都是完全独立的,模型不知道你昨天说过什么,也不记得你上周建立的偏好。
这件事必须先想明白,不然后面很容易把"记忆"这个词理解错。
AI 产品里说的记忆,通常是宿主系统把某些信息保存在外部,下一次对话时重新喂回去。模型表现得像"记得",其实是因为它再次看到了这些信息。
目前有三种常见的记忆机制:
1. 对话历史(短期):把历史消息直接放进上下文窗口,模型"看到"历史,就"记得"了。限制是:上下文有大小限制,超过就得截断,而且对话结束后历史消失。
2. 外部数据库(长期):把记忆存在向量数据库或关系型数据库里,每次对话时检索相关记忆注入上下文。灵活但复杂,需要额外维护一套检索系统。
3. 文件注入(结构化长期):把记忆写成文本文件,每次对话开始时直接作为系统提示词的一部分注入给模型。CLAUDE.md 就是这种方案的产品化实现。
三种机制各有取舍,实际系统里通常结合使用。
为什么文件注入会成为一个很实用的方案
很多人刚开始会觉得,既然都说到记忆了,为什么不直接上向量库、上检索、上自动分类?当然可以,但那会马上把问题升级成另一个系统工程。
文件注入常见的原因很朴素:它稳定、可解释。
- 记了什么,开发者一眼能看到
- 改了什么,直接改文件就行
- 模型为什么会遵守某条规则,也容易追溯
代价就是它不够灵活。内容一长,上下文成本就会上来;内容一多,也很难做到按需精确取用。所以文件注入更像"小而确定的长期规则层",不是无限扩张的知识库。
CLAUDE.md:最简单的持久化记忆
CLAUDE.md 是 Claude Code 持久化记忆的核心机制。原理简单:启动时读取一批 Markdown 文件,把内容注入系统提示词,每次对话模型都会"看到"这些记忆。
和向量检索不同,CLAUDE.md 不做语义匹配,而是整体注入。你写什么,模型就知道什么,不会因检索偏差而遗漏。代价是上下文消耗随内容增长,要克制地写。
CLAUDE.md 的核心价值在于"确定"。
很多规则类信息不怕短,就怕漏。项目技术栈、禁止修改目录、提交规范、输出语言偏好——这些内容一旦漏掉,模型再聪明也可能在错误前提下工作。全量注入比检索更适合这类信息,因为目标是稳定出现,不是相关性排序。
四层优先级体系
Claude Code 定义了四层 CLAUDE.md 的加载顺序(来自 utils/claudemd.ts):
加载顺序(优先级从低到高):
1. /etc/claude-code/CLAUDE.md ← 企业托管(最低优先级)
2. ~/.claude/CLAUDE.md ← 用户个人(对所有项目生效)
3. 项目 CLAUDE.md / .claude/rules/ ← 项目级(团队共享)
4. CLAUDE.local.md ← 本地私有(不提交 Git)越靠后的文件优先级越高——因为模型对上下文末尾的内容更"注意"。
这套分层在解决一个真实冲突:同一种"记忆"并不都属于同一个责任层。
企业希望有统一安全规则,个人希望有自己的工作习惯,项目希望有团队共同约定,本地机器上又可能有私有配置。把它们混在同一个文件里,最终一定会变成谁都不敢改、谁改都可能伤到别人。
分层之后,系统就可以把"谁来定义什么"这件事分开:
- 企业层解决全局不可违背的约束
- 用户层解决个人长期偏好
- 项目层解决团队共享约定
- 本地层解决不应进入仓库的私有规则
这说到底是权限和归属问题。
分层的实际意义:
- 企业层:IT 部门统一下发安全规范、编码标准,所有人的 Claude Code 都会遵守,用户无法覆盖
- 用户层:个人习惯,比如"输出语言默认中文"、"不要在代码里加注释",对自己的所有项目生效
- 项目层:项目约定,比如"这个项目用 Vue 3 Composition API"、"所有 API 必须有 JSDoc",提交到仓库,团队共享
- 本地层:不适合提交 Git 的私有内容,比如本地环境的特殊配置、个人调试记录
@include 指令
CLAUDE.md 支持类似编程语言的 @include 指令,可以引用其他文件:
markdown
@./api-conventions.md
@./test-guidelines.md这让你可以把记忆拆成多个专题文件,主文件只做引用。大型项目里,前端规范、后端规范、测试规范分开维护,主 CLAUDE.md 不堆内容,保持简洁。
源码里注明:循环引用会被自动检测和阻断,文件 A 引用 B、B 又引用 A 不会导致死循环。
@include 实际上是在帮你把记忆模块化。
当项目变大之后,前端约定、后端约定、测试约定、发布约定往往不该揉成一整块。模块化之后,团队可以按主题维护,也能让主入口文件保持克制,不至于变成一份又长又乱的总章程。
什么内容适合写进 CLAUDE.md
适合:
- 技术栈选型("这个项目用 Next.js 14 App Router,不是 Pages Router")
- 编码风格约定("函数用箭头函数,不用 function 关键字")
- 测试策略("单元测试用 Vitest,不用 Jest")
- 架构约定("API 层放在
src/api/,不要在组件里直接调用 fetch") - 禁止事项("不要修改
src/legacy/目录下的文件")
不适合:
- 每次都会变化的内容(记忆文件应该是稳定的背景知识)
- 非常长的内容(会大量占用上下文,让记忆本身成为负担)
- 密钥、密码、敏感信息(记忆文件可能被提交到仓库)
- 具体的任务进度(用 TodoWriteTool 等任务管理工具更合适)
推荐做法:CLAUDE.md 写的是"这个项目的基本规则",而不是"今天要做什么"。规则是稳定的,任务是动态的,两者不要混用同一个文件。
自动记忆提取
手动维护 CLAUDE.md 需要用户主动整理。Claude Code 还有一个机制:自动记忆提取(来自 services/extractMemories/extractMemories.ts)。
工作原理
每次 Agent Loop 完整结束后(模型返回最终结论,不再调用工具),Claude Code 会在后台偷偷运行一个「记忆提取 Agent」:
- 这个子 Agent 读取刚刚完成的对话记录
- 判断哪些内容值得被记住——比如用户纠正了代码风格、说明了架构决策、强调了某个约定
- 把有价值的内容提取出来,写入到自动记忆目录(
~/.claude/app-dev/projects/<项目路径>/memory/) - 下次对话时,这些自动提取的记忆会被重新注入
几个关键设计点:
fork 模式运行:记忆提取 Agent 是主对话 Agent 的一个分叉,共享 prompt cache(降低成本),但在独立上下文里运行,不影响主对话。
只在对话完整结束后触发:不会在每次工具调用之间提取,只在整个 Agent Loop 收尾时跑一次。记忆提取本身需要一次模型推理,频繁触发成本太高。
写入专用目录,不是 CLAUDE.md:自动提取的记忆和手动写的 CLAUDE.md 分开存储。手动记忆是团队约定,自动记忆是运行时学习,生命周期不一样。
这里最值得注意的是它怎么避免把系统搞乱。
来自 Claude Code v2.1.88 services/extractMemories/extractMemories.ts 和 prompts.ts 的实现可以看出,自动记忆提取是一个分叉出来的子 Agent,独立于主 Agent 运行:
- 它在主对话完整结束后才运行,而不是每轮都插进来
- 它和主对话共享 prompt cache,但运行在独立上下文里
- 它能用的工具被严格限制,只允许读、搜,以及在记忆目录里写
- 如果主 Agent 这一轮已经自己写了记忆,后台提取会跳过,避免重复
这套设计的重点在于:提取动作本身不能反过来污染主任务,也不能获得过大的权限。
它在学什么
自动记忆提取主要关注:
- 用户明确指出模型做错了什么,以及正确的做法
- 用户补充解释的项目背景信息
- 对话中暴露的约定和规则("以后都用这个函数,不用那个")
- 用户表达强烈偏好的内容
它不记住每次对话的所有细节,只关注「下次还会用到」的内容。
团队记忆同步
Claude Code 还有一个实验性功能:团队记忆同步(services/teamMemorySync/)。在多人协作场景下,一个团队成员提炼出的项目记忆,可以同步给团队其他人——类似「组织级 CLAUDE.md」的云端版本。A 发现了一个项目约定,告诉了 AI,AI 记住了,同步给整个团队的 AI 实例。
一旦进入团队协作,记忆系统就不只是提高个人效率的事了。它开始影响组织内的规则传播方式——记忆到底算私人资产还是团队资产,需要在设计时想清楚。
记忆系统 vs RAG
理解了 CLAUDE.md,你可能会想:这不就是极简版 RAG?有相似之处,但区别很大:
| 维度 | CLAUDE.md 记忆 | RAG |
|---|---|---|
| 检索方式 | 全量注入,不做检索 | 语义检索,按相关性注入 |
| 内容规模 | 小(几 KB 到几十 KB) | 大(可以是整个知识库) |
| 确定性 | 高(写什么就注入什么) | 中(检索结果可能有偏差) |
| 适合场景 | 稳定的规则和约定 | 大量文档的按需查询 |
| 维护成本 | 低(手动写 Markdown) | 高(需要构建和维护检索系统) |
CLAUDE.md 解决的是"把固定规则稳定地告诉模型",RAG 解决的是"从大量文档中按需检索相关信息"。两者目标不同,也不冲突。
记忆、任务状态、上下文压缩各管什么
这三个概念经常被混着讲,结果越学越乱。拆开看就清楚了:
- 持久化记忆:解决跨会话的稳定背景规则
- 任务状态:解决这一轮任务当前做到哪里
- 上下文压缩:解决单次会话太长、历史放不下
如果把这三者混为一谈,系统设计就会变得很别扭。比如把任务进度写进 CLAUDE.md,会让记忆文件越来越像临时日志;反过来把长期规则只留在对话里,又会在新会话时全部丢失。
最稳的做法通常是:规则归规则,任务归任务,历史归历史。
工程上的注意事项
CLAUDE.md 要放进 .gitignore 还是提交?
.claude/CLAUDE.local.md(本地私有记忆):放进.gitignore,不提交CLAUDE.md(项目记忆):应该提交到仓库,团队共享- 原则:和
.env类似——个人配置不提交,团队配置提交
字数上限
Claude Code 源码里有一个上限:MAX_MEMORY_CHARACTER_COUNT = 40000。超过这个长度,记忆文件会挤压对话空间,得不偿失。把 CLAUDE.md 当精华来写,不是日记。
记忆被覆盖的情况
四层记忆是叠加的,低优先级和高优先级内容冲突时,高优先级赢。如果模型没有遵守某个约定,先检查是否被更高优先级的记忆覆盖了。
用起来之后的几个体会
记忆最适合放"下次还必须知道"的规则。今天这次任务的临时细节放进去,只会让记忆文件越来越乱。
对规则类信息,确定性往往比智能检索更重要——项目约定最怕漏,不怕短。自动提取能帮你补充稳定偏好,但别指望它替你管理复杂的项目知识体系。
记忆文件一长,就从助力变成负担。好的记忆通常短、分层、按主题拆开。如果你说不清一条信息到底该进 CLAUDE.md、任务清单还是知识库,多半是还没分清它到底是规则、状态还是资料。
和其他章节的关系
- RAG 原理:两者都是把外部知识注入模型,但方式和适用场景不同。CLAUDE.md 是确定性全量注入,RAG 是语义检索按需注入。
- Prompt 工程:CLAUDE.md 本质上是一种特殊的系统提示词管理方式,写 CLAUDE.md 的技巧和写 System Prompt 的技巧高度重叠。
- Agent 基础原理:记忆系统让 Agent 在多次会话之间保持一致性,是 Agent 状态管理的"跨会话"延伸。
下一章:上下文压缩策略——记忆系统解决多次会话间的知识保留,上下文压缩解决单次会话历史过长时怎么不丢关键信息地继续运行。