深度解析 Claude Code 记忆系统:从源码看 AI 编程助手如何"记住"你的项目
本文基于 Claude Code v2.1.88 泄露源码及社区分析资料,深入剖析其记忆系统的架构设计与实现细节。如果你正在构建 AI Agent 系统,这套记忆架构非常值得参考。
一、为什么需要记忆系统?
Claude Code 是 Anthropic 官方的 CLI 编程助手。每次启动一个新会话,模型面对的是一个全新的上下文窗口——它不知道你的项目用什么技术栈、你偏好什么代码风格、上次修了什么 bug。
没有记忆系统,你每次都要花几分钟"教育"AI:解释架构、重复约定、描述上下文。这在复杂项目上是巨大的效率损耗和 token 浪费。
Claude Code 的解决方案是一套 三层持久化记忆体系,配合会话内的 上下文压缩机制,让 AI 在跨会话和长会话两个维度上都能高效利用知识。
二、记忆系统全景架构
从源码结构看,Claude Code 的记忆相关代码分布在以下目录:
src/
├── memdir/ # Auto Memory 核心(记忆目录管理)
│ └── memdir.ts # buildMemoryPrompt / loadMemoryPrompt / 截断逻辑
├── services/
│ ├── SessionMemory/ # 会话记忆提取(Session Memory)
│ │ └── sessionMemory.ts
│ └── compact/ # 上下文压缩(三层策略)
├── utils/
│ └── api.ts # CLAUDE.md 注入逻辑(prependUserContext)
└── constants/ # System Prompt 各段落定义
整个记忆体系可以概括为 "手动 + 自动 + 会话"三层架构:
三、Layer 1:CLAUDE.md —— 手动记忆层
3.1 不在 System Prompt 里
这是最大的认知误区。很多人以为 CLAUDE.md 的内容被拼接到 system prompt 中,实际上并非如此。
从源码看,CLAUDE.md 通过 prependUserContext() 函数注入,插入位置是消息列表的最前面,作为一条特殊的 user message:
// src/utils/api.ts(简化)
export function prependUserContext(messages, context): Message[] {
return [
createUserMessage({
content: `As you answer the user's questions, you can use...
${context.claudeMdContent}
${context.memoryContent}
...`
}),
...messages
]
}
这意味着 CLAUDE.md 不享受 system prompt 级别的缓存优化。每次会话它作为 user message 的一部分被发送,会消耗实际的输入 token。
3.2 四级作用域
CLAUDE.md 支持四个作用域层级,按优先级从低到高:
| 层级 | 路径 | 场景 |
|---|---|---|
| 企业级 | MDM 管控策略注入 | 企业统一管控 |
| 用户级 | ~/.claude/CLAUDE.md | 个人全局偏好 |
| 项目级 | ./CLAUDE.md 或 ./.claude/CLAUDE.md | 团队共享约定(提交到 git) |
| 本地级 | ./CLAUDE.local.md | 个人项目偏好(gitignore) |
项目级的 CLAUDE.md 可以通过 /init 命令自动生成——Claude 会分析你的代码库,提取构建命令、测试指令和项目约定。
3.3 最佳实践
由于 CLAUDE.md 直接消耗 token,写法上需要注意效率:
- 越短越好:简洁的指令比冗长的描述更容易被模型遵循
- 结构化:用清晰的分类(构建命令、代码风格、架构约定)组织内容
- 避免废话:不要写"请帮我"、"你应该"这类无效前缀
- 可执行:写成可直接执行的命令或可直接遵循的规则
四、Layer 2:Auto Memory —— 自动记忆层(memdir)
这是 Claude Code 记忆系统中最有技术含量的部分,代码位于 src/memdir/memdir.ts。
4.1 核心设计:基于文件的记忆目录
每个项目在 ~/.claude/projects/<project>/memory/ 下有一个专属的记忆目录。目录路径通过 git 仓库路径派生,所以同一仓库的所有 worktree 和子目录共享同一个记忆空间。
~/.claude/projects/
├── <project-hash-A>/
│ └── memory/
│ ├── MEMORY.md # 索引文件(入口点)
│ ├── debugging.md # 主题文件:调试经验
│ ├── patterns.md # 主题文件:代码模式
│ └── architecture.md # 主题文件:架构笔记
└── <project-hash-B>/
└── memory/
└── MEMORY.md
4.2 MEMORY.md:200 行硬截断的索引文件
源码中定义了明确的截断常量:
// src/memdir/memdir.ts
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000 // 25KB
只有 MEMORY.md 的前 200 行或前 25KB 会在会话启动时自动加载。超出部分直接被截断,并且会产生警告提示:
WARNING: MEMORY.md is 350 lines (limit: 200). Only part of it was loaded.
Keep index entries to one line under ~200 chars; move detail into topic files.
这意味着 MEMORY.md 应该被当作索引使用,而不是存放大量细节。每条记忆控制在一行、200 字符以内,详细内容放到独立的 topic 文件中。
4.3 Topic 文件:按需加载
像 debugging.md、patterns.md 这样的主题文件不会在启动时加载。Claude 在会话过程中需要相关信息时,会通过标准的文件读取工具按需读取它们。
这是一个典型的冷热分层设计:
- 热数据(MEMORY.md 前 200 行)→ 每次启动加载,消耗固定 token
- 温数据(topic 文件)→ 按需读取,用工具调用触发
- 冷数据(历史会话)→ 不加载,依赖 Session Memory 提取
4.4 四类记忆分类法
从 buildMemoryLines() 函数的源码注释可以看到,Auto Memory 采用了一个封闭的四类分类体系:
// src/memdir/memdir.ts
/**
* Constrains memories to a closed four-type taxonomy:
* - user: 用户偏好和习惯
* - feedback: 用户的修正和反馈
* - project: 项目特定知识
* - reference: 参考信息
*
* Content derivable from current project state (code patterns,
* architecture, git history) is explicitly excluded.
*/
关键设计原则:可以从代码库当前状态推导出来的信息(代码模式、架构、git 历史)被显式排除在记忆之外。这避免了记忆与实际代码之间的不一致——代码变了但记忆没更新的问题。
4.5 记忆写入时机
Claude 并不是每次会话都写记忆。从源码和官方文档看,它的写入策略是:
- 用户纠正时:当用户纠正 Claude 的行为("不要用分号"、"我们用 pnpm 不用 npm"),Claude 会主动将这些偏好写入记忆
- 有价值的发现时:调试过程中发现的坑、构建系统的特殊配置等
- Claude 自主判断:基于"这条信息在未来的对话中是否有用"来决定是否记忆
写入操作在 UI 中会显示 "Writing memory" 提示,用户可以随时编辑或删除这些文件(它们就是普通的 Markdown 文件)。
4.6 记忆注入到 System Prompt
loadMemoryPrompt() 函数负责将记忆内容注入到 system prompt 的动态部分(SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之后):
// src/memdir/memdir.ts(简化)
export function buildMemoryPrompt(params: {
displayName: string
memoryDir: string
extraGuidelines?: string[]
}): string {
const entrypoint = params.memoryDir + ENTRYPOINT_NAME
const raw = fs.readFileSync(entrypoint, { encoding: 'utf-8' })
const t = truncateEntrypointContent(raw) // 硬截断
const lines = buildMemoryLines(params.displayName, params.memoryDir, ...)
lines.push(`## ${ENTRYPOINT_NAME}`, '', t.content)
return lines.join('\n')
}
注意这里是同步读取(fs.readFileSync),因为记忆加载发生在 system prompt 组装阶段,需要在 API 调用前完成。
五、Layer 3:Session Memory —— 会话内记忆管理
5.1 Session Memory 提取
src/services/SessionMemory/sessionMemory.ts 实现了从对话过程中自动提取值得记忆的内容。从架构分析中可以看到,它的工作方式是:
// 会话结束或 compact 触发时
shouldExtractMemory() -> runForkedAgent() -> Session Memory 更新
这里用了一个巧妙的设计——fork 出一个子 agent 来做记忆提取,不阻塞主会话的执行流程。
5.2 三层上下文压缩
长会话中上下文会逐渐填满,Claude Code 的 services/compact/ 实现了三层压缩策略:
| 策略 | 机制 | 触发时机 |
|---|---|---|
| autoCompact | 将历史消息总结为摘要 | 上下文接近限制时自动触发 |
| snipCompact | 修剪冗长的工具输出 | 单次工具输出过大时 |
| contextCollapse | 折叠旧的消息段落 | 保留关键信息,折叠细节 |
这套压缩机制确保了即使在超长会话中,关键的上下文信息不会因为窗口限制而丢失。
六、System Prompt 的缓存架构
Claude Code 的 system prompt 不是一整块文本,而是被精心分割为静态段和动态段:
静态段通过 Anthropic API 的 prompt caching 机制可以被缓存复用,大幅降低 token 消耗和延迟。Auto Memory 注入在动态段——这意味着每次记忆内容变化,都会破坏后续的缓存,所以记忆内容要尽量稳定和精简。
七、CLAUDE.md vs Auto Memory:注入位置的差异
这两套机制虽然都提供跨会话记忆,但注入位置完全不同:
| 维度 | CLAUDE.md | Auto Memory |
|---|---|---|
| 注入位置 | User message(对话开头) | System prompt(动态段) |
| 缓存影响 | 不影响 system prompt 缓存 | 变化会破坏缓存 |
| 加载方式 | 完整加载(无截断) | MEMORY.md 前 200 行 |
| 写入者 | 用户手动 | Claude 自动 |
| 版本控制 | 可提交到 git | 本地存储,不共享 |
| 适合内容 | 团队约定、构建命令 | 个人偏好、调试经验 |
八、记忆系统的设计启示
如果你在构建自己的 AI Agent 记忆系统,Claude Code 的设计有几个值得借鉴的点:
8.1 分层而非全量
不是所有记忆都需要每次加载。热数据(MEMORY.md 索引)自动加载,温数据(topic 文件)按需读取,冷数据(历史会话)通过提取机制保留精华。
8.2 索引 + 详情分离
MEMORY.md 当索引用,详细内容放 topic 文件。这个模式和数据库的"索引-数据"分离思想一致——索引小且快,数据大但不常读。
8.3 排除可推导信息
不记忆可以从代码库当前状态推导出来的内容。这避免了记忆与现实不一致的维护负担。
8.4 记忆是纯文本
所有记忆都是 Markdown 文件,人类可读可编辑。没有私有数据库、没有向量存储、没有嵌入计算。简单可靠。
8.5 硬性截断保护
200 行 / 25KB 的硬截断不是建议,是强制执行的。这种"宁可丢信息也不爆上下文"的设计,在生产系统中非常重要。
九、局限性
当然,这套系统也有明显的局限:
- 单 Agent 绑定:记忆存储在
~/.claude/下,不能跨工具共享(比如 Cursor 或 OpenCode 无法读取) - 无语义检索:topic 文件的查找依赖 Claude 自己的文件读取工具,本质上是 grep 级别的搜索,没有向量相似度匹配
- 无版本管理:记忆文件没有版本历史,误删除或错误修改无法回退
- 上下文开销:即使是索引级别的记忆,200 行也是不小的 token 消耗
社区已经有一些项目在尝试解决这些问题,比如 memsearch(跨 Agent 语义记忆)和 memory-mcp(基于 MCP 的记忆服务),有兴趣的可以关注。
十、总结
Claude Code 的记忆系统是一个务实的工程设计——没有花哨的向量数据库,没有复杂的 RAG 管线,就是 Markdown 文件 + 分层加载 + 硬性截断。但这种简单性恰恰是它的优势:可预测、可调试、可人工干预。
对于 AI Agent 开发者来说,Claude Code 的记忆架构提供了一个很好的参考基线:先用最简单的方案解决 80% 的问题,再在必要时引入更复杂的机制。
参考资料:
- Claude Code 源码教学指南 - 中英双语 20 章教程
- Claude Code 官方记忆文档
- liuup/claude-code-analysis - 源码静态分析
- Claude Code Architecture Breakdown
- 深入浅出 Claude Code:从源码理解 CLAUDE.md
如果这篇文章对你有帮助,欢迎点赞收藏。关于 AI Agent 架构设计的更多内容,可以关注我后续的分享。