Claude Code源码解读:记忆系统全指南
本文基于 Claude Code 源码分析与实际使用经验,系统讲解记忆系统的工作原理、使用方法和最佳实践。
更新于 2026.08.07
本文目录 11 节

Claude Code 记忆系统详解
一、概述:为什么需要记忆
Claude Code 每次对话都是独立的。没有记忆系统,你第二天开工时 Claude 对你的项目一无所知——用什么包管理器、遵循什么代码风格、踩过什么坑,全都得重新说一遍。
记忆系统解决的核心问题:让跨会话的经验沉淀下来,而不是每次都从零开始。
本质上,记忆系统就是一个受 Claude 自身管理的 Markdown 文件系统。Claude 会读写 ~/.claude/ 下的 .md 文件,下次对话时自动加载相关记忆。没有数据库,没有云同步——文件系统本身就是数据库。
二、存储架构
Claude Code 的指令/记忆体系分为两层,理解这个分层是用好记忆的前提:
┌─────────────────────────────────────────────────────────┐
│ Claude Code 指令体系 │
├──────────────────────┬──────────────────────────────────┤
│ 上下文层(指令) │ 记忆层(自动管理) │
│ Context Layer │ Memory Layer │
├──────────────────────┼──────────────────────────────────┤
│ /etc/claude-code/ │ ~/.claude/projects/<slug>/ │
│ CLAUDE.md (全局管控) │ memory/ │
│ │ ├── MEMORY.md (索引) │
│ ~/.claude/ │ ├── feedback_xxx.md │
│ CLAUDE.md (个人) │ ├── project_xxx.md │
│ rules/*.md │ └── ... │
│ │ │
│ 项目根/ │ memory/team/ (团队记忆,beta) │
│ CLAUDE.md (项目) │ ├── MEMORY.md │
│ .claude/CLAUDE.md │ └── ... │
│ .claude/rules/*.md │ │
│ │ ~/.claude/agent-memory/ │
│ 本地/ │ <type>/ (代理记忆) │
│ CLAUDE.local.md │ │
└──────────────────────┴──────────────────────────────────┘
上下文层 vs 记忆层
| 维度 | 上下文层(CLAUDE.md / rules) | 记忆层(memory/) |
|---|---|---|
| 维护者 | 人工编写 | Claude 自动维护 |
| 适合内容 | 项目约定、固定规则 | 跨会话的经验和偏好 |
| 版本控制 | 通常提交到 Git | 不进 Git(在 .gitignore 中) |
| 加载方式 | 每次对话全量注入 | 索引全量 + 按相关性动态召回 |
| 路径 | 项目目录下 | ~/.claude/projects/<slug>/memory/ |
记忆系统不是替代 CLAUDE.md,而是补充它。不变的规则放 CLAUDE.md,会演变的经验放记忆。
三、记忆文件格式
每个记忆文件都是带 YAML frontmatter 的 Markdown 文件:
---
name: project-build-system
description: 项目使用 pnpm workspace 管理 monorepo,构建命令是 pnpm build
metadata:
type: project
---
本项目是 pnpm workspace monorepo:
- 根目录 `pnpm-workspace.yaml` 定义了 packages
- 构建:`pnpm build`(所有包)或 `pnpm --filter <pkg> build`(单个包)
- 测试:`pnpm test` 使用 vitest
**Why:** Claude 之前每次猜测用 npm,浪费时间纠正。
**How to apply:** 涉及构建/测试时默认用 pnpm。
Frontmatter 字段说明
| 字段 | 必填 | 说明 |
|---|---|---|
name | 推荐 | 短横线命名的唯一标识,如 user-preferred-style。用于 MEMORY.md 索引中的链接 |
description | 推荐 | 一行摘要,这是召回系统判断相关性的主要依据 |
metadata.type | 推荐 | 记忆类型:user / feedback / project / reference |
description 是最重要的字段。 召回时,系统首先扫描所有文件的 description,再用 LLM 判断哪些与当前问题相关。写得模糊的 description 等于这条记忆永远不会被召回。
四、四种记忆类型
系统提示词中明确定义了四种记忆类型,每种有不同的用途:
user — 用户画像
你是谁、你的角色、偏好、技术栈。
---
name: user-backend-engineer
description: 用户是后端工程师,主要用 Go 和 Python,不太熟悉前端
metadata:
type: user
---
用户是有5年经验的后端工程师,主力语言 Go,次要 Python。
前端知识有限,解释前端概念时需要多给一些背景。
feedback — 行为修正
Claude 做错了什么、你希望它怎么改。这是最有价值的类型——直接减少重复犯错。
---
name: feedback-no-unnecessary-comments
description: 用户不希望在代码中添加显而易见的注释
metadata:
type: feedback
---
不要在代码中添加"显而易见"的注释。
比如 `i++ // 递增 i` 这种注释完全不需要。
只有在逻辑不直观、有坑需要提醒时才加注释。
project — 项目知识
项目特有的架构决策、业务逻辑、技术约束。
---
name: project-error-handling
description: 项目的错误处理约定:用自定义错误类型,不用字符串
metadata:
type: project
---
项目约定:
- 所有错误用自定义类型(继承 AppError),不用字符串
- API 层统一用 AppError.toJSON() 输出
- 日志用 logger.error(err),不要 console.log
reference — 参考资料
常用但不好记的信息——命令、URL、配置模板。
---
name: reference-deploy-commands
description: 生产环境部署流程和命令
metadata:
type: reference
---
生产部署:
1. `pnpm build`
2. `docker build -t app:$(git rev-parse --short HEAD) .`
3. `kubectl apply -f k8s/prod/`
监控面板:https://grafana.internal/d/app-overview
五、注入上下文的三条路径
记忆写到磁盘后,如何在下次对话中生效?答案是通过三条独立的注入路径,各司其职:
路径 A:系统提示词(会话开始时加载一次)
MEMORY.md 索引 → 作为上下文层的一部分注入
行为指令(类型说明、保存规则)→ 作为系统提示词的动态段注入
路径 B:用户上下文(每次对话预置)
CLAUDE.md + rules + MEMORY.md → 全量注入到对话上下文
路径 C:按相关性召回(每轮对话动态加载)
记忆主题文件 → Sonnet 侧查询选择 → 作为 <system-reminder> 注入
路径 A:系统提示词中的行为指令
源码位置:src/constants/prompts.ts
systemPromptSection('memory', () => loadMemoryPrompt());
loadMemoryPrompt() 生成的记忆行为指令被注册为可缓存的系统提示词段,包含:
- 四种类型的定义和示例
- 什么该存、什么不该存
- 什么时候去读记忆
- 如何验证记忆中的信息是否过时
这部分内容在 /clear 或 /compact 之前只加载一次,利用 prompt cache 节省 token。
路径 B:用户上下文预置
源码位置:src/utils/claudemd.ts
getMemoryFiles() 函数按优先级加载所有指令文件:
- 管控级
/etc/claude-code/CLAUDE.md - 用户级
~/.claude/CLAUDE.md+rules/*.md - 项目级
CLAUDE.md、.claude/CLAUDE.md、.claude/rules/*.md(从根目录到 CWD 逐级扫描) - 本地级
CLAUDE.local.md(不提交 Git) - 记忆索引
MEMORY.md(截断至 200 行 / 25KB)
这些内容通过 getClaudeMds() 格式化后,在每次对话的上下文中预置。
路径 C:按相关性动态召回(核心创新)
源码位置:src/memdir/findRelevantMemories.ts + src/utils/attachments.ts
这是记忆系统最精巧的部分。不是把所有记忆都塞进上下文,而是每轮对话动态召回最相关的几条:
用户发消息
↓
startRelevantMemoryPrefetch() 发起异步预取
↓
scanMemoryFiles() 扫描所有 .md 文件的 frontmatter
↓
selectRelevantMemories() 用 Sonnet 侧查询打分
↓
选出最多 5 个最相关记忆(每个最多 4KB)
↓
作为 <system-reminder> 注入当前对话
召回的限制参数:
- 单文件上限:4KB(超出截断)
- 每轮最多:5 个文件
- 会话累计上限:60KB
为什么用 LLM 而不是关键词匹配? 因为”相关性”是语义层面的。用户说”帮我改构建脚本”,应该召回
reference-deploy-commands.md——这种关联靠关键词匹配很难做到。
六、两种触发机制
机制 1:主动存储(对话过程中)
Claude 在对话中发现值得记住的信息时,会在对话过程中主动调用 Write 工具写入记忆文件,然后更新 MEMORY.md 索引。
什么时候会触发主动存储?
- 用户明确偏好(“以后都用 pnpm”)
- 项目特有的约定(“我们的错误处理用 Result 类型”)
- 踩过的坑(“这个包和那个包版本冲突”)
- 用户明确要求(“记住这个”)
什么时候不会触发?
- 一次性任务(调试完就结束的 bug)
- 代码中已有的信息(读代码就能知道的)
- 临时上下文(当前任务的中间状态)
机制 2:自动提取(对话结束后)
源码位置:src/services/extractMemories/extractMemories.ts
这是一个后台智能体,在每次查询循环结束时运行。它使用 forked agent 模式——与主对话共享 prompt cache,但有独立的工具权限。
主对话结束
↓
handleStopHooks 触发 executeExtractMemories()
↓
forked agent 分析最近的对话
↓
检查:主 agent 是否已写过记忆?→ 跳过(避免重复)
↓
扫描已有记忆文件清单(避免重复创建)
↓
最多 5 轮对话,只允许读操作 + 写记忆目录
↓
写入新记忆文件 + 更新 MEMORY.md
自动提取的约束:
- 只在主 agent 运行(子 agent 不触发)
- 如果主 agent 已经写过记忆,跳过(互斥)
- 最多 5 个对话轮次(防跑偏)
- 工具权限受限:只能读文件 + 写记忆目录
七、日常使用指南
场景 1:建立项目记忆
新项目开工时,花 5 分钟告诉 Claude 项目的关键信息:
这个项目的技术栈:Next.js 15 + Supabase + Tailwind CSS v4
包管理器用 pnpm,Node 版本要求 >= 20
部署在 Vercel 上,main 分支自动部署
数据库 migration 用 supabase cli,请记住这些
Claude 会创建 project_tech_stack.md 并更新 MEMORY.md。之后新开对话时,它就知道用 pnpm 而不是 npm。
场景 2:纠正 Claude 的行为
Claude 做了不想要的事,直接说:
不要用 console.log,我们项目用 winston logger
以后都用 logger.info() / logger.error()
这会生成一条 feedback 类型的记忆。下次 Claude 想写 console.log 时,记忆会被召回,它就知道改用 logger。
场景 3:存储参考资料
一些不好记但常用的信息:
我们的 staging 环境部署:
- URL: https://staging.example.com
- 部署命令: ./scripts/deploy-staging.sh
- 数据库连接: 用 1Password 里的 "Staging DB" 条目
请记住这些
场景 4:用 /memory 编辑
如果 Claude 写的记忆不准确,或者你想手动调整:
/memory
打开文件选择器,选中要编辑的记忆文件,会在 $EDITOR 中打开。
场景 5:主动搜索记忆
我们之前处理过类似的问题吗?搜索一下记忆
Claude 会用 Grep/Glob 工具在记忆目录中搜索相关内容。
八、源码解读
8.1 记忆目录发现与路径解析
源码:src/memdir/paths.ts
记忆目录路径的解析优先级:
1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE 环境变量(最高优先级)
2. settings.json 中的 autoMemoryDirectory
3. <memoryBase>/projects/<sanitized-git-root>/memory/
其中 memoryBase = CLAUDE_CODE_REMOTE_MEMORY_DIR 或 ~/.claude
启用/禁用的判断链:
CLAUDE_CODE_DISABLE_AUTO_MEMORY 环境变量 → 禁用
CLAUDE_CODE_SIMPLE (--bare) 模式 → 禁用
CCR 无持久化存储 → 禁用
settings.json 中 autoMemoryEnabled → 按配置
默认 → 启用
8.2 记忆文件扫描
源码:src/memdir/memoryScan.ts
scanMemoryFiles() 的工作流程:
- 递归扫描记忆目录中的所有
.md文件(排除 MEMORY.md) - 读取每个文件的前 30 行提取 frontmatter
- 提取
description和type字段 - 按修改时间倒序排列(最新的在前)
- 上限 200 个文件
返回 MemoryHeader[]:
interface MemoryHeader {
filename: string;
filePath: string;
mtimeMs: number;
description: string;
type: string;
}
8.3 LLM 相关性打分
源码:src/memdir/findRelevantMemories.ts
selectRelevantMemories() 的实现:
- 将所有记忆的 header 格式化为文本清单:
- [project] project_build.md (3 days ago): 使用 pnpm monorepo 构建 - [feedback] feedback_no_console.md (1 week ago): 不要用 console.log - 发送给 Sonnet,系统提示词要求它”选出对当前查询最有用的记忆”
- 输出格式:
{ selected_memories: string[] }(文件名列表) - 最多返回 5 个
- 结果经过文件名验证过滤
设计细节:最近使用的工具列表也会传给选择器,避免重复推荐已经在用的参考文档,但会推荐警告/注意事项类记忆。
8.4 过时检测
源码:src/memdir/memoryAge.ts
记忆文件带有时间戳,系统会根据修改时间生成过时提示:
- 当天:无提示
- 1 天前:
<system-reminder>This memory was last updated yesterday. Before recommending paths, functions, or flags from this memory, verify they still exist.</system-reminder> - N 天前:类似提示,包含天数
这提醒 Claude 不要盲目信任旧记忆,先验证再建议。
8.5 自动提取智能体
源码:src/services/extractMemories/extractMemories.ts
自动提取使用 forked agent 模式——与主对话共享 prompt cache(省 token),但有独立的工具权限:
// 工具权限白名单
createAutoMemCanUseTool(): {
allow: [Read, Grep, Glob, read-only Bash, Edit/Write(仅记忆目录)]
}
节流机制:
- 可配置频率(
tengu_bramble_lintel,默认每 1 轮触发一次) - 如果上一次提取还在进行,新请求会被合并(coalescing)
- 提取过程中挂起的上下文会被暂存,用于尾部运行
互斥机制:通过 hasMemoryWritesSince() 检查主 agent 是否已写过记忆文件。如果已写,跳过提取——避免重复。
8.6 团队记忆(Beta)
源码:src/memdir/teamMemPaths.ts
团队记忆是个人记忆的扩展,存储在 memory/team/ 子目录下:
- 需要开启 feature gate
tengu_herring_clock - 个人记忆和团队记忆共用同一套类型体系,但有
scope区分 - 团队记忆有严格的路径验证(防止 symlink 遍历攻击)
8.7 代理记忆
源码:src/tools/AgentTool/agentMemory.ts
子 agent 有自己独立的记忆系统,三个作用域:
| 作用域 | 路径 | 用途 |
|---|---|---|
user | ~/.claude/agent-memory/<type>/ | 用户级代理记忆 |
project | .claude/agent-memory/<type>/ | 项目级代理记忆 |
local | .claude/agent-memory-local/<type>/ | 本地代理记忆 |
还支持快照机制:从 .claude/agent-memory-snapshots/<type>/ 复制初始记忆到代理目录。
九、常见问题与经验
问题 1:记忆写了但下次没生效
症状:Claude 记住了”用 pnpm”,但下次对话还是用 npm。
原因:记忆靠相关性召回,不是全量注入。如果新对话的问题与”包管理器”不相关,这条记忆不会被召回。
解决:确保 description 写得具体且包含关键词。description: 项目使用 pnpm 作为包管理器 比 description: 项目工具配置 好得多。
问题 2:Claude 把代码细节写进记忆
症状:记忆里出现了具体的代码片段、函数名、变量名。
原因:Claude 有时会过度记忆。
解决:系统提示词已明确排除代码模式,但如果还是发生了,用 /memory 编辑删除,或告诉 Claude:
记忆里不应该有具体代码,代码看仓库就行。删掉那些代码片段的记忆
问题 3:记忆太多导致召回不准
症状:随着记忆增多,Claude 似乎召回了不太相关的记忆。
原因:Sonnet 侧查询在候选多时可能选错。召回上限是 5 个,但候选可能有上百个。
解决:定期清理过时记忆。用 /memory 检查,删掉不再相关的。保持 description 精确。
问题 4:description 写得太模糊
反面教材:
description: 一些项目配置
正面教材:
description: 项目使用 Turborepo + pnpm workspace monorepo,ESLint flat config,Prettier 格式化
原则:description 是给召回系统看的”搜索关键词”,要包含最可能被匹配的词。
问题 5:区分 CLAUDE.md 和记忆
什么时候用 CLAUDE.md,什么时候让 Claude 自动记?
| 用 CLAUDE.md | 用记忆 |
|---|---|
| 团队共识的代码规范 | 个人偏好(“我喜欢 2 空格缩进”) |
| 项目架构文档 | 踩过的坑和解决方案 |
| CI/CD 流程说明 | 常用命令和快捷方式 |
| 提交到 Git 的内容 | 不进 Git 的内容 |
问题 6:想要 Claude 记住但没记住
有时你说了一个偏好,Claude 没有主动存储。
直接告诉它:
请记住:我习惯用函数式组件写 React,不用 class 组件
Claude 收到”请记住”会立即创建记忆文件。
高级技巧
- 批量初始化:项目初期一次性告诉 Claude 多个关键信息,让它创建多条记忆
- 记忆间引用:用
[[name]]语法链接相关记忆,如[[project-build-system]] - 按目录查看:
ls ~/.claude/projects/<slug>/memory/直接看所有记忆文件 - 备份迁移:记忆就是文件,复制目录即可备份或迁移到新机器
十、快速参考
文件位置速查
| 文件 | 路径 | 作用 |
|---|---|---|
| 个人全局指令 | ~/.claude/CLAUDE.md | 所有项目通用的个人规则 |
| 全局规则目录 | ~/.claude/rules/*.md | 按文件组织的全局规则 |
| 项目指令 | 项目根/CLAUDE.md | 项目级约定(提交到 Git) |
| 项目规则目录 | 项目根/.claude/rules/*.md | 按文件组织的项目规则 |
| 本地指令 | 项目根/CLAUDE.local.md | 个人项目配置(不提交 Git) |
| 记忆目录 | ~/.claude/projects/<slug>/memory/ | Claude 自动维护的记忆文件 |
| 记忆索引 | ~/.claude/projects/<slug>/memory/MEMORY.md | 所有记忆的摘要索引 |
| 全局管控 | /etc/claude-code/CLAUDE.md | 管理员设置的组织级规则 |
记忆与上下文的对比
| 维度 | 上下文层(CLAUDE.md) | 记忆层(memory/) |
|---|---|---|
| 谁来写 | 人 | Claude |
| 存什么 | 固定规则、项目约定 | 可演变的经验、偏好 |
| 何时加载 | 每次全量注入 | 索引全量 + 主题按相关性召回 |
| 版本控制 | 通常在 Git 里 | 不在 Git 里 |
| 上限 | 无硬限制(但影响 token) | 单文件 4KB,会话累计 60KB |
记忆类型速查
| 类型 | 用途 | 示例 |
|---|---|---|
user | 用户画像 | “后端工程师,主力 Go” |
feedback | 行为修正 | “不要用 console.log” |
project | 项目知识 | “monorepo 用 Turborepo” |
reference | 参考资料 | “部署命令:…” |
关键源码文件
| 文件 | 职责 |
|---|---|
src/memdir/memdir.ts | 记忆提示词构建、MEMORY.md 加载 |
src/memdir/paths.ts | 记忆路径解析、启用/禁用判断 |
src/memdir/memoryTypes.ts | 四种记忆类型定义 |
src/memdir/memoryScan.ts | 记忆文件扫描(frontmatter 解析) |
src/memdir/findRelevantMemories.ts | LLM 相关性打分召回 |
src/memdir/memoryAge.ts | 记忆过时检测 |
src/services/extractMemories/extractMemories.ts | 自动提取后台智能体 |
src/utils/claudemd.ts | CLAUDE.md + MEMORY.md 加载 |
src/utils/attachments.ts | 按相关性注入记忆到对话 |
src/constants/prompts.ts | 系统提示词组装 |
一句话总结:Claude Code 的记忆就是一个受管理的 Markdown 文件系统——你告诉它重要的事,它写成文件;下次对话时,它通过 LLM 语义搜索召回最相关的记忆。没有魔法,就是文件 + 语义搜索。
评论
无需登录,审核通过后公开。只有博主可以回复。
正在加载…
已公开的评论