乐于分享
好东西不私藏

Claude Code 源码泄露深度解析:Dream 记忆整合机制

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 获取锁流程

  1. 读取现有锁的 mtime 和 PID
2.锁未过期<1hPID仍存活返回null阻塞
3.PID或已过期强制回收
  1. 写入自己的 PID,再次读取验证(防竞态)
5.读回的PID不是自己返回null竞争失败
  1. 返回先前 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 的开发者来说,这套设计至少有四点值得直接抄作业:

  1. 渐进式门控 — 从内存检查到文件 I/O,99% 的无效调用在前两级就挡住了
  2. 文件即状态 — 不引入额外数据库,用文件系统本身的元数据承载所有状态
  3. 权限最小化 — 子代理只能读和写指定目录,路径 normalize 防止遍历攻击
  4. 幂等 + 可回滚 — 任何失败都不留脏状态,下次重试安全无副作用

这大概就是「好的工程」的样子:简单、安全、不引人注意,但你离不开它。