深入浅出 Claude Code(二):从源码解构 MEMORY.md,重塑你的上下文注入链路

上一篇我们重写了 CLAUDE.md,但这只是表层。今天这篇,我们将顺着代码的调用栈,扒开 Claude Code 的记忆系统,看看你的指令在底层究竟被赋予了什么级别的权重。

你让 Claude Code "记住"的东西,下次对话它可能完全不记得。或者记得,但不照做。

其实不是功能坏了——是你不清楚这些 memory 在系统里是怎么流动的。存在哪、怎么加载、权重多高、什么格式才有效,搞清楚这条链路,memory 才能真正起作用。


1. Memory 存在哪

所有 memory 文件存储在本地文件系统,按项目隔离:

~/.claude/projects/{sanitized-project-path}/memory/
├── MEMORY.md              ← 索引文件
├── user_profile.md        ← topic 文件
├── feedback_no_summary.md
├── project_deadline.md
└── reference_linear.md

{sanitized-project-path} 是你项目根目录的路径哈希。不同项目的 memory 互不干扰。

这些就是普通的 markdown 文件,你可以直接用编辑器打开改。


2. 它怎么被加载到模型的

这是整篇最关键的一点。

Claude Code 的输入结构分两层:system prompt 和 user message。Memory 和 CLAUDE.md 分别在这两层里:

System Prompt(优先级更高):
  位置 1-9:静态指令(身份、工具使用规范、语气等)
  位置 10:会话指导
  位置 11:★ Memory 系统指令 + MEMORY.md 索引内容
  位置 12-18:环境信息、MCP 指令等

User Message(优先级更低):
  第一条:★ CLAUDE.md 内容(包在 <system-reminder> 里,带降权提示)
  第二条:你的第一句话
  后续:对话历史

两个关键差异:

Memory 在 system prompt 里,没有降权。 模型被直接告知"你有一个持久化的记忆系统,直接用"。

CLAUDE.md 在 user message 里,带降权。 注入时附带一句 this context may or may not be relevant to your tasks,显式告诉模型这些内容可能不相关。

同一条指令,放在 memory 里比放在 CLAUDE.md 里更容易被遵守。这不是玄学,是注入位置和降权声明的直接影响。


3. MEMORY.md 是索引,不是记事本

很多人往 MEMORY.md 里堆大段内容。问题是它有硬截断:

MAX_ENTRYPOINT_LINES = 200      // 超过 200 行,后面的直接丢弃
MAX_ENTRYPOINT_BYTES = 25_000   // 超过 25KB,在最近的换行处截断

截断后末尾追加一条警告,超出部分永久丢失(本次加载)。

MEMORY.md 的正确用法是当索引——每条一行,指向 topic 文件:

- [用户画像](user_profile.md) — 高级 Go 开发者,偏好简洁代码
- [不要总结](feedback_no_summary.md) — 回答结束不要总结做了什么
- [发布冻结](project_release_freeze.md) — 4月3日前不合并非关键 PR

每行控制在 150 字符以内。详细内容写在各自的 topic 文件里——topic 文件没有行数限制。

system prompt 中只注入 MEMORY.md 的内容和 Memory 系统的使用指令。topic 文件只在模型主动用 Read 工具读取时才会被看到。所以 MEMORY.md 索引条目的描述要写得足够清晰——模型根据这一行描述来判断要不要去读 topic 文件。


4. 四种类型:按对行为的影响排序

Memory 文件用 frontmatter 声明类型。四种类型中,对 Claude 行为影响最大的排在前面:

feedback(行为修正)—— 影响最大

“你做错了 X,以后这样做"或"你做对了 Y,继续保持”。

源码对 feedback 的特别说明:

Record from failure AND success: if you only save corrections, you will avoid past mistakes but drift away from approaches the user has already validated, and may grow overly cautious.

只记纠正不记确认,模型会越来越保守。好的 feedback 两种都要记。

格式要求三行结构——规则、原因、适用场景:

---
name: no-extra-changes
description: 只改用户要求的部分,不要顺便改其他代码
type: feedback
---

只改用户要求改的部分。不要加注释、加类型标注、重构周围代码。

**Why:** 之前"顺手"改动多次引入新问题,用户需要精确控制变更范围。
**How to apply:** 所有代码修改操作。

为什么要 Why 和 How to apply?源码注释说得很直白:

Include why so you can judge edge cases later.

没有 Why,模型只会机械执行规则。有了 Why,遇到边缘情况模型能自己判断该不该打破规则。

user(用户画像)—— 影响中等

关于你是谁的信息。模型据此调整沟通方式。

---
name: user-profile
description: 高级 Go 开发者,新接触 React,偏好简洁
type: user
---

用户是高级 Go 开发者,10 年经验。刚开始接触项目的 React 前端部分。
偏好简洁代码,不喜欢过度抽象。用中文交流。

project(项目背景)—— 影响中等

不能从代码和 git 历史推导出来的项目信息。

---
name: release-freeze
description: 4月3日起冻结非关键合并,移动端切 release 分支
type: project
---

4月3日起冻结非关键 merge,移动端团队切 release 分支。

**Why:** 移动端发版需要稳定的代码基线。
**How to apply:** 4月3日后不建议合并非关键 PR。

注意一个源码里的要求:相对日期必须转为绝对日期。“周四"要写成"2026-04-03”——memory 跨会话,下周再看"周四"就不知道指哪天了。

reference(外部指针)—— 影响最小

指向外部系统的指针,告诉模型哪里能找到信息。

---
name: bug-tracker
description: pipeline bug 在 Linear 的 INGEST 项目追踪
type: reference
---

Pipeline 相关的 bug 在 Linear 项目 "INGEST" 中追踪。

5. 什么不该存

源码里硬编码了一个排除清单:

✗ 代码模式、架构、文件路径 —— 读代码就能知道
✗ Git 历史、谁改了什么 —— git log / git blame 是权威来源
✗ 调试方案和修复步骤 —— 修复已经在代码里
✗ CLAUDE.md 里已经有的 —— 重复无意义
✗ 临时任务进度、当前对话上下文 —— 用 task 系统,不是 memory

还有一条值得单独说:

If they ask you to save a PR list or activity summary, ask what was surprising or non-obvious about it — that is the part worth keeping.

即使你说"记住这个 PR 列表",模型应该追问"这里面什么是让你意外的?"——保存那个意外的部分,而不是一整个列表。能从 git log 还原的信息不值得占 memory 的空间。


6. 陈旧机制:老 memory 会被警告

模型读取 memory 文件时,系统会根据文件修改时间附加陈旧提醒(memdir/memoryAge.ts):

今天 / 昨天 → 无警告
2 天以上 → "This memory is N days old. Memories are point-in-time
             observations, not live state — verify against current
             code before asserting as fact."

源码还规定了一条验证规则:

如果 memory 里提到了某个文件路径 → 先检查文件是否还存在
如果 memory 里提到了某个函数名 → 先 grep 确认
"memory 说 X 存在" ≠ "X 现在存在"

所以 memory 里不要写"函数 handleAuth 在 main.go 第 42 行"——文件一改就过时。写"项目用中间件模式处理认证,入口在 main.go"这种不依赖行号的描述。


7. 你没说"记住",它也会自动存

除了你主动说"记住这个",Claude Code 还有一个后台提取机制。

模型完成回答后,如果满足以下条件,会自动启动一个后台 agent 扫描对话内容、提取值得保存的信息:

1.本轮主 agent 没有手动写过 memory

2.extractMemories feature flag 启用

3.auto memory 开启

所以你的 memory 目录里可能有你没主动要求保存的文件——那是后台 agent 提取的。


8. Compact 后 Memory 重新加载

执行 /compact 或 /clear 时,Memory 的缓存会被完全清除:

function runPostCompactCleanup(): void {
  resetGetMemoryFilesCache('compact')   // Memory 文件缓存清除
  clearSystemPromptSections()           // 包括 memory section
}

下一轮对话时 MEMORY.md 被重新读取。

这意味着:如果你在对话中途手动编辑了 memory 文件(比如用编辑器加了一条 feedback),执行 /compact 后修改立即生效。不需要重启。


9. Memory 和 CLAUDE.md 怎么配合

不要重复——源码明确禁止把 CLAUDE.md 里已有的内容存到 memory。

正确的配合方式:

CLAUDE.md 存规则,Memory 存对规则的强化和例外。

比如 CLAUDE.md 写了"commit message 用中文",但模型老是忘。不要在 memory 里再写一遍"commit message 用中文"。写一条 feedback:

---
name: enforce-chinese-commit
description: 强调 commit message 必须中文,之前多次忘记
type: feedback
---

CLAUDE.md 中的"commit message 用中文"规则必须严格遵守。

**Why:** 之前多次输出英文 commit message,用户每次都要手动改。
**How to apply:** 每次 git commit 前检查 message 是否为中文。

用 feedback 来加强 CLAUDE.md 的权重,而不是重复内容。feedback 在 system prompt 里(高权重),CLAUDE.md 在 user message 里(低权重),两层叠加比写两遍同一句话有效得多。


10. 安全边界

Memory 目录路径不能通过项目级 settings.json 设置:

// SECURITY: projectSettings is intentionally excluded — a malicious repo
// could otherwise set autoMemoryDirectory: "~/.ssh" and gain silent
// write access to sensitive directories

防止恶意仓库把 memory 目录指向 ~/.ssh 然后让 AI 往里写文件。只有用户级和管理员级配置可以改路径。

但 memory 文件的内容没有任何校验——你写什么进去就注入什么到 system prompt。这是设计意图:文件在你本地磁盘上,你有完全控制权。

159 个赞

前排学习,感谢佬

学习学习

学习学习

假装学会了

学习学习

不错,看完了,第三章呢

1 个赞

第三章不知道写什么了

1 个赞

写subagent,agent协作

ok,就当下期预告

wo03ne?:distorted_face:

前排学习

学习学习

已学习,棒棒哒

点个赞,有时间好好看一下

学习一波,加个书签

感谢各位支持

向大佬学习

type一共有几种类型?

感谢分享,前来学习