Claude Code 源码泄露深度解析:Dream 记忆整合机制
Claude Code 源码泄露深度解析:Dream 记忆整合机制
Claude Code 源码通过 npm source map 泄漏后,其内部记忆整合机制「Dream」首次曝光。本文逐文件拆解这套借鉴「睡眠巩固记忆」隐喻的后台系统。
引言
4 月 1 日,Claude Code 的完整源码通过 npm registry 的 source map 文件被意外暴露,50 万行 TypeScript 公之于众。社区已成功从源码编译并运行。
在众多被曝光的子系统中,Dream(梦境) 机制尤其值得关注——它是 Claude Code 实现跨会话记忆的核心,设计精巧程度远超预期。
Part 1:深度技术分析
1. 概述
Dream 是 Claude Code 内置的后台记忆整合机制。它借鉴神经科学中「睡眠巩固记忆」的隐喻——在用户交互间隙,自动启动子代理,对分散的会话记忆进行回顾、去重、修正和索引,使未来对话能快速定位上下文。
整个机制的核心设计原则:零干扰、低成本、幂等安全。
2. 核心文件清单
- **autoDream.ts** — 调度主逻辑:门控 → 锁 → 子代理 → 结果处理
- config.ts — 功能开关(用户设置 / GrowthBook 远程配置)
- consolidationLock.ts — 基于文件 mtime 的分布式锁
- consolidationPrompt.ts — 4 阶段整合 Prompt 模板
- DreamTask.ts — 任务状态机 + UI 注册
- backgroundHousekeeping.ts — 启动时初始化 Dream 闭包
- stopHooks.ts — 每轮对话结束触发 Dream
3. 启动与触发
3.1 初始化
Dream 在应用启动时通过 startBackgroundHousekeeping() 初始化。initAutoDream() 创建一个闭包作用域隔离所有可变状态(lastSessionScanAt 等),并将实际执行函数赋给模块级 runner 变量。
3.2 触发时机
每轮 AI 回复结束后,handleStopHooks() 以 fire-and-forget 模式调用 executeAutoDream()。仅主线程触发——子代理和 –bare 模式下不触发。
4. 三级门控:从廉价到昂贵
源码注释的设计哲学:
Gate order (cheapest first):
1. Time: hours since lastConsolidatedAt >= minHours (one stat)
2. Sessions: transcript count with mtime > lastConsolidatedAt >= minSessions
3. Lock: no other process mid-consolidation
4.1 功能开关门(零 I/O)
`isGateOpen()` 依次检查:KAIROS 模式(互斥)→ 远程模式(不触发)→ 自动记忆开关 → autoDreamEnabled。
isAutoDreamEnabled() 优先级:settings.json 中的 autoDreamEnabled(用户显式设置)> GrowthBook 特性标志 tengu_onyx_plover 的 enabled 字段。
4.2 时间门(一次 stat 调用)
readLastConsolidatedAt() 读取锁文件的 mtime。距离上次整合不足 minHours(默认 24 小时)则直接退出。锁文件同时充当「上次整合时间」的持久存储。
4.3 扫描节流
当时间门通过但会话门未通过时,锁文件 mtime 不前进,导致下一轮时间门仍然通过。扫描节流用 10 分钟间隔(SESSION_SCAN_INTERVAL_MS)防止重复目录扫描。
4.4 会话门(目录扫描)
查找自上次整合以来有修改的会话转录文件(.jsonl),排除当前会话。默认需要 >= 5 个会话。
📌 配置来源:默认配置由 GrowthBook 特性标志 tengu_onyx_plover 下发,本地兜底值为 minHours=24, minSessions=5。
5. 分布式锁:文件 mtime 即状态
consolidationLock.ts 实现了极简但健壮的进程间锁。锁文件路径为 <autoMemPath>/.consolidate-lock。
- 文件内容(PID) — 标记持有者,做进程存活检查
- 文件 mtime — 记录上次整合完成时间戳
- **文件是否存在** — 从未整理 → 不存在 → mtime = 0
5.1 获取锁流程
- 读取现有锁的 mtime 和 PID
2.锁未过期(<1h)且PID仍存活→返回null(阻塞)
3.死PID或已过期→强制回收
- 写入自己的 PID,再次读取验证(防竞态)
5.读回的PID不是自己→返回null(竞争失败)
- 返回先前 mtime(供回滚用)
5.2 回滚机制
整理失败时,用 utimes() 把文件修改时间回退到整理之前,清空 PID 防止自阻塞。priorMtime = 0 时直接删除文件(恢复到「从未整理」状态)。
6. Prompt 工程:四阶段整合协议
Phase 1 — Orient(定位)
浏览记忆目录、读 MEMORY.md 索引、了解已有 topic 文件,避免创建重复内容。
Phase 2 — Gather(采集)
按优先级采集新信息:日志文件 > 已过时记忆 > 转录搜索(窄搜索,不整体读取 JSONL)。
Phase 3 — Consolidate(整合)
合并新信号到已有文件、相对日期转绝对日期、删除被证伪的旧记忆。
Phase 4 — Prune & Index(剪枝和索引)
MEMORY.md 保持 200 行 / 25KB 以内,每条索引一行 ~150 字符。
自动 Dream 还会在 Prompt 尾部追加工具约束:Bash 限制为只读命令(ls, find, grep, cat 等),以及待审阅的会话 ID 列表。
7. 子代理执行与权限沙箱
7.1 Forked Agent
Dream 使用 runForkedAgent() 启动——完美分叉,共享父对话的 prompt cache。设置 skipTranscript: true 不写转录,querySource 标记为 auto_dream。
7.2 权限沙箱
- FileRead / Grep / Glob — ✅ 完全放行
- Bash — ⚠️ 仅只读命令
- FileEdit / FileWrite — ⚠️ 仅限 auto-memory 目录
- REPL — ✅ 放行
- 其他所有工具 — ❌ 拒绝
🛡️ 安全设计:路径校验使用 isAutoMemPath(),对路径 normalize() 后检查是否以 getAutoMemPath() 为前缀,防止 ../ 路径遍历攻击。
8. 任务生命周期管理
状态机(DreamTaskState):
-registerDreamTask()→running
-completeDreamTask()→completed
-failDreamTask()→failed
-DreamTask.kill()→killed(用户按x中止)
阶段检测仅两个阶段:starting 和 updating。不解析 4 阶段 Prompt 语义——第一个 Edit/Write tool_use 出现时从 starting 翻转为 updating。
进度监控:makeDreamProgressWatcher() 监听子代理的每条 assistant 消息,提取 text blocks、统计 tool_use count、收集 Edit/Write 的 file_path。最多保留最近 30 个 turn,UI 只显示最近 6 个。
9. 与 ExtractMemories 的关系
- **ExtractMemories**:每轮触发,只看当前会话最新消息,提取+写入新记忆。类比:短时记忆 → 长时记忆。
- Dream:~24 小时触发一次,跨多个历史会话,整合+去重+剪枝。类比:长时记忆的整理归档。
10. UI 呈现
- 底部状态栏:Dream 运行时显示「dreaming」
- 详情对话框(Shift+↓):Status / 耗时 / 会话数 / 触达文件数 / 推理摘要
- 完成通知:聊天流中插入「Improved N files」(区别于 ExtractMemories 的 Saved)
- 中止:详情面板按 x 可停止,自动回滚锁文件
11. 设计亮点总结
1. 最廉价门控优先
功能开关(内存)→ 时间门(1次stat)→ 扫描节流(闭包变量)→ 会话门(目录扫描)→ 分布式锁(文件I/O)。99% 的轮次在前两级就退出。
2. 文件 mtime 即状态
锁文件身兼三职——互斥锁、时间戳存储、PID 记录。没有独立的数据库或状态文件。
3. 防御性配置解析
getConfig() 对 GrowthBook 返回值做逐字段类型校验和有限性检查。
4. 闭包隔离测试
所有可变状态封装在 initAutoDream() 闭包中,测试只需重新调用即可获得干净环境。
5. 权限沙箱 + 路径 normalize
子代理只能读文件和写 auto-memory 目录,路径经过 normalize 防止遍历攻击。
6. 优雅降级
任何环节失败都静默降级。失败后回滚锁 mtime,由扫描节流提供自然退避。
7. 与 KAIROS 模式互斥
KAIROS(助手模式)有自己的 disk-skill dream,自动 Dream 主动让路。
Part 2:小白也能看懂的版本
一句话总结
Claude Code 会像人一样「做梦」——在你不用它的时候,自动回顾最近的聊天记录,把重要的事情整理成笔记,下次你来的时候它就能更快想起之前聊了什么。
为什么需要「做梦」?
想象你有一个非常厉害的助理,但他有个问题:每次见面都会忘记上次聊了什么。
Claude Code 解决这个问题的方式是给助理配了一个「笔记本」(memory 目录)。但聊天多了,笔记零散、重复、甚至互相矛盾;笔记本越来越乱,想找东西越来越慢。
所以 Claude Code 设计了一个「整理笔记」的后台任务,代号 Dream——趁你不注意的时候,悄悄帮你把笔记整理好。
Dream 的完整流程
你和 Claude 聊完一轮
↓
Claude 偷偷看一眼:「要不要整理笔记?」
↓
┌─ 功能关了?→ 算了
├─ 距离上次整理没过 24 小时?→ 算了
├─ 这段时间聊天不到 5 次?→ 算了
└─ 别的进程正在整理?→ 算了
↓
全部条件满足
↓
派一个「分身」去默默整理笔记
↓
整理完了,轻轻说一句:「Improved 3 files」
四个判断条件
第 1 层:开关有没有打开?(零成本)
只是查一个内存里的变量,几乎不花时间。
第 2 层:上次整理是多久前?(一次系统调用)
看一个文件的「最后修改时间」,距离上次超过 24 小时才继续。
第 3 层:最近聊了几次?(扫描目录)
需要扫描会话目录,有 10 分钟冷却期。至少聊了 5 个不同会话才继续。
第 4 层:有没有别人正在整理?(文件锁)
有另一个 Claude Code 窗口在整理就不重复劳动。
💡 设计亮点:99% 的时候在第 1、2 层就退出了,所以对性能几乎零影响。
文件锁:一个文件干三件事
这是整个设计里最巧妙的地方。用一个 .consolidate-lock 文件同时实现三个功能:
- 文件内容(PID) — 判断是谁在整理、那个进程还活着吗
- 文件修改时间 — 记录上次是什么时候整理完的
- **文件是否存在** — 从来没整理过 → 文件不存在
如果整理失败了:把修改时间回退到整理之前,就像什么都没发生过。下次条件满足了重新来。
整理笔记的四个步骤
Step 1 — 看看现在笔记本长什么样
打开记忆目录,读 MEMORY.md 索引,了解已有内容。
Step 2 — 找找有什么新信息
翻最近的聊天记录(只搜关键词,不全看),看有没有过时的笔记。
Step 3 — 动手整理
合并新信息到已有笔记(不创建重复的),「昨天」改成具体日期,删掉错误的旧笔记。
Step 4 — 更新索引
确保 MEMORY.md 在 200 行以内,每条索引一行,去掉过时链接。
安全限制
整理笔记的「分身」权限被严格限制:
- ✅ 读任何文件
- ✅ 搜索文件内容
- ✅ 执行只读命令(ls, grep, cat 等)
- ✅ 写文件到记忆目录内
- ❌ 写文件到其他任何地方
- ❌ 执行会修改系统的命令
Dream vs ExtractMemories
| 维度 | ExtractMemories | Dream |
|---|---|---|
| 做什么 | 把刚聊的有价值信息存下来 | 把历史笔记整理、去重、更新 |
| 频率 | 每次聊完都跑 | ~24 小时跑一次 |
| 范围 | 只看当前最新消息 | 回顾多个历史会话 |
| 打比方 | 上课时做的随堂笔记 | 期末前整理成复习资料 |
最值得学习的三点设计
1. 能不干就不干
四层检查从便宜到贵,绝大多数时候在第一层就退了。这是性能优化的金标准。
2. 一个文件三种用途
锁文件的内容是 PID(标记谁在用),修改时间是上次整理时间,存在与否表示是否从未整理。极致简洁。
3. 永远优雅失败
任何一步出错都静默跳过,不弹报错,不卡住用户。失败了就把时间戳回退,下次自然会重试。
结语
Dream 机制展现了 Claude Code 在工程层面的成熟度:把一个「记忆整理」的需求,拆解成了门控、锁、权限沙箱、Prompt 协议、状态机、进度监控、遥测、优雅降级等完整子系统。每个模块都不复杂,但组合在一起构成了一个生产级的后台任务框架。
对于做 AI Agent 的开发者来说,这套设计至少有四点值得直接抄作业:
- 渐进式门控 — 从内存检查到文件 I/O,99% 的无效调用在前两级就挡住了
- 文件即状态 — 不引入额外数据库,用文件系统本身的元数据承载所有状态
- 权限最小化 — 子代理只能读和写指定目录,路径 normalize 防止遍历攻击
- 幂等 + 可回滚 — 任何失败都不留脏状态,下次重试安全无副作用
这大概就是「好的工程」的样子:简单、安全、不引人注意,但你离不开它。
夜雨聆风