Claude Code AutoDream 记忆巩固机制深度解析
摘要:Claude Code 通过 Extract Memories 和 AutoDream 两个子系统实现类人记忆管理。Extract Memories 在每轮对话后异步提取关键信息(用户偏好、反馈、项目上下文等),按类型分类存储。AutoDream 则定期(24小时+5个会话后)对记忆进行整合优化,模仿人类睡眠中的记忆巩固机制。系统通过严格门控控制触发频率,并定义了四类记忆(user/feed
基于 Claude Code 源码(通过 sourceMap 还原)逆向分析其后台记忆提取与巩固整理系统。
涉及两个核心子系统:Extract Memories(逐轮提取)和 AutoDream(跨会话巩固)。

一、为什么 AI 需要睡觉?
人类在睡眠中会进行记忆巩固——将短期记忆转化为长期记忆,整合相关信息,修剪过时内容。Claude Code 借鉴了这个机制:
- Extract Memories(提取记忆):每轮对话结束后,从当前对话中提取值得持久化的信息——相当于「白天随手记笔记」
- AutoDream(梦境巩固):积累足够多的会话后,对所有记忆文件进行整合、合并和修剪——相当于「夜间整理笔记」
两者协同工作,形成完整的记忆生命周期:
用户对话 → Extract Memories 提取新记忆 → 写入记忆文件
↓
AutoDream 定期触发 ← 24h + 5个会话 ← 积累信号
↓
整合、合并、修剪记忆文件 → 更新索引
二、Extract Memories:逐轮提取
2.1 触发时机
每轮对话结束时,在 stopHooks.ts 中以 fire-and-forget 方式触发:
// stopHooks.ts — 每轮结束后
if (!isBareMode()) {
void extractMemoriesModule!.executeExtractMemories(
stopHookContext,
toolUseContext.appendSystemMessage,
)
void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
}
关键设计决策:
- Fire-and-forget:不阻塞主对话,后台静默运行
- 仅主 agent 触发:子 agent(subagent)不触发,避免重复提取
- 互斥机制:如果主 agent 已经在当前轮写入了记忆文件,forked agent 跳过本次提取
- 回合节流:通过 GrowthBook flag
tengu_bramble_lintel控制每 N 轮才触发一次(默认每轮) - 最大 5 回合:硬编码上限,防止 agent 陷入验证循环
2.2 运行机制
Extract Memories 通过 runForkedAgent 启动一个完美分叉的子 agent:
- 共享父对话的 prompt cache(省钱)
- 跳过 transcript 写入(避免与主线程竞争)
- 受限的工具权限(只能读写记忆目录)
主对话线程 Forked Agent
│ │
├─ 用户消息 │
├─ 助手回复 │
├─ stopHooks 触发 │
│ └─ fire-and-forget ──→ ├─ 分析增量消息
│ ├─ 读取已有记忆清单
│ ├─ 提取新记忆
│ └─ 写入记忆文件
│ │
├─ 继续等待用户输入 ←─────── appendSystemMessage("Saved N memories")
2.3 完整提示词
以下是 Extract Memories 子 agent 接收的完整提示词(非 Team 模式):
你现在作为记忆提取子代理运行。分析上方最近的 ~{N} 条消息,并利用它们更新你的持久记忆系统。
可用工具:Read、Grep、Glob、只读 Bash(ls/find/cat/stat/wc/head/tail 及类似命令),
以及仅在记忆目录路径内的 Edit/Write。不允许使用 Bash rm。
所有其他工具 — MCP、Agent、可写的 Bash 等 — 将被拒绝。
你的回合数有限。Edit 需要先 Read 同一文件,因此高效策略是:
第 1 回合 — 并行发出所有可能需要更新的文件的 Read 调用;
第 2 回合 — 并行发出所有 Write/Edit 调用。
不要在多个回合中交替读写。
你必须仅使用最近 ~{N} 条消息中的内容来更新持久记忆。
不要浪费任何回合尝试进一步调查或验证这些内容 —
不 grep 源文件,不读代码确认某个模式是否存在,不执行 git 命令。
## 现有记忆文件
{已有的记忆文件清单}
写入前请检查此列表 — 更新现有文件而非创建重复项。
如果用户明确要求你记住某件事,立即将其保存为最合适的类型。
如果他们要求你忘记某件事,找到并删除相关条目。
## 记忆类型
...(见下文)
## 不要保存什么到记忆中
...(见下文)
## 如何保存记忆
...(见下文)
2.4 四种记忆类型
提示词定义了四种记忆类型,每种都有严格的 when_to_save、how_to_use 和 body_structure:
| 类型 | 用途 | 保存时机 | 内容结构 |
|---|---|---|---|
| user | 用户角色、偏好、知识 | 了解到用户任何个人信息时 | 自由格式 |
| feedback | 用户对工作方式的指导 | 被纠正或被确认时 | 规则 + Why + How to apply |
| project | 项目上下文和进展 | 了解到谁在做什么、为什么时 | 事实 + Why + How to apply |
| reference | 外部系统的指针 | 了解到外部资源位置时 | 自由格式 |
每种类型都附带具体示例。以 feedback 为例:
<type>
<name>feedback</name>
<description>用户给你的关于如何开展工作的指导 — 包括应该避免什么和应该继续做什么。
这些是需要重点读写的记忆类型。从失败和成功两方面记录:如果只保存纠正,
你会避免过去的错误但会偏离用户已验证的方法,并可能变得过于谨慎。</description>
<when_to_save>当用户纠正你的方法("不是那个"、"别"、"停止做 X")
或确认一个非显而易见的方法有效时("对没错"、"完美,继续这样做")。
纠正容易注意到;确认则更隐蔽 — 请留意它们。</when_to_save>
<how_to_use>让这些记忆指导你的行为,这样用户就不需要重复提供相同的指导。</how_to_use>
<body_structure>以规则本身开头,然后是 **Why:** 行和 **How to apply:** 行。
了解*原因*让你能判断边界情况,而不是盲目遵循规则。</body_structure>
<examples>
user: 不要在这些测试中 mock 数据库 — 上季度我们被坑了,mock 的测试通过了但生产迁移失败了
assistant: [保存反馈记忆:集成测试必须连接真实数据库,不能 mock。
原因:之前事故中 mock/生产环境差异掩盖了迁移问题]
</examples>
</type>
2.5 什么不该保存
提示词明确列出了排除规则,且即使用户明确要求保存也适用:
- 代码模式、约定、架构、文件路径或项目结构 — 可从当前项目状态推导
- Git 历史、最近更改或谁改了什么 — git log / git blame 是权威来源
- 调试解决方案或修复方法 — 修复在代码中;提交消息包含上下文
- 已记录在 CLAUDE.md 文件中的任何内容
- 临时任务细节:进行中的工作、临时状态、当前对话上下文
这条规则很有深意——如果用户说"帮我记住这周的 PR 列表",agent 不应该照做,而是要反问"其中有什么是令人意外的或非显而易见的?那才是值得保留的。"
2.6 保存格式:两步走
第一步 — 写记忆文件,带 frontmatter:
---
name: {{记忆名称}}
description: {{一句话描述 — 用于决定未来对话中的相关性,所以要具体}}
type: {{user, feedback, project, reference}}
---
{{记忆内容 — feedback/project 类型结构为:规则/事实,然后 **Why:** 和 **How to apply:** 行}}
第二步 — 在 MEMORY.md 索引中添加一行指针:
- [标题](文件名.md) — 一句话摘要(≤150 字符)
索引有硬约束:MEMORY.md 始终加载到系统提示词中,超过 200 行会被截断。
三、AutoDream:跨会话巩固
3.1 设计哲学
Extract Memories 是「逐轮提取」,关注增量;AutoDream 是「跨会话整合」,关注全局。它的设计灵感来自人脑的睡眠巩固机制——不是简单地记住更多东西,而是对已有记忆进行重组和优化。
3.2 三层门控(由便宜到昂贵)
AutoDream 不会每轮都跑,而是通过三层递进的门控来决定是否触发:
门控 1: 功能开关 ─→ 一次配置读取
门控 2: 时间门控 (24h) ─→ 一次 stat 调用
门控 3: 会话门控 (5个) ─→ 扫描 transcript 目录
门控 4: 分布式锁 ─→ 写入 lock 文件
门控 1:功能开关
function isGateOpen(): boolean {
if (getKairosActive()) return false // KAIROS 模式不触发
if (getIsRemoteMode()) return false // 远程模式不触发
if (!isAutoMemoryEnabled()) return false // AutoMemory 未启用不触发
return isAutoDreamEnabled() // 检查 AutoDream 开关
}
开关来源优先级:settings.json 的 autoDreamEnabled > GrowthBook feature flag tengu_onyx_plover。
门控 2:时间门控
距上次整合必须 ≥ minHours(默认 24 小时)。实现极其轻量——只需一次 stat 调用读取 lock 文件的 mtime:
const hoursSince = (Date.now() - lastAt) / 3_600_000
if (hoursSince < cfg.minHours) return
门控 3:会话门控
上次整合后必须有 ≥ minSessions(默认 5 个)新会话被修改过:
sessionIds = await listSessionsTouchedSince(lastAt)
sessionIds = sessionIds.filter(id => id !== currentSession)
if (sessionIds.length < cfg.minSessions) return
为防止时间门控通过但会话不够时每轮都扫描文件系统,还有一个 10 分钟扫描节流:
const SESSION_SCAN_INTERVAL_MS = 10 * 60 * 1000
if (Date.now() - lastSessionScanAt < SESSION_SCAN_INTERVAL_MS) return
门控 4:分布式锁
多进程场景下(比如多个终端窗口同时运行 Claude Code),需要确保同一时间只有一个进程在做巩固:
.lock 文件设计:
- 路径: <memory-dir>/.consolidate-lock
- 内容: 持有者 PID
- mtime: 获取锁的时间
- 过期: 60 分钟(防 PID 复用)
竞争逻辑:
- 读取 lock 文件,检查 PID 是否存活
- 如果 PID 死了或文件不存在 → 尝试获取
- 写入自己的 PID → 读回验证(防竞争)
- 验证通过 → 持有锁,开始巩固
3.3 完整提示词
AutoDream 的提示词是一个 4 阶段的结构化指令:
# Dream: Memory Consolidation
你正在执行一次梦境 — 对你的记忆文件进行反思性整理。
将你最近学到的内容综合为持久、组织良好的记忆,
以便未来的会话能够快速定位。
记忆目录: `{memoryRoot}`
会话记录: `{transcriptDir}`(大型 JSONL 文件 — 请窄范围 grep,不要读取整个文件)
---
## Phase 1 — Orient(定位)
- `ls` 记忆目录查看现有内容
- 阅读 MEMORY.md 了解当前索引
- 浏览已有主题文件,以便改进它们而非创建重复
- 如果存在 logs/ 或 sessions/ 子目录,查看最近的条目
## Phase 2 — Gather(收集信号)
按优先级寻找值得持久化的新信息:
1. **日报日志** (logs/YYYY/MM/YYYY-MM-DD.md) — 追加式的信息流
2. **已偏移的现有记忆** — 与代码库现状矛盾的事实
3. **Transcript 搜索** — 如果需要特定上下文:
grep -rn "<窄关键词>" {transcriptDir}/ --include="*.jsonl" | tail -50
不要穷举式地阅读 transcript。只查找你已经怀疑重要的内容。
## Phase 3 — Consolidate(整合)
对于每件值得记住的事,在记忆目录顶层写入或更新一个记忆文件。
使用系统提示词中自动记忆部分的记忆文件格式和类型约定。
重点:
- 将新信号合并到现有主题文件,而非创建近似重复
- 将相对日期("昨天"、"上周")转换为绝对日期
- 删除被证伪的事实 — 如果今天的调查推翻了旧记忆,修正源头
## Phase 4 — Prune(修剪索引)
更新 MEMORY.md,使其保持在 200 行以内且约 25KB 以内。
它是一个**索引**,不是内容堆 — 每条记录应为一行约 150 字符以内。
- 删除指向已过时、错误或被取代的记忆的指针
- 降级冗长条目:如果索引行超过约 200 字符,将详情移入主题文件
- 为新重要的记忆添加指针
- 解决矛盾 — 如果两个文件不一致,修正错误的那个
---
返回你整合、更新或修剪内容的简要摘要。
如果没有变化(记忆已经很紧凑),说明即可。
**工具约束:** Bash 限制为只读命令。
本次审查的会话 ({N} 个):
- {sessionId1}
- {sessionId2}
...
3.4 安全沙箱
AutoDream 子 agent 运行在严格受限的环境中,通过 createAutoMemCanUseTool 回调控制工具权限:
| 工具 | 权限 |
|---|---|
| Read / Grep / Glob | 完全允许(只读) |
| Bash | 仅允许只读命令(ls, find, grep, cat, stat, wc, head, tail) |
| Edit / Write | 仅允许记忆目录内的文件 |
| 其他所有工具 | 拒绝(MCP、Agent 等) |
这确保了巩固过程不会意外修改用户代码或执行危险操作。
3.5 容错机制
AutoDream 设计了多层容错:
场景 处理方式
──────────────────────────────────────────────────
fork 失败 → 回滚 lock mtime 到获取前的值
下次时间门控仍能通过
用户从后台任务面板杀死 → DreamTask.kill() 触发 abort + lock 回滚
进程崩溃 → lock 文件 PID 变为死进程
下一个进程 60 分钟后可回收
扫描节流 → 作为 backoff
避免频繁重试
回滚机制的核心是 rollbackConsolidationLock——将 lock 文件的 mtime 恢复到获取前的值,这样下次检查时时间门控仍然通过:
async function rollbackConsolidationLock(priorMtime: number): Promise<void> {
if (priorMtime === 0) {
await unlink(lockPath()) // 恢复到无文件状态
return
}
await writeFile(lockPath(), '')
await utimes(lockPath(), priorMtime / 1000, priorMtime / 1000)
}
3.6 UI 可见性
Dream 任务通过 DreamTask 注册到任务系统,在 UI 中可见:
- Footer pill:底部状态栏显示正在运行的 Dream 任务
- Shift+Down 对话框:详情面板展示进度
- Phase 检测:当第一次 Edit/Write 工具调用出现时,phase 从
starting翻转为updating - 完成通知:如果修改了文件,主对话中内联显示 “Improved N memories” 消息
四、记忆的读取侧
写入的记忆在后续会话中通过系统提示词加载。相关引导语(来自 memoryTypes.ts):
何时访问记忆
- 当记忆看起来相关时,或用户引用了之前的对话内容
- 当用户明确要求你检查、回忆或记住时,你必须访问记忆
- 如果用户说"忽略"或"不使用"记忆:把 MEMORY.md 当作空的。
不要应用记住的事实、引用、比较或提及记忆内容
从记忆推荐之前
一个命名了特定函数、文件或标志的记忆,声称它在写入时存在。
它可能已被重命名、删除或从未合并。在推荐之前:
- 如果记忆命名了文件路径:检查文件是否存在
- 如果记忆命名了函数或标志:grep 搜索它
- 如果用户即将根据你的推荐行动(而不只是询问历史):先验证
"记忆说 X 存在"不等于"X 现在存在"。
这条规则非常精妙——它承认记忆会过时,要求 agent 在推荐之前先验证,而不是盲目信任。
五、架构亮点总结
5.1 渐进式成本控制
整个系统的设计哲学是「由便宜到昂贵」:
操作 成本
─────────────────────────────────
读取配置 flag 几乎为零
stat lock 文件 一次 I/O
扫描 transcript 目录 多次 I/O
获取分布式锁 文件写入
启动 forked agent 一次 API 调用
运行 4 阶段巩固 多轮 API 调用
每一层都尽可能早地退出,避免不必要的资源消耗。
5.2 互斥设计
Extract Memories 和 AutoDream 之间存在隐式互斥:
- 如果主 agent 已经在当前轮写入了记忆,Extract Memories 跳过
- Extract Memories 和 AutoDream 共享同一个
canUseTool沙箱 - 它们都通过
skipTranscript: true避免与主线程竞争
5.3 记忆的类型系统
四种类型(user / feedback / project / reference)不是随意划分的,而是覆盖了「无法从代码推导的信息」的完整光谱:
user → 关于人(不可从任何来源推导)
feedback → 关于偏好(隐含在行为中,不可从代码推导)
project → 关于上下文(存在于人的意图中,不可从 git 推导)
reference → 关于外部系统(不在项目目录内)
每种类型的 body_structure 要求(规则 + Why + How to apply)确保了记忆不仅仅是事实记录,还包含了足够的上下文让未来的 agent 做出判断。
5.4 对开发者的启示
Claude Code 的记忆系统给 AI 应用开发带来了几个关键启示:
- 记忆不是存储,是压缩——不是存下所有对话,而是提取不可推导的关键信息
- 写入要克制,读取要验证——严格限制什么该保存,读取时不盲目信任
- 后台静默运行——用户无感,不阻塞主流程
- 渐进式门控——用最便宜的检查最先排除,避免不必要的资源消耗
- 分布式友好——多进程环境下通过文件锁协调,优雅处理崩溃和竞争
这套机制的核心思想可以用一句话概括:让 AI 在用户看不见的地方,安静地整理自己的笔记,以便下次见面时更懂你。
本文基于 Claude Code 源码(通过 sourceMap 还原)分析,涉及文件:
services/extractMemories/extractMemories.ts— 逐轮记忆提取services/extractMemories/prompts.ts— 提取提示词services/autoDream/autoDream.ts— 梦境巩固主逻辑services/autoDream/consolidationPrompt.ts— 巩固提示词services/autoDream/consolidationLock.ts— 分布式锁services/autoDream/config.ts— 配置tasks/DreamTask/DreamTask.ts— 后台任务 UImemdir/memoryTypes.ts— 记忆类型系统utils/backgroundHousekeeping.ts— 启动入口query/stopHooks.ts— 触发入口
更多推荐



所有评论(0)