Claude Code 的记忆系统是怎么工作的?源码级拆解
你周五用 Claude Code 调 bug 到凌晨两点,周一打开新会话,它张嘴就说:“上次那个 auth 模块的 bug 修好了吗?测试记得起 Redis。“你愣住了——你没告诉过它这些。
一、一个细思极恐的瞬间
用过 Claude Code 的开发者大概都经历过这个瞬间:
你关掉终端,第二天重新打开,开始一个新对话。Claude 什么都没问,直接开始干活。它知道你的项目用 pnpm 而不是 npm,知道测试需要本地 Redis,知道你讨厌 verbose 的输出。
你仔细回想:我什么时候告诉它这些的?
答案是:你没有直接告诉它。是它自己"记住"的。
Claude Code 每次启动都是一个全新的上下文窗口,不携带任何历史对话。所有它"知道"的东西,都必须在第一轮对话之前从磁盘加载。
我花了一周时间读 Claude Code 的 TypeScript 源码(是的,它是开源的),发现它的记忆系统比官方文档描述的复杂得多。今天来拆解一下。
二、两套记忆系统:你写的 vs AI 写的
Claude Code 有两套完全独立的记忆系统,各司其职:
关键区别:
| 维度 | CLAUDE.md | Auto Memory |
|---|---|---|
| 谁写的 | 你手动写 | Claude 自动写 |
| 存储位置 | 项目目录 / 用户目录 | ~/.claude/projects/ 下 |
| 同步方式 | Git(可提交) | 本地(不同步) |
| 更新频率 | 你改才变 | 每轮对话可能更新 |
| 大小限制 | 40KB(建议) | 索引 200 行 / 25KB |
| 加载方式 | 启动时全部加载 | 索引全加载,topic 按需 |
2.1 CLAUDE.md 的加载链
优先级(高→低):
1. 管理级 /etc/claude-code/CLAUDE.md ← IT 管理员写的,无法覆盖
2. 用户级 ~/.claude/CLAUDE.md ← 你的个人偏好
3. 项目级 ./CLAUDE.md 或 ./.claude/CLAUDE.md ← 团队共享(git 提交)
4. 本地级 ./CLAUDE.local.md ← 你的个人覆盖(不提交)
加载顺序:
工作目录 → 向上遍历目录树 → 加载沿途所有 CLAUDE.md
离工作目录近的优先级高
一个细节: CLAUDE.local.md 不在官方文档里,但源码中存在。它在项目 CLAUDE.md 之后、Auto Memory 之前加载。适合放个人的数据库密码、调试开关之类的——不提交到 git,但本地有效。
2.2 Auto Memory 的四种类型
Auto Memory 不是随意的笔记,而是有严格的分类:
为什么这样分? 因为团队协作时,你的编码偏好(user)不应该泄露给队友,但项目进度(project)应该共享。这种分类直接影响记忆的加载和同步策略。
三、三个时间尺度:实时 / 会话 / 梦境
这是整套系统最精妙的部分。Auto Memory 不是"Claude 注意到什么就随手记下来”,而是有三个独立的后台进程,在不同时间尺度上提取信息:
3.1 Per-Turn:实时提取(每轮对话后)
触发:每次完整查询循环结束
方式:fork 一个子 Agent 在后台运行
成本:极低(共享父 Agent 的 prompt cache)
权限:只读项目文件,只写记忆目录
一个细节: 如果主 Agent 在本轮已经写过记忆(你明确说"记住这个”),提取步骤会跳过那个范围,避免重复。
3.2 Per-Session:会话记忆(上下文增长时)
这是一个独立于 Auto Memory 的系统,目的是让当前会话的上下文在压缩后还能保留。
触发条件(三个同时满足):
✅ 总上下文 ≥ 10,000 tokens
✅ 上下文增长 ≥ 5,000 tokens(自上次提取后)
✅ 工具调用 ≥ 3 次
提取的结构化笔记:
┌─────────────────────────────────────────┐
│ Session Memory 模板 │
├─────────────────────────────────────────┤
│ ## Current State │
│ 当前正在做什么,进展到哪一步 │
│ (上限 ~2000 tokens) │
├─────────────────────────────────────────┤
│ ## Files and Functions │
│ 涉及的文件、函数、关键代码位置 │
│ (上限 ~2000 tokens) │
├─────────────────────────────────────────┤
│ ## Errors & Corrections │
│ 遇到的错误、尝试过的方案、最终解决方案 │
│ (上限 ~2000 tokens) │
├─────────────────────────────────────────┤
│ ## Worklog │
│ 工作日志:做了什么、还剩什么 │
│ (上限 ~2000 tokens) │
└─────────────────────────────────────────┘
总计上限 ~12,000 tokens
为什么需要这个? 当对话太长需要压缩时,有这些结构化笔记,auto-compact 可以复用它们,而不是从头重新摘要整个对话。效果好很多——因为笔记是增量提取的,上下文还完整时就记录了关键信息。
3.3 autoDream:梦境整合(每天/每几天)
这是最让人惊讶的部分。源码中明确把这叫做 “DreamTask”,UI 中会显示进度条。
整合流程(四阶段):
Phase 1: 定向
├── 读取 MEMORY.md 索引
└── 浏览现有 topic 文件
目的:了解当前记忆状态
Phase 2: 发现
└── 搜索日志和会话记录中的新知识
目的:找到需要整合的信息
Phase 3: 写入
├── 写入/更新 topic 文件
├── 合并新信号到现有文件
└── 相对日期 → 绝对日期("昨天" → "2026-05-11")
目的:持久化新知识
Phase 4: 清理
├── 修剪 MEMORY.md 至 200 行以下
├── 移除过期指针
└── 解决新旧事实矛盾
目的:保证记忆质量
一个有趣的细节: 如果你杀掉整合进程,锁的修改时间会回滚,让下一个会话可以重试。这个错误处理很体贴。
它什么时候运行? 不是等你关掉电脑或睡觉,而是在一次对话结束时——只要满足那三个 Gate。只是因为 24 小时和 5 个会话的门槛,大多数时候不会触发。
四、记忆召回:Sonnet 替你选
记忆存下来了,但怎么在需要的时候找到正确的记忆?
Claude Code 的方案是:让 Sonnet 替你选,不是关键词匹配。
为什么用 Sonnet 而不是关键词? 因为语义理解更好。比如用户问"怎么跑测试",关键词匹配可能找不到 feedback_testing.md,但 Sonnet 能理解这个意图。
但这里有一个结构性缺陷: 没有向量搜索,没有语义索引。如果 MEMORY.md 中的描述写得不好,或者 topic 文件太多(最多扫描 200 个,按修改时间排序),记忆就可能被"遗忘"。
五、什么能活过压缩?
长对话会被压缩。压缩后,什么能保留?
源码中的清理代码:
// 压缩时,强制清除所有缓存,从磁盘重新加载
getUserContext.cache.clear()
resetGetMemoryFilesCache('compact')
clearSystemPromptSections()
教训: 如果你在对话中告诉 Claude 什么重要的东西,确保它写入了记忆。否则压缩后就没了。
六、与其他工具的对比
| 维度 | Claude Code | Aider | Cursor | DeepSeek-TUI |
|---|---|---|---|---|
| 记忆系统 | ✅ 三层架构 | ❌ 无 | ⚠️ 简单 rules | ✅ 基础记忆 |
| 自动提取 | ✅ per-turn | ❌ | ❌ | ⚠️ 手动 |
| 梦境整合 | ✅ autoDream | ❌ | ❌ | ❌ |
| 记忆分类 | ✅ 4 种类型 | ❌ | ❌ | ❌ |
| 模型辅助召回 | ✅ Sonnet 选择 | ❌ | ❌ | ❌ |
| 团队同步 | ⚠️ feature flag | ❌ | ❌ | ❌ |
| 记忆持久化 | ✅ 文件系统 | ❌ | ⚠️ 配置文件 | ✅ 文件系统 |
结论: Claude Code 的记忆系统是目前 AI 编程工具中最完善的。Aider 完全没有记忆,Cursor 只有简单的 rules 文件,DeepSeek-TUI 有基础记忆但没有自动提取和整合。
七、这套系统的局限
读完源码,我认为有几个结构性局限:
7.1 记忆是 per-repo 的
你在项目 A 教 Claude 的偏好,不会自动带到项目 B。用户级的 ~/.claude/CLAUDE.md 可以处理一部分,但它是静态的——Claude 不能在会话中自动更新它。
7.2 记忆是 machine-local 的
项目目录名经过 sanitize(非字母数字替换为连字符),但不同机器、不同用户名、不同挂载点会产生不同的目录名。同一项目,不同机器,不同记忆。
7.3 没有记忆衰减
所有记忆都是平等的。一个月前的偏好和今天的偏好权重一样。没有"热度"概念,没有自动遗忘机制。
7.4 依赖 Sonnet 的选择能力
如果 Sonnet 选错了记忆文件,或者描述写得不好,记忆就"丢了"。没有 fallback 机制。
八、对我们的启示
这套系统给我们什么启发?
8.1 记忆是 AI 工具的核心差异化
目前市面上的开源 AI 编程工具(Aider、OpenCode、Continue),没有一个有真正意义上的记忆系统。这是差异化机会。
8.2 梦境整合是一个优雅的设计
不是每次对话都提取记忆(太频繁,噪音多),也不是完全不提取(会丢失)。三个时间尺度的设计——实时、会话、梦境——是一个很好的平衡。
8.3 模型辅助召回比关键词匹配更好
让 Sonnet 选择相关的记忆文件,而不是 grep 关键词。这在语义理解上好很多,但成本也更高。
对于开源工具,可以考虑一个折中方案:先用关键词过滤缩小范围,再用小模型选择。
8.4 记忆质量 > 记忆数量
Claude Code 严格限制记忆的大小(200 行索引、4KB 单文件、60KB 会话累计)。这不是偷懒,而是有意为之——太多记忆反而会干扰。
九、写在最后
读完 Claude Code 的记忆系统源码,我最大的感受是:
好的 AI 工具不是模型能力的比拼,而是工程设计的比拼。
同样的 Claude 模型,有记忆系统和没有记忆系统,用户体验天差地别。记忆系统本身不需要更强大的模型,它需要的是精心的工程设计——什么时候提取、怎么存储、如何召回、何时整合。
这些工程细节,才是真正的壁垒。
本文基于 Claude Code TypeScript 源码分析。 源码地址:github.com/anthropics/claude-code 参考文章:How Claude Code memory actually works