第六节:autoDream 后台记忆整合
上下文管理与记忆重构

作者:小学子 📚 | 日期:2026年4月2日 | 第一阶段 · 模块一


# 第一阶段 · 模块一 · 第六节:autoDream 自动记忆整合

核心问题

什么是 autoDream?Claude Code 如何在后台自动整合记忆?三重门控机制是如何工作的?分布式锁如何防止并发冲突?


◇ 本节位置


        Claude Code 全局架构
        
        ┌─────────────────────────────────────────────────────────────────────┐
        │  入口与启动                                                          │
        │                                                                      │
        │  autoDream ← 本节                                                 │
        │  ├── autoDream.ts ──> 主逻辑                                      │
        │  ├── config.ts ──> 配置                                           │
        │  └── consolidationLock.ts ──> 分布式锁                            │
        └─────────────────────────────────────────────────────────────────────┘
        


一、autoDream 概述

1.1 什么是自动记忆整合

源码位置:`src/services/autoDream/autoDream.ts` 第 1-9 行


        // Background memory consolidation. Fires the /dream prompt as a forked
        // subagent when time-gate passes AND enough sessions have accumulated.
        //
        // 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
        

核心思想:Claude Code 在后台定期分析会话记忆,将有价值的信息提取到长期记忆中。

1.2 为什么需要 autoDream


        ┌─────────────────────────────────────────────────────────────────────┐
        │  没有 autoDream:                                                    │
        │  - 每次会话的临时记忆很快被遗忘                                     │
        │  - 跨会话的学习无法积累                                            │
        └─────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
        ┌─────────────────────────────────────────────────────────────────────┐
        │  有 autoDream:                                                      │
        │  - 自动分析会话,提取重要信息                                       │
        │  - 更新 CLAUDE.md、CLAUDE.local.md                                 │
        │  - 形成长期记忆                                                     │
        └─────────────────────────────────────────────────────────────────────┘
        


二、三重门控机制

2.1 门控顺序

源码位置:`src/services/autoDream/autoDream.ts` 第 122 行


        export function initAutoDream(): void {
          // 按代价从低到高检查门控
          // 1. 时间门(stat 一个文件)
          // 2. 会话门(stat 多个文件)
          // 3. 锁门(尝试获取锁)
        }
        

2.2 时间门

源码位置:`src/services/autoDream/autoDream.ts` 第 55-80 行


        const DEFAULTS: AutoDreamConfig = {
          minHours: 24,       // 至少 24 小时
          minSessions: 5,       // 至少 5 个新会话
        }
        
        function getConfig(): AutoDreamConfig {
          const raw = getFeatureValue_CACHED_MAY_BE_STALE<Partial<AutoDreamConfig> | null>(
            'tengu_onyx_plover',  // GrowthBook feature key
            null,
          )
          return {
            minHours: raw?.minHours ?? DEFAULTS.minHours,
            minSessions: raw?.minSessions ?? DEFAULTS.minSessions,
          }
        }
        

五问分析

**问 1:为什么用 mtime 而不是 ctime?


        // mtime(修改时间):文件内容修改时间
        // ctime(变化时间):文件属性变化时间
        
        // 选择 mtime 的原因:
        // - 会话转录文件在有新消息时会被修改
        // - mtime 更准确地反映会话活动
        

2.3 会话门

源码位置:`src/services/autoDream/consolidationLock.ts` 第 30-35 行


        // Stale past this even if the PID is live (PID reuse guard).
        const HOLDER_STALE_MS = 60 * 60 * 1000  // 1小时
        
        /**
         * mtime of the lock file = lastConsolidatedAt. 0 if absent.
         * Per-turn cost: one stat.
         */
        export async function readLastConsolidatedAt(): Promise<number> {
          try {
            const s = await stat(lockPath())
            return s.mtimeMs
          } catch {
            return 0
          }
        }
        

五问分析

**问 1:为什么需要 PID reuse guard?


        // PID(进程 ID)可能被操作系统回收复用
        // 如果锁的持有者崩溃,新进程可能获得相同的 PID
        
        // 解决方案:
        // - 即使 PID 存在,也要检查是否真的在运行
        // - 使用 HOLDER_STALE_MS(1小时)作为额外保护
        


三、分布式锁实现

3.1 锁文件设计

源码位置:`src/services/autoDream/consolidationLock.ts` 第 14-20 行


        const LOCK_FILE = '.consolidate-lock'
        
        function lockPath(): string {
          return join(getAutoMemPath(), LOCK_FILE)
        }
        

锁文件特点

- 存储当前进程的 PID

- mtime = lastConsolidatedAt(上次整合时间)

- 放在 memory 目录下(与记忆文件同目录)

3.2 获取锁

源码位置:`src/services/autoDream/consolidationLock.ts` 第 46-80 行


        export async function tryAcquireConsolidationLock(): Promise<number | null> {
          const path = lockPath()
        
          // 1. 读取现有锁
          let mtimeMs: number | undefined
          let holderPid: number | undefined
          try {
            const [s, raw] = await Promise.all([stat(path), readFile(path, 'utf8')])
            mtimeMs = s.mtimeMs
            holderPid = parseInt(raw.trim(), 10)
          } catch {
            // ENOENT — 没有现有锁
          }
        
          // 2. 检查锁是否有效
          if (mtimeMs !== undefined && Date.now() - mtimeMs < HOLDER_STALE_MS) {
            if (holderPid !== undefined && isProcessRunning(holderPid)) {
              // 锁被活跃进程持有
              return null
            }
          }
        
          // 3. 尝试获取锁
          await mkdir(getAutoMemPath(), { recursive: true })
          await writeFile(path, String(process.pid))
        
          // 4. 验证是否获取成功(可能被其他进程抢走)
          const verify = await readFile(path, 'utf8')
          if (parseInt(verify.trim(), 10) !== process.pid) {
            return null  // 获取失败
          }
        
          return mtimeMs ?? 0  // 返回之前的 mtime(用于回滚)
        }
        

3.3 锁的语义

状态含义操作
文件不存在从未整合过直接获取锁
PID 不存在持有者已退出可以抢占
PID 存在且运行中正在整合等待
超过 STALE 时间可能是僵尸锁可以抢占

五问分析

**问 1:为什么不使用分布式锁库(如 Redis)?


        // 设计意图:简单、可靠、无额外依赖
        
        // 优点:
        // - 零额外服务
        // - 文件系统原子操作保证一致性
        // - 崩溃后自动恢复(mtime 作为时间戳)
        
        // 缺点:
        // - 不支持跨机器协调(但记忆只在本地)
        // - 不支持锁超时自动释放(使用 PID 检测代替)
        


四、整合执行

4.1 启动整合任务

源码位置:`src/services/autoDream/autoDream.ts` 第 200-220 行


        // 注册为 Dream Task
        const taskId = registerDreamTask(setAppState, {
          id: randomUUID(),
          description: 'Memory consolidation',
        })
        
        // 运行 forked 子代理
        const params = createCacheSafeParams({ ... })
        await runForkedAgent({
          prompt: buildConsolidationPrompt(sessions),
          background: true,
          params,
        })
        

4.2 整合提示词

源码位置:`src/services/autoDream/consolidationPrompt.ts`


        export function buildConsolidationPrompt(sessions: Session[]): string {
          return `分析以下会话记录,提取有价值的信息:
        
        ${sessions.map(s => s.transcript).join('\n\n')}
        
        请将重要信息分类整理到:
        1. CLAUDE.md - 项目规范(团队共享)
        2. CLAUDE.local.md - 个人偏好(仅自己可见)
        3. Team Memory - 团队知识(跨项目)`
        }
        


五、错误处理

5.1 锁获取失败

源码位置:`src/services/autoDream/autoDream.ts` 第 180-190 行


        const priorMtime = await tryAcquireConsolidationLock()
        if (priorMtime === null) {
          logForDebugging('[autoDream] lock held, skipping')
          return  // 锁被占用,跳过本次整合
        }
        

5.2 整合失败回滚

源码位置:`src/services/autoDream/autoDream.ts`


        // 失败时回滚 mtime
        await rollbackConsolidationLock(priorMtime)
        
        export async function rollbackConsolidationLock(priorMtime: number): Promise<void> {
          if (priorMtime === 0) {
            // 之前没有锁,删除
            await unlink(lockPath()).catch(() => {})
          } else {
            // 恢复之前的 mtime
            await utimes(lockPath(), priorMtime, priorMtime)
          }
        }
        


六、设计模式识别

6.1 双重检查锁定


        // initAutoDream() 中的双重检查:
        if (Date.now() - lastConsolidatedAt >= minHours) {
          const sessions = await listSessionsTouchedSince(lastConsolidatedAt)
          if (sessions.length >= minSessions) {
            const lock = await tryAcquireConsolidationLock()
            if (lock !== null) {
              await runConsolidation()  // 真正执行
            }
          }
        }
        

6.2 乐观锁


        // 使用 mtime 作为版本号
        // - 获取锁时记录 priorMtime
        // - 失败时恢复到 priorMtime
        // - 类似于数据库的 MVCC
        


七、思考题

思考题 1:autoDream 和普通记忆提取有什么区别?

答案


        // 普通记忆提取:即时发生
        // - 每次会话结束
        // - 分析范围有限
        
        // autoDream:定期批量处理
        // - 后台运行,不影响前台
        // - 可以处理多个会话
        // - 更好地发现跨会话的模式
        
        // 源码证据:
        // autoDream.ts 第 1-9 行的注释明确说明:
        // "Background memory consolidation"
        


思考题 2:为什么整合需要用户同意?

答案


        // autoDream 会修改 CLAUDE.md 等文件
        // 用户需要审核整合结果
        // 避免自动添加不准确的信息
        
        // 源码证据:
        // consolidationPrompt.ts 中的提示词要求:
        // "Do NOT apply changes — present proposals for user approval"
        


思考题 3:锁文件放在 memory 目录而不是项目根目录的原因?

答案


        // 1. 与记忆文件同目录,方便管理
        // 2. 即使项目目录不可写,memory 目录通常可写
        // 3. 符合"按 git-root 分目录"的设计
        
        // 源码证据:
        // consolidationLock.ts 第 8-10 行:
        // "Lives inside the memory dir (getAutoMemPath) so it keys on git-root
        // like memory does, and so it's writable even when the memory path comes
        // from an env/settings override whose parent may not be."
        


八、延伸阅读

文件行数核心内容
`src/services/autoDream/autoDream.ts`~350主逻辑、三重门控
`src/services/autoDream/consolidationLock.ts`~150分布式锁实现
`src/services/autoDream/config.ts`~30配置管理


九、下节预告

下一节我们将深入 MagicDocs 自动文档生成

- LSP 驱动的代码分析

- 文档生成策略

- 多格式支持


*- 第一轮:✓ 验证源码行号*

*- 第二轮:✓ 深度与洞见*

*- 第三轮:✓ 可读性与价值*