资讯详情

Claude Code 源码剖析 模块一 · 第六节:autoDream 自动记忆整合

📅 2026/10/3 16:19:31 | 华诺云谱 👁 阅读
Claude Code 源码剖析 模块一 · 第六节:autoDream 自动记忆整合
1. 从一次长会话“失忆”说起autoDream 到底在解决什么问题如果你用 Claude Code 连续做过几天的大项目大概率遇到过这种体验昨天在会话里反复强调的命名规范、目录约定、某个接口的返回结构今天新开一个会话它又像第一次见面一样重新问你。这不是模型变笨了而是会话记忆和长期记忆之间缺了一层自动搬运机制。Claude Code 里负责这层搬运的模块就叫autoDream中文可以理解为“自动记忆整合”。它做的事情很朴素在后台定期扫描你最近的会话转录transcript把其中有长期价值的信息提取出来写进CLAUDE.md、CLAUDE.local.md这类记忆文件里。这样下一次会话启动时这些文件会被当作上下文加载模型就“记得”了。它适合谁三类人最该读懂它一是天天用 Claude Code 写业务代码、会话越开越多的开发者二是想给团队做一套共享记忆规范的技术负责人三是像我这样喜欢扒源码、想知道“后台到底偷偷干了什么”的人。因为 autoDream 不是每次会话都跑它有一套三重门控还配了一把基于文件系统的分布式锁防止多个 Claude Code 进程同时整合、互相覆盖。这一节我们就从源码路径出发把触发条件、锁的获取与释放、整合执行、失败回滚整条链路拆开。你会拿到可复制的阅读路径、关键函数调用链以及一套本地复现 autoDream 行为的验证步骤。理解它之后你对“长会话场景下记忆怎么管理”会有一个可落地的模型而不是停留在“它好像会记东西”的模糊印象。先给一个全局定位方便你建立地图感src/services/autoDream/ ├── autoDream.ts # 主逻辑三重门控 启动整合任务 ├── config.ts # 配置minHours / minSessions ├── consolidationLock.ts # 分布式锁获取 / 校验 / 回滚 └── consolidationPrompt.ts # 整合提示词约束输出到哪些记忆文件记住这个目录结构后面每一段代码你都能对号入座。autoDream 的设计哲学是“代价从低到高逐层过滤”先用一次stat判断时间再用多次stat数会话最后才尝试抢锁。这个顺序不是随便排的它直接决定了模块在高频调用下的开销。2. 三重门控与分布式锁autoDream 的触发条件与并发控制要读懂 autoDream核心就两件事什么时候触发以及多个进程怎么不打架。前者是三重门控后者是consolidationLock.ts里的文件锁。我们逐个拆。2.1 三重门控为什么顺序是时间→会话→锁源码autoDream.ts开头的注释把设计意图写得很直白门控按代价从低到高排列。// 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第一道时间门只做一次stat读锁文件的mtime也就是上次整合时间lastConsolidatedAt。如果距离现在不足minHours直接返回连会话目录都不扫。默认值是 24 小时const DEFAULTS: AutoDreamConfig { minHours: 24, // 至少 24 小时 minSessions: 5, // 至少 5 个新会话 }第二道会话门要扫多个转录文件的mtime代价高一些。它统计“修改时间晚于lastConsolidatedAt”的会话数量达到minSessions才继续。这里有个细节值得注意为什么用mtime而不是ctime因为mtime是文件内容修改时间会话有新消息时转录文件会被写入mtime能准确反映“这个会话最近活跃过”而ctime是 inode 属性变化时间权限、重命名都会动它噪声太大。第三道锁门最贵因为它涉及写文件和校验所以放最后。只有前两道都过了才去tryAcquireConsolidationLock()。2.2 锁文件设计一个文件承载三种语义consolidationLock.ts里的锁非常轻量就是一个文件const LOCK_FILE .consolidate-lock function lockPath(): string { return join(getAutoMemPath(), LOCK_FILE) }这个文件同时承载三种信息文件内容存当前持有者的PID文件的mtime就是lastConsolidatedAt文件放在 memory 目录下跟随 git-root 分目录。注释里解释了为什么放 memory 目录而不是项目根目录——即使项目目录不可写memory 目录通常也可写而且它和记忆文件同目录管理起来一致。读取上次整合时间就一行statexport async function readLastConsolidatedAt(): Promisenumber { try { const s await stat(lockPath()) return s.mtimeMs } catch { return 0 } }文件不存在时返回 0表示“从未整合过”这为后面的回滚逻辑埋了伏笔。2.3 获取锁读→判活→写→验证tryAcquireConsolidationLock()是整个模块最精彩的一段它用“写后验证”解决了文件系统没有原子 CAS 的问题export async function tryAcquireConsolidationLock(): Promisenumber | 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 }第 2 步有两个判断时间上是否新鲜HOLDER_STALE_MS默认 1 小时以及持有者 PID 是否还活着。为什么要两个都判因为PID 会被操作系统回收复用。如果持有者崩溃了新进程可能拿到同一个 PID光看 PID 会误判成“锁还活着”。所以即使 PID 存在超过 1 小时也视为僵尸锁可以抢占。第 4 步的“写后验证”是关键两个进程可能同时读到旧锁、同时写入自己的 PID最后谁的名字留在文件里谁赢输的那个读到别人的 PID 就返回null。这是一种乐观并发策略不需要任何锁库。2.4 锁的语义与回滚把状态整理成一张表你排查问题时对照着看状态含义操作文件不存在从未整合过直接获取锁PID 不存在持有者已退出可以抢占PID 存在且运行中正在整合等待超过 STALE 时间可能是僵尸锁可以抢占获取锁时返回的priorMtime是“上一次整合时间”它的用途是失败回滚。如果整合中途出错要把锁文件的mtime恢复成原值否则下次时间门会误以为刚整合过export async function rollbackConsolidationLock(priorMtime: number): Promisevoid { if (priorMtime 0) { await unlink(lockPath()).catch(() {}) } else { await utimes(lockPath(), priorMtime, priorMtime) } }priorMtime 0说明之前根本没有锁文件那就删掉否则用utimes把时间戳改回去。这套“记录旧值→失败恢复”的模式和数据库 MVCC 里的版本号思路是一致的。3. 可复制配置本地复现 autoDream 的完整 settings 片段光读源码不够我们得能跑起来。这一节给你一份可复制的配置把 autoDream 相关的参数、记忆目录、以及接入 Claude Code 所需的 Base URL / Key / Model ID 三件套都摆清楚。3.1 autoDream 参数配置autoDream 的配置通过 feature flag 读取带默认值兜底function getConfig(): AutoDreamConfig { const raw getFeatureValue_CACHED_MAY_BE_STALEPartialAutoDreamConfig | null( tengu_onyx_plover, null, ) return { minHours: raw?.minHours ?? DEFAULTS.minHours, minSessions: raw?.minSessions ?? DEFAULTS.minSessions, } }如果你想在本地快速验证不想等 24 小时最直接的办法是把minHours调小。在项目根目录的.claude/settings.json里写入{ env: { CLAUDE_CODE_AUTO_DREAM_MIN_HOURS: 0, CLAUDE_CODE_AUTO_DREAM_MIN_SESSIONS: 1 } }注意不同版本的 Claude Code 读取环境变量的键名可能不同如果上面的键不生效优先以你本地config.ts里实际读取的键为准。改配置前先备份原文件。3.2 记忆目录与锁文件位置autoDream 的所有产物都在 memory 目录下路径由getAutoMemPath()决定通常跟随 git-root。你可以手动确认# 进入你的项目根目录 cd /path/to/your/project # 查看 memory 目录不同版本路径可能略有差异 ls -la .claude/ 2/dev/null || ls -la ~/.claude/ # 查看锁文件 find . -name .consolidate-lock 2/dev/null锁文件.consolidate-lock的内容就是 PIDmtime就是上次整合时间。你可以用一条命令同时看到两者stat -c pid%n mtime%y .claude/.consolidate-lock 2/dev/null cat .claude/.consolidate-lock 2/dev/null3.3 接入配置三件套如果你是通过 API 方式接入 Claude Code需要把 Base URL、Key、Model ID 配全。以settings.json为例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的密钥, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }三个字段缺一不可ANTHROPIC_BASE_URL指向接口地址ANTHROPIC_API_KEY是身份凭证ANTHROPIC_MODEL指定模型 ID。少任何一个请求都会在鉴权或路由阶段失败。密钥可以在控制台创建具体入口见文末 CTA。3.4 整合提示词的约束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 - 团队知识跨项目 }提示词里还有一条重要约束不要直接应用改动而是提出建议供用户审核。这解释了为什么 autoDream 不会悄悄改你的CLAUDE.md——它只生成提案最终落地需要你确认。这个设计避免了自动写入不准确信息污染长期记忆。4. 验证请求本地复现 autoDream 并观察成功结果配置好了接下来验证它真的会触发。我们分三步造会话、看门控、观察整合。4.1 制造足够的会话转录autoDream 的会话门要求“修改时间晚于lastConsolidatedAt的会话数 ≥ minSessions”。所以先制造几个新会话文件。最省事的办法是连续开几个 Claude Code 会话每个里随便问一句然后退出。转录文件通常落在会话目录下# 找到会话转录目录路径随版本变化先定位 find ~ -type d -name *transcript* 2/dev/null | head find ~ -type d -name *session* 2/dev/null | head找到后确认文件数量和修改时间ls -lt 会话目录 | head -204.2 手动触发并观察日志把minHours设为 0、minSessions设为 1 后重启 Claude Code。触发时你会看到调试日志[autoDream] lock held, skipping或者整合启动的日志。如果看到lock held, skipping说明锁被别人占着属于正常并发保护不是 bug。4.3 用脚本模拟锁竞争想亲眼看到“写后验证”生效可以写个小脚本模拟两个进程抢锁#!/bin/bash LOCK.claude/.consolidate-lock mkdir -p .claude # 进程 A 写入自己的 PID echo $$ $LOCK sleep 0.1 # 读回验证 CURRENT$(cat $LOCK) if [ $CURRENT $$ ]; then echo 进程 $$ 成功持有锁 else echo 进程 $$ 抢锁失败当前持有者 $CURRENT fi同时开两个终端跑你会看到只有一个打印“成功持有锁”另一个打印“抢锁失败”。这就是tryAcquireConsolidationLock()第 4 步验证逻辑的简化版。4.4 确认整合产物整合成功后检查记忆文件是否被更新ls -lt CLAUDE.md CLAUDE.local.md 2/dev/null git diff CLAUDE.md 2/dev/null如果提示词约束生效你应该看到的是提案式改动而不是直接覆盖。确认无误后再手动接受。5. 本篇常见错排查401、local proxy failed 与锁相关报错跑不通的时候报错信息往往指向不同层。这一节按真实报错逐条对照。5.1 401 Unauthorized最常见。原因通常是 Key 没配、配错或者 Base URL 和 Key 不匹配。检查顺序echo $ANTHROPIC_API_KEY echo $ANTHROPIC_BASE_URL如果 Key 为空说明环境变量没加载。确认settings.json的env段被正确读取或者直接在 shell 里 export 一次测试。注意 Key 和 Base URL 必须成对用 A 平台的 Key 打 B 平台的地址必然 401。5.2 local proxy failed这个报错通常出现在网络层表示本地代理或转发环节没起来。先确认你的ANTHROPIC_BASE_URL写的是完整可访问地址没有多余斜杠或路径。然后单独测连通性curl -sS -o /dev/null -w %{http_code}\n https://taotoken.net/api返回 200 或 401 都说明网络通返回超时或连接拒绝就是链路问题。注意不要配置任何非官方的网络转发工具直接用标准 HTTPS 访问即可。5.3 reading choices 相关报错这类报错一般出现在响应解析阶段说明返回体结构和客户端预期不一致。常见原因是 Model ID 写错或者 Base URL 指向了不兼容的端点。核对三件套{ ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-..., ANTHROPIC_MODEL: claude-sonnet-4-20250514 }Model ID 必须和平台支持的列表一致拼错一个字符就会走到错误分支。5.4 OAuth 相关报错如果你用的是 OAuth 登录方式而非 API Key报错会提示 token 过期或 scope 不足。处理方式是重新登录刷新凭证。注意 OAuth 和 API Key 两种模式不要混用混用会导致鉴权头冲突。5.5 锁相关整合一直不触发如果日志里反复出现lock held, skipping说明锁文件一直被认为有效。检查cat .claude/.consolidate-lock ps -p $(cat .claude/.consolidate-lock) 2/dev/null如果 PID 对应的进程早就不存在但文件还在且mtime在 1 小时内就会被判为“可能还活着”。等超过HOLDER_STALE_MS1 小时后会自动可抢占。想立即恢复可以手动删锁文件rm -f .claude/.consolidate-lock注意删锁前确认没有正在运行的整合任务否则可能造成并发写入。5.6 整合失败后时间戳没回滚如果整合报错但mtime被更新了下次时间门会误判。检查rollbackConsolidationLock()是否被调用。正常情况下失败路径会执行回滚把mtime恢复成priorMtime。如果没恢复手动改回去touch -d 2025-01-01 00:00:00 .claude/.consolidate-lock6. 把 autoDream 用起来从源码理解到长期编码实践读到这里你应该已经能把 autoDream 的完整链路串起来了时间门一次stat过滤掉绝大多数调用会话门数文件确认有足够新内容锁门用“读→判活→写→验证”保证多进程安全整合失败还有mtime回滚兜底。这套设计没有引入任何外部依赖全靠文件系统语义非常适合本地优先的工具。如果你打算长期用 Claude Code 做项目我的建议是把 autoDream 和 Coding Plan 结合起来用。前者负责把跨会话的经验沉淀到CLAUDE.md后者负责在长任务、Agent 场景下保持稳定的编码节奏。两者配合你就不用每次开新会话都重新交代一遍项目背景。具体操作上先把接入三件套配好密钥在 API Keys 页面创建配置方法参考接入文档。想先验证模型对话是否正常可以去模型对话页面发一条测试请求。确认链路通了再回到 autoDream 的验证步骤把minHours调小观察一次完整整合。等看到CLAUDE.md里出现你昨天强调过的规范这套记忆管理策略就算真正跑通了。最后留一个实用技巧把.consolidate-lock加进.gitignore。它是本地运行时产物提交上去只会给团队其他人添乱。记忆文件CLAUDE.md则相反应该提交让团队共享同一份项目规范。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑