Claude Code 源码解构 :记忆系统(二)
摘要 Claude Code 采用三层记忆流水线系统解决AI编程中的记忆问题:短期记忆(Session Memory)实时记录对话,中期记忆(Auto Memory)保存用户偏好和经验,长期记忆(Auto Dream)定期清理合并信息。系统通过四种记忆类型(user/feedback/project/reference)分类存储信息,采用纯文本Markdown文件格式确保可读性和可维护性,并设计双

一、导读
关联文章:
Claude Code 源码解构 :如何生成深度摘要(一)
Claude Code 的记忆系统并非简单的日志堆砌,而是一个由后台子代理(Forked Agent)自动运转的三层记忆流水线。它巧妙解决了 AI 编程中常见的“聊得越多忘得越快”的问题。核心要点如下:
- 短期记忆(Session Memory):像一个实时的会议记录员,在后台默默写笔记,让 AI 在清理冗长对话时不会“断片”。
- 中期记忆(Auto Memory):每轮对话结束后,自动提取你的偏好、踩过的坑等“代码里找不到的经验”,并长期保存。
- 长期记忆(Auto Dream):类似于人类的睡眠机制,定期在后台清理过期信息、合并重复内容,防止记忆越来越臃肿。
- 底层魔法:它利用了“提示词缓存(Prompt Cache)”技术,让这些后台动作几乎不花额外的 Token,也不影响你的操作速度。
二、记忆四类型系统
Claude Code 的记忆系统遵循一个核心原则:只保存不可从当前项目状态推导的信息。
Claude Code 的记忆系统服务于 4 种类型:用户偏好(user)、反馈修正(feedback)、项目信息(project)、外部引用(reference)。每种类型有独立的存储文件和触发条件。这种“闭合系统”的设计逻辑是为了防止类型爆炸(Type Explosion)与检索歧义:
| 类型 | 核心功能 | 解决的问题 |
|---|---|---|
| user | 沟通风格 | 解决“沟通风格”问题,决定输出是专业还是通俗。 |
| feedback | 行为对齐 | 解决“对齐”问题,防止 Agent 在同一个错误上犯两次。 |
| project | 决策上下文 | 解决“决策上下文”问题,让 Agent 理解代码背后的动机。 |
| reference | 工具链打通 | 解决“工具链打通”问题,连接外部生态。 |
实战练习:记忆类型分类
以下信息应该保存为哪种记忆类型?
- “我们团队使用 Linear 项目 ‘BACKEND’ 来追踪后端 Bug” —— reference(外部系统指针)
- “用户是初级开发者,第一次使用 TypeScript” —— user(用户画像)
- “集成测试必须使用真实数据库,不要 mock —— 上次 mock 导致生产事故” —— feedback(行为指导,包含 Why: 生产事故)
- “认证中间件重写是因为法律合规要求,不是技术债” —— project(项目决策,包含 Why: 法律合规)
- “API 文档的 Swagger UI 在 http://localhost:3000/api-docs” —— reference(外部引用)
- “每次创建新组件时,先写测试再写实现” —— feedback(行为指导,包含明确的规则和隐含的原因)
有意思地方:Feedback 类型的正负反馈平衡
其中最有意思的设计在 feedback 类型上。大多数 AI 系统只记“负反馈”——你骂它“别 mock 数据库!”,它记住了。但 Claude Code 的 Prompt 里明确要求正面反馈也要记:
“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.”
“如果只记批评不记表扬,时间长了模型就会趋向保守——不敢做决定、不敢主动行动,事事请示。Prompt 还特别提醒:‘批评很容易注意到;肯定更安静——要主动留意。’”
三、记忆文件格式与底层架构解析
1. 记忆文件的物理形态:纯文本的胜利
每条记忆在底层并不是存在高大上的向量数据库里,而是被保存为一个独立的 Markdown 文件,并使用 YAML Frontmatter 声明元数据。
格式强制要求包含 name(记忆名称)、description(一行描述,用于判断未来对话的相关性)和 type(必须是前文提到的四种类型之一)。
为什么使用 Markdown 文件而非数据库?
这是一个极具工程智慧的架构选择,优势显而易见:
- 可读性:开发者可以直接用 VSCode 等文本编辑器查看和修改记忆。
- 版本控制:记忆文件天然支持 Git 追踪(如果放在项目目录下)。
- 可移植性:文件系统是最低公共 denominator(公分母),无需额外依赖。
- 可调试性:出现问题时,直接
ls和cat就能诊断。 - 成本:无需维护数据库连接、索引、备份。
“对于 Agent 的记忆场景,查询模式是简单的‘加载所有相关记忆’而非复杂的关联查询,文件系统的能力已经足够。”
2. MEMORY.md 索引文件:双重容量保护
MEMORY.md 是整个记忆系统的入口点——它不是记忆本身,而是一个索引文件。每次对话开始时,它会被自动加载到上下文中,让 Agent 快速了解已有的记忆概况。
为了防止索引文件过载,系统执行严格的双重截断逻辑。
源码位置:index.ts
const MAX_ENTRYPOINT_LINES = 200;
const MAX_ENTRYPOINT_BYTES = 25_000; // 约 25KB
export function truncateEntrypointContent(content: string) {
// 1. 先按行截断(保持自然边界,200行上限)
let lines = content.split('\n').slice(0, MAX_ENTRYPOINT_LINES);
// 2. 再按字节截断(硬性底线,25KB上限)
let result = Buffer.from(lines.join('\n')).slice(0, MAX_ENTRYPOINT_BYTES).toString();
return result;
}
双重容量保护的设计智慧:
为什么需要两层限制?它们各有侧重:
- 行数限制(200行):保护的是 Agent 的理解效率。即使每行很短,200 行以上的索引也需要 Agent 花费更多 Token 去筛选。它确保了索引始终是一个“快速浏览”的工具,而非“深度阅读”的文档。
- 字节限制(25KB):保护的是上下文预算。如果每条索引的描述都很长(接近 150 字符),200 行可能达到 30KB。字节限制提供了一个硬性的成本底线。
- 顺序的讲究:先按行,再按字节。当记忆少但描述长时,触发字节限制;当记忆多但描述短时,触发行数限制。
四、三层记忆流水线实战解读
第一层:Session Memory —— 会话内的“实时快照”
当对话 Token 增长 ≥5K 且工具调用 ≥3 次时,后台会 Fork 一个子代理,更新 {sessionId}/session-memory/summary.md。需要明确的是,记忆最终落盘就是纯文本的 Markdown 文件,而不是存入向量数据库,这保证了极低的管理成本和极高的可读性。
核心配置(阈值控制):
源码位置:session.ts
export const DEFAULT_SESSION_MEMORY_CONFIG = {
minimumMessageTokensToInit: 10_000, // 首次提取阈值
minimumTokensBetweenUpdate: 5_000, // 增量更新阈值
toolCallsBetweenUpdates: 3, // 动作频率阈值
}
更新方式:就地编辑而非追加
Session Memory 不是追加写入,而是让 forked agent 用 Edit 工具就地编辑笔记文件的每个 section。Prompt 中有严格约束:
“NEVER modify, delete, or add section headers… ONLY update the actual content that appears BELOW the italic section descriptions”
深度下钻:Auto Compact 协同优化
这是 Session Memory 最巧妙的用法,但原理比“直接复用”更细致。
源码里有一条专门的 sessionMemoryCompact 分支:当 Auto Compact(自动压缩)触发后,系统会先等正在进行的 Session Memory 提取结束,再尝试直接用 summary.md 构造压缩摘要。
- 成功时:就不会再额外发起一次昂贵的 LLM 压缩请求。
- 回退机制:如果 feature gate 没开、Session Memory 还是空模板、或者边界算不出来,才回退传统 compact。
“所以更准确的说法不是‘永远零调用’,而是‘优先走一条零额外 compact 调用的快路径’。”
第二层:Auto Memory Extraction —— 跨会话的“自动沉淀”
在每轮对话结束(Handle Stop Hooks)时触发。为了节省 Token,它采用 Prompt Cache 共享模式。
解释一下 Prompt Cache:
把它理解为餐厅里的“常客菜单”。当主 Agent 已经和模型交流了半天(产生了大量上下文),这些对话>内容已经在服务器端被“缓存”了。此时后台子代理(Forked Agent)接手去提取记忆时,它不需要把前面的聊天记录重新发送一遍让模型重读,而是直接说“照着刚才那份菜单给我提取一下重点”,这不仅极大地省了 Token 钱,还让提取速度快如闪电。
权限沙箱(最小权限原则):
后台子代理的权限被严格限制,确保安全。
源码位置:sandbox.ts
// 权限过滤逻辑
if (tool === Read || tool === Grep || tool === Glob) allow();
if (tool === Bash && isReadOnly(command)) allow();
if ((tool === Edit || tool === Write) && isAutoMemPath(file_path)) allow();
deny(); // 禁止修改源码、调用 MCP 或网络工具
Prompt 引用:提取指令(完整中文版)
为了确保提取质量,系统为后台 Agent 提供了核心提取指令。这里分为两种情况:单用户模式和团队模式(包含 Team Memory)。
源码位置:prompts.ts
基础行为准则(所有模式通用):
“你现在作为记忆提取子代理(subagent)运行。分析上方最近的约 {newMessageCount} 条消息,并使用它们来更新你的持久化记忆系统。
你的 turn(轮次)预算有限。FileEdit 需要先对同一个文件执行 FileRead,因此高效的策略是:第 1 轮 —— 并行发出所有你可能更新的文件的 FileRead 调用;
第 2 轮 —— 并行发出所有 FileWrite/FileEdit 调用。不要在多个轮次中交替进行读写操作。
如果用户明确要求你记住某事,请立即将其保存为最适合的类型。如果他们要求你忘记某事,请找到并删除相关条目。”
单用户模式(无 Team Memory)保存策略:
“保存记忆是一个两步过程:
第 1 步 —— 将记忆写入其独立的文件中(例如:user_role.md、feedback_testing.md),并使用以下 frontmatter 格式:
[示例格式]
第 2 步 —— 在MEMORY.md中添加指向该文件的指针。MEMORY.md是一个索引,而不是记忆本身 —— 每个条目应该是一行,长度在 150 个字符以内:- [标题](file.md) — 单行吸引人的描述。它没有 frontmatter。绝不要将记忆内容直接写入MEMORY.md中。
MEMORY.md始终会被加载到你的 system prompt 中 —— 超过 200 行的内容将被截断,所以请保持索引简洁- 按主题而非时间顺序对记忆进行语义组织
- 不要写入重复的记忆。在写入新记忆之前,先检查是否存在可以更新的现有记忆。”
实战案例解读:索引与内容的物理分离
在上面的 Prompt 中明确规定了“两步走”。这到底长什么样?
- 记忆内容(Content):它保存在如
~/.claude/projects/my-app/memory/feedback_ci.md这样的独立文件中,里面写着“CI 经常因为缓存挂掉,记得跑rm -rf .cache”。- 记忆索引(Index):它保存在
~/.claude/projects/my-app/memory/MEMORY.md中,内容可能只有一行:- [CI 缓存问题解决](feedback_ci.md) - 遇到构建报错时需要清理缓存目录。- 为什么要这样设计? 因为如果把所有长篇大论的记忆都塞进 Prompt 里,不但贵,还会让模型分心。通过只加载轻量级的
MEMORY.md(类似于一本书的目录),Agent 在需要时再通过FileRead去读取具体的.md内容,这是一种极其优雅的按需加载(Lazy Loading)策略。
团队模式(Team Memory 开启)保存策略:
“保存记忆是一个两步过程:
第 1 步 —— 将记忆写入所选目录(私人或团队,根据该类型的作用域指导)中独立的文件,并使用以下 frontmatter 格式:
[示例格式]
第 2 步 —— 在同一目录的MEMORY.md中添加指向该文件的指针。每个目录(私人和团队)都有其自己的MEMORY.md索引。
- 两个
MEMORY.md索引都会被加载到你的 system prompt 中 —— 超过 200 行的内容将被截断,所以请保持它们简洁。- 你绝对不能在共享的团队记忆中保存敏感数据。例如,绝不要保存 API 密钥或用户凭证。”
深度解读:
- 两步走策略:强制要求先写内容文件,再更新索引文件。这种“指针机制”避免了主
MEMORY.md膨胀。 - 团队模式的隔离:在团队模式下,Prompt 明确区分了私人目录和团队目录,并加入了强硬的安全底线(禁止保存敏感数据)。
互斥机制:如果主 Agent 在本轮已手动写了记忆(如用户说“记住这个”),后台提取会检测到并自动跳过,避免冲突。
第三层:Auto Dream —— 记忆的“熵减与整合”
当满足特定阈值时(通常是累积 ≥5 个新会话且距上次整合 ≥24 小时),系统会在后台自动触发“梦境”(Auto Dream)任务。这不是用户主动敲命令,而是系统自我维护的“垃圾回收”机制。
“梦境”四个阶段:
- Orient(定位):扫描
memory/目录构建地图。 - Gather(采集信号):通过 Grep 搜索 JSONL 转录文件,寻找用户纠错、显式保存等信号。
- Consolidate(整合):关键动作——将相对日期(“下周二”)转为绝对日期(“2026-04-07”),合并重叠条目。
- Prune(修剪):删除过时事实,更新
MEMORY.md索引(保持在 200 行/25KB 以内)。
核心算法:MEMORY.md 索引保护与修剪
在定位和整合后,为了防止索引文件过载,系统会执行严格的截断逻辑(上文提到的 200行/25KB)。
“Auto Dream 通过将相对日期转为绝对日期,并利用双重截断保护索引,解决了记忆随时间失效与上下文爆炸的问题。”
四、实战场景:三层记忆如何协同工作?
为了直观理解这三层管线,我们模拟一个真实开发场景:
1. 新项目接入(Onboarding)
应用:开发者首次进入项目。
价值:project 记忆会自动解释代码库中那些“看起来不合理但有特殊原因”的设计(如:为了兼容旧版浏览器而保留的 Polyfill),reference 则直接拉齐工具链地址,极大缩短了开发者的摸索周期。
2. 复杂重构(Refactoring)
应用:对核心模块进行大规模调整(Session Memory 发力)。
操作:你正在让 Agent 重构 50 个文件。
表现:对话进行到第 20 轮时,上下文已满。此时 Session Memory 已经在后台更新了 3 次笔记。触发 Auto Compact 时,Agent 直接从 summary.md 读取“当前重构进度”和“待办列表”,而不需要重新分析前 20 轮对话。
结论:Session Memory 保证了长任务中途“不断片”。
3. 跨天处理环境 Bug(Auto Memory 表现)
应用:在多个微服务或仓库间频繁切换,并解决偶发问题。
操作:周五下午,你和 Agent 发现由于 node_modules 缓存导致了一个诡异的 CI 报错,并找到了 rm -rf .cache 的方案。
表现:对话结束时,Auto Memory 提取了这条反馈。周一你开启新会话问“CI 又报错了”,Agent 会立即从 memory/feedback_ci.md 提示你尝试清理缓存。
结论:Auto Memory 实现了跨会话的“经验传承”。
4. 技术栈迁移半年后(Auto Dream 价值)
应用:系统后台运行 Auto Dream 进行知识沉淀与清理。
操作:项目从 Webpack 迁移到 Vite 已半年。
表现:半年前的记忆里有大量“Webpack 调试技巧”。Auto Dream 在某次运行中发现这些文件很久没被引用,且最近 50 次会话都在用 Vite,它会自动合并“构建工具”相关笔记,删除陈旧的 Webpack 记录。
结论:Auto Dream 负责“知识保鲜”,防止认知污染。
五、记忆系统评估与工业级避坑指南
1. 记忆系统的能力边界与隐性成本
在享受自动化记忆带来的便利时,必须清晰认知其架构层面的代价:
-
提取质量高度依赖模型能力
Forked agent 本质上是让 LLM 做“阅读理解 + 笔记整理”。为了控制成本,提取 Prompt 明确要求它只依据最近的对话内容写 memory,绝对不要再去 grep 代码或读源码验证。
“这让流程变得便宜,但也意味着它写入的是‘对话里被说过且模型认为值得记住的事实’,而不是‘再次核实后的客观事实’。一旦写错,后续会话中这个错误记忆很容易被召回,产生极强的放大效应。” -
截断保护可能导致信息丢失
MEMORY.md强制设定的“200 行 + 25KB”硬限意味着记忆总量存在绝对物理上限。对于积累了大量记忆的长期项目,索引截断是不可避免的。虽然 Auto Dream 的修剪机制能缓解这一问题,但无法从根本上解决超大规模项目记忆溢出的痛点。 -
后台 Fork 的隐性 Token 成本
尽管 Forked agent 巧妙地利用了 Prompt Cache 共享前文,但每次触发提取时,它仍需要生成全新的输出(Output Tokens)。在每分钟多轮工具调用的“高频对话场景”下,Session Memory 的频繁更新会导致不容忽视的 Token 消耗积少成多。
2. 避坑指南:正确使用记忆系统的姿势
误区一:盲信记忆(Stale Data)
- 错误做法:记忆说“入口在
main.ts”,Agent 直接去改。 - 正确做法:记忆只是时间点的快照。Agent 在根据记忆行动前,必须先通过
FileRead或ls验证目标文件或状态是否依然存在。
误区二:把记忆系统当作项目文档
- 错误做法:试图用记忆系统替代项目文档,让 Agent 记住长篇大论的技术规范和接口设计。
- 正确做法:这违背了“只保存不可推导信息”的原则。技术规范应该放在代码仓库的
docs/目录中,而架构决策的“为什么(Why)”才应该放在记忆系统中。
误区三:忽略相对日期问题
- 错误做法:用户说“这个功能下周二上线”,Agent 原封不动保存了“下周二上线”。但两天后,“下周二”变成了“这周二”;再过一周,变成了“上周二”。这种记忆不仅无用,还会严重误导后续的逻辑推理。
- 正确做法:所有涉及时间的记忆必须在提取或 Auto Dream 阶段转化为绝对日期。Agent 应该将“下周二”转换为具体日期(如 2026-04-07)后再行保存。
误区四:缓存击穿(Cache Miss)
- 错误做法:为了安全,给后台 Agent 阉割掉大量主 Agent 的工具。
- 正确做法:后台 Agent 的工具列表必须与主 Agent 保持字节级一致(哪怕部分工具在逻辑上被禁用),否则会导致 Prompt Cache 校验失败,致使每次提取都变成昂贵的全量请求。
3. 五层加载体系(优先级从低到高)
Claude Code 将记忆与指令分离,加载顺序如下:
- 全局级 (
/etc/claude-code/CLAUDE.md) - 用户级 (
~/.claude/CLAUDE.md) - 项目级 (
.claude/rules/*.md) - 本地级 (
CLAUDE.local.md) - 自动记忆 (
MEMORY.md) —— 注意:MEMORY.md 作为第一条 User Message 注入。
4. 验证优先原则与避坑总结
- 认知冗余:手动维护的
CLAUDE.md与自动记忆是互斥的。如果你写了明确规则,系统会优先跳过自动提取。
参考文献
更多推荐

所有评论(0)