← 全部文章

Claude Code源码解读:记忆系统全指南

本文基于 Claude Code 源码分析与实际使用经验,系统讲解记忆系统的工作原理、使用方法和最佳实践。

更新于 2026.08.07

本文目录 11 节
Claude Code源码解读:记忆系统全指南封面

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() 函数按优先级加载所有指令文件:

  1. 管控级 /etc/claude-code/CLAUDE.md
  2. 用户级 ~/.claude/CLAUDE.md + rules/*.md
  3. 项目级 CLAUDE.md、.claude/CLAUDE.md、.claude/rules/*.md(从根目录到 CWD 逐级扫描)
  4. 本地级 CLAUDE.local.md(不提交 Git)
  5. 记忆索引 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() 的工作流程:

  1. 递归扫描记忆目录中的所有 .md 文件(排除 MEMORY.md)
  2. 读取每个文件的前 30 行提取 frontmatter
  3. 提取 description 和 type 字段
  4. 按修改时间倒序排列(最新的在前)
  5. 上限 200 个文件

返回 MemoryHeader[]:

interface MemoryHeader {
  filename: string;
  filePath: string;
  mtimeMs: number;
  description: string;
  type: string;
}

8.3 LLM 相关性打分

源码:src/memdir/findRelevantMemories.ts

selectRelevantMemories() 的实现:

  1. 将所有记忆的 header 格式化为文本清单:
    - [project] project_build.md (3 days ago): 使用 pnpm monorepo 构建
    - [feedback] feedback_no_console.md (1 week ago): 不要用 console.log
  2. 发送给 Sonnet,系统提示词要求它”选出对当前查询最有用的记忆”
  3. 输出格式:{ selected_memories: string[] }(文件名列表)
  4. 最多返回 5 个
  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 收到”请记住”会立即创建记忆文件。

高级技巧

  1. 批量初始化:项目初期一次性告诉 Claude 多个关键信息,让它创建多条记忆
  2. 记忆间引用:用 [[name]] 语法链接相关记忆,如 [[project-build-system]]
  3. 按目录查看:ls ~/.claude/projects/<slug>/memory/ 直接看所有记忆文件
  4. 备份迁移:记忆就是文件,复制目录即可备份或迁移到新机器

十、快速参考

文件位置速查

文件路径作用
个人全局指令~/.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.tsLLM 相关性打分召回
src/memdir/memoryAge.ts记忆过时检测
src/services/extractMemories/extractMemories.ts自动提取后台智能体
src/utils/claudemd.tsCLAUDE.md + MEMORY.md 加载
src/utils/attachments.ts按相关性注入记忆到对话
src/constants/prompts.ts系统提示词组装

一句话总结:Claude Code 的记忆就是一个受管理的 Markdown 文件系统——你告诉它重要的事,它写成文件;下次对话时,它通过 LLM 语义搜索召回最相关的记忆。没有魔法,就是文件 + 语义搜索。

评论