深入理解 Claude Code 的记忆系统

AI 编程助手有一个天然短板:每次会话都是"失忆"的。你昨天告诉它的编码规范、上周纠正过的错误习惯、项目里那些约定俗成的坑,新开一个会话就得重新解释一遍。

Claude Code 用一套分层的记忆系统来解决这个问题。这套系统由两大部分组成:你写给 Claude 的指令记忆(CLAUDE.md 体系),和 Claude 自己写给自己的自动记忆(Auto Memory)


一、CLAUDE.md:分层的指令记忆

CLAUDE.md 是由人主动维护的持久化指令文件,每次会话启动时自动注入上下文。它按作用域分为五层:

层级 位置 作用范围
托管策略(Managed Policy) Windows: C:\Program Files\ClaudeCode\CLAUDE.md
macOS: /Library/Application Support/ClaudeCode/CLAUDE.md
Linux/WSL: /etc/claude-code/CLAUDE.md
整台机器所有用户,由 IT 统一下发,无法被排除或覆盖
用户记忆 ~/.claude/CLAUDE.md 你本人的所有项目
项目记忆 <项目根>/CLAUDE.md<项目根>/.claude/CLAUDE.md 随仓库提交,全团队共享
本地记忆 <项目根>/CLAUDE.local.md 只属于你、只在本项目(应加入 .gitignore
子目录记忆 <子目录>/CLAUDE.md 按需懒加载

加载机制:向上遍历 + 拼接

Claude Code 启动时会从当前工作目录一路向上遍历到文件系统根,把沿途所有 CLAUDE.md / CLAUDE.local.md 收集起来。所谓"文件系统根",在 Linux/macOS 上是 /,在 Windows 上是盘符根目录(如 D:\)。假设你在 D:\projects\my-app\backend 启动,它会依次检查:

D:\projects\my-app\backend   ← 当前目录
D:\projects\my-app
D:\projects
D:\                          ← 文件系统根,到此为止

收集到的文件按"根 → 当前目录"的顺序拼接进上下文——注意是拼接,不是覆盖。而当前目录以下的子目录 CLAUDE.md 不在启动时加载,只有当 Claude 实际读取那个子目录里的文件时才按需加载,避免浪费上下文。

"后加载的优先级更高"是真的吗?

这个问题值得单独说清楚:没有硬性的"后者覆盖前者"规则。LLM 的上下文不像 CSS 那样有明确的层叠优先级,所有指令都会被"看到"并综合权衡。

但实践中有两个事实:

  1. Claude Code 故意把离工作目录更近(更具体)的文件排在后面加载。位置靠后、离当前对话更近的内容在模型注意力上通常权重略高,所以项目规则实际上倾向于压过用户全局规则——这是一种软倾向,不是保证。
  2. 官方文档明确警告:如果两条指令真的互相矛盾,Claude 可能任选其一,行为不可预测。

所以正确的做法不是依赖"后写的赢",而是定期审查各层文件,主动消除冲突。

Monorepo 场景:排除不相关的上层文件

Monorepo(单一大仓库)指一个 git 仓库里装了多个独立项目或团队的代码:

company-monorepo/
├── CLAUDE.md              ← 公司级总规则
├── team-a/payment/        ← A 团队的支付服务
├── team-b/frontend/       ← B 团队的前端
└── team-c/data-pipeline/  ← C 团队的数据管道

当你在 team-b/frontend/ 里工作时,向上遍历会把仓库根、甚至中间层其他团队的 CLAUDE.md 全部加载进来——它们可能与你毫无关系,白白消耗上下文还可能引入矛盾指令。此时可以在 .claude/settings.local.json 中用 claudeMdExcludes 按 glob 模式排除:

{
  "claudeMdExcludes": [
    "**/company-monorepo/CLAUDE.md",
    "/path/to/monorepo/other-team/.claude/rules/**"
  ]
}

托管策略层的 CLAUDE.md 是唯一的例外——它无法被排除。


二、@import:把文件内容"钉"进上下文

CLAUDE.md 内可以用 @路径 语法导入其他文件:

项目概览见 @README,npm 命令见 @package.json

# 附加指令
- git 工作流:@docs/git-instructions.md
- 个人偏好:@~/.claude/my-project-instructions.md

语法要点:

  • 相对路径相对于包含该导入的文件解析(不是工作目录),也支持绝对路径和 ~/ 家目录路径
  • 最大递归深度 4 层
  • 代码块和行内代码中的 @xxx 不会被解析——想字面提到某个路径而不触发导入,用反引号包住即可:`@README`
  • 项目里首次遇到外部导入时会弹一次批准对话框,拒绝后导入保持禁用

@import 与纯文本提及路径的本质区别

这是一个容易被忽视但很关键的设计取舍——核心区别在于内容进不进上下文,以及什么时候进

@docs/api.md 纯文本写"参见 docs/api.md"
加载时机 启动时立即注入整个文件内容 只是一个字符串,什么都不加载
是否保证 Claude 看到 保证(内容已在上下文里) 不保证——Claude 需要自己决定用读文件工具去读,可能读也可能不读
Token 成本 每次会话都消耗(无论用不用得上) 零成本,直到真被读取

选择原则:每次会话都必须遵守的规则@import(保证在场);偶尔才需要的参考资料用文本路径提及(省 token,按需读取)。这也解释了官方文档那句提醒——"import 帮你组织文件,但不省 token":它本质上等于把内容复制进了 CLAUDE.md。


三、.claude/rules/:模块化与路径作用域规则

当项目规则越写越多,塞在一个 CLAUDE.md 里既难维护又浪费上下文。.claude/rules/ 目录允许把指令拆成模块化的规则文件,目录下所有 .md 文件会被递归发现:

your-project/
├── .claude/
│   ├── CLAUDE.md
│   └── rules/
│       ├── code-style.md
│       ├── testing.md
│       └── frontend/
│           └── react.md

它最有价值的能力是路径作用域规则:通过 YAML frontmatter 声明 paths,让规则只在 Claude 操作匹配文件时才加载:

---
paths:
  - "src/api/**/*.ts"
---

# API 开发规范
- 所有接口必须做输入校验
- 使用标准错误响应格式

没有 paths 字段的规则文件则与 CLAUDE.md 一样,在启动时无条件加载。个人通用规则可以放在 ~/.claude/rules/(先于项目规则加载,因此项目规则的实际影响力更高);跨项目共享规则可以直接用符号链接。

插一句:什么是 YAML frontmatter?

指 Markdown 文件开头用两行 --- 包起来的元数据块,内容是 YAML 格式的键值对。它给"读这个文件的程序"提供结构化信息,正文才是给人或模型看的内容。这个约定最早来自 Jekyll 等静态博客生成器,如今已是 Markdown 生态的通用惯例。Claude Code 生态里多处用到它:rules 文件的 paths、技能文件(SKILL.md)的 name/description、子代理定义(.claude/agents/*.md)的模型与工具声明,以及自动记忆文件的元信息。


四、日常操作:#、/memory 与 /init

# 快捷键:消息以 # 开头,内容会被存入记忆,例如:

# 提交前必须先跑测试

存储目标不是固定的。较早的版本会弹出选择器让你挑目标文件(项目 CLAUDE.md、用户级 CLAUDE.md 还是 CLAUDE.local.md);引入自动记忆后的版本则由 Claude 根据内容性质判断归属——像"规则/指令"的内容倾向写入 CLAUDE.md,像"经验/发现"的内容倾向写入自动记忆。想精确控制去向,直接在消息里说明即可:# 把这条加到项目 CLAUDE.md:所有日期用 ISO 8601 格式

/memory 命令:查看当前会话到底加载了哪些记忆文件、开关自动记忆、直接打开某个文件编辑。排查"Claude 不听指令"问题时,第一步永远是用它确认文件有没有被加载。

/init 命令:分析代码库自动生成初始 CLAUDE.md(已存在则给出改进建议而非覆盖),会自动发现构建命令、测试方式和项目约定,还会读取 .cursorrulesAGENTS.md.windsurfrules 等其他工具的规则文件并吸收其内容。设置环境变量 CLAUDE_CODE_NEW_INIT=1 可启用交互式多阶段初始化流程。

关于 AGENTS.md

AGENTS.md 是一个跨工具的开放约定(见 https://agents.md ),可以理解为"写给 AI 编程代理看的 README"。它的背景是:每家 AI 编程工具都发明了自己的指令文件——Claude Code 用 CLAUDE.md、Cursor 用 .cursorrules、Windsurf 用 .windsurfrules——团队同时用多个工具时,同样的规范要维护好几份。AGENTS.md 就是为统一这件事而生,OpenAI Codex、Cursor、Google Jules 等多家工具已采纳。

Claude Code 对它的支持有两条路:一是导入复用,在 CLAUDE.md 里写一行 @AGENTS.MD,通用规则放 AGENTS.md 供所有工具共享,Claude 专属规则继续写在 CLAUDE.md 里;二是 /init 初始化时自动读取并整合其内容。


五、Auto Memory:Claude 写给自己的记忆

这是较新的功能(Claude Code v2.1.59+),与 CLAUDE.md 的根本区别一句话就能说清:CLAUDE.md 由你写,自动记忆由 Claude 自己写。Claude 在会话过程中把值得记住的东西记录下来——构建命令、调试发现、你的纠正反馈、代码风格习惯、架构笔记——供以后的会话使用。

存储结构

每个项目在用户主目录下有独立的记忆目录:

~/.claude/projects/<项目标识>/memory/
├── MEMORY.md          # 索引文件(启动时加载前 200 行或前 25KB)
├── debugging.md       # 主题文件(按需读取)
├── api-conventions.md # 主题文件(按需读取)
└── ...
  • 项目标识基于 git 仓库派生,同一仓库的所有 worktree 和子目录共享一份记忆;不在 git 仓库中则按项目根目录计算
  • 记忆是本机私有的,不跨机器同步
  • 全部是纯 Markdown,你可以随时打开审查、编辑或删除

精巧的加载设计

自动记忆的上下文成本控制得很好:

  • 启动时只加载 MEMORY.md前 200 行或前 25KB(先到为准),因此 Claude 会主动保持索引精简,把细节挪到主题文件
  • 主题文件不占启动上下文,Claude 需要时才用普通文件工具按需读取
  • 界面上出现 "Writing memory" / "Recalled memory" 提示时,就是 Claude 在读写这个目录

开关与配置

自动记忆默认开启,三种方式控制:

  1. /memory 命令中直接切换
  2. settings.json 中设置 "autoMemoryEnabled": false
  3. 环境变量 CLAUDE_CODE_DISABLE_AUTO_MEMORY=1

还可以用 "autoMemoryDirectory": "~/my-memory-dir" 自定义存储位置(必须是绝对路径或 ~/ 开头;写在项目级 settings 时需要先通过工作区信任确认)。子代理(subagent)也可以单独启用自己的持久记忆。


六、选型:什么信息放哪里

把两类记忆放在一起对比:

维度 CLAUDE.md Auto Memory
谁来写 你 / 团队 Claude 自动
内容性质 规则、指令、标准 经验、发现、习惯
上下文成本 全文加载 仅索引前 200 行 / 25KB
适合放什么 编码规范、架构决策、工作流 构建命令、调试心得、易变信息

更广义的选型原则——记忆系统不是万能的,官方明确建议分流:

  • 稳定的项目规则 → CLAUDE.md(目标控制在 200 行以内;写具体可验证的指令,比如"用 2 空格缩进"而不是"格式规范一点")
  • 只对某些目录/文件生效的规则.claude/rules/ 路径作用域规则
  • 多步骤操作流程 → Skills(按需加载,不占每次启动的上下文)
  • 必须在固定时机强制执行的动作(如每次提交前 lint) → Hooks。这一条尤其重要:Hooks 由客户端强制执行,不依赖 Claude 的判断;而 CLAUDE.md 只是"影响行为",不是硬约束
  • 经常变化的信息 → 交给自动记忆

七、常见问题排查

"Claude 不遵守我的 CLAUDE.md"
先跑 /memory 确认文件确实被加载了;再检查指令是否够具体;最后排查多层文件之间有没有互相矛盾的规则——冲突时 Claude 可能任选其一。需要精确诊断时,可以用 InstructionsLoaded hook 记录哪些指令文件在何时、为何被加载。

"/compact 之后指令丢了"
项目根的 CLAUDE.md 会在上下文压缩后自动重新注入,但子目录的嵌套 CLAUDE.md 不会——要等 Claude 下次读取那个目录的文件才重新加载。只在对话里口头说过的指令,压缩后就没了;重要的规则一定要落到 CLAUDE.md。

"不知道自动记忆存了什么"
/memory → 进入 auto memory 文件夹浏览,全是纯 Markdown,不满意直接改或删。

"CLAUDE.md 太大了"
拆成路径作用域规则、删掉不是每次会话都需要的内容。注意 @import 拆分只改善组织结构、不减少 token。另外 CLAUDE.md 支持 HTML 块级注释 <!-- ... -->,注入上下文前会被剥离,可用来写只给人看的维护说明。


结语

Claude Code 的记忆系统本质上是一套分层的上下文管理策略:托管策略管住底线,用户记忆承载个人偏好,项目记忆沉淀团队共识,路径作用域规则精准投放,自动记忆让 Claude 自己积累经验——每一层都在"保证指令在场"和"节约上下文"之间做取舍。

用好它的关键不在于把所有东西都塞进记忆,而在于理解每类信息的正确归宿:规则给 CLAUDE.md,流程给 Skills,强制动作给 Hooks,经验交给自动记忆。当你发现自己第三次向 Claude 解释同一件事时,那就是该写入记忆的信号。

posted @ 2026-07-15 07:50  无风听海  阅读(10)  评论(0)    收藏  举报