:autoDream 自動(dòng)記憶整合)
1. 從一次長會(huì)話“失憶”說起autoDream 到底在解決什么問題如果你用 Claude Code 連續(xù)做過幾天的大項(xiàng)目大概率遇到過這種體驗(yàn)昨天在會(huì)話里反復(fù)強(qiáng)調(diào)的命名規(guī)范、目錄約定、某個(gè)接口的返回結(jié)構(gòu)今天新開一個(gè)會(huì)話它又像第一次見面一樣重新問你。這不是模型變笨了而是會(huì)話記憶和長期記憶之間缺了一層自動(dòng)搬運(yùn)機(jī)制。Claude Code 里負(fù)責(zé)這層搬運(yùn)的模塊就叫autoDream中文可以理解為“自動(dòng)記憶整合”。它做的事情很樸素在后臺(tái)定期掃描你最近的會(huì)話轉(zhuǎn)錄transcript把其中有長期價(jià)值的信息提取出來寫進(jìn)CLAUDE.md、CLAUDE.local.md這類記憶文件里。這樣下一次會(huì)話啟動(dòng)時(shí)這些文件會(huì)被當(dāng)作上下文加載模型就“記得”了。它適合誰三類人最該讀懂它一是天天用 Claude Code 寫業(yè)務(wù)代碼、會(huì)話越開越多的開發(fā)者二是想給團(tuán)隊(duì)做一套共享記憶規(guī)范的技術(shù)負(fù)責(zé)人三是像我這樣喜歡扒源碼、想知道“后臺(tái)到底偷偷干了什么”的人。因?yàn)?autoDream 不是每次會(huì)話都跑它有一套三重門控還配了一把基于文件系統(tǒng)的分布式鎖防止多個(gè) Claude Code 進(jìn)程同時(shí)整合、互相覆蓋。這一節(jié)我們就從源碼路徑出發(fā)把觸發(fā)條件、鎖的獲取與釋放、整合執(zhí)行、失敗回滾整條鏈路拆開。你會(huì)拿到可復(fù)制的閱讀路徑、關(guān)鍵函數(shù)調(diào)用鏈以及一套本地復(fù)現(xiàn) autoDream 行為的驗(yàn)證步驟。理解它之后你對“長會(huì)話場景下記憶怎么管理”會(huì)有一個(gè)可落地的模型而不是停留在“它好像會(huì)記東西”的模糊印象。先給一個(gè)全局定位方便你建立地圖感src/services/autoDream/ ├── autoDream.ts # 主邏輯三重門控 啟動(dòng)整合任務(wù) ├── config.ts # 配置minHours / minSessions ├── consolidationLock.ts # 分布式鎖獲取 / 校驗(yàn) / 回滾 └── consolidationPrompt.ts # 整合提示詞約束輸出到哪些記憶文件記住這個(gè)目錄結(jié)構(gòu)后面每一段代碼你都能對號(hào)入座。autoDream 的設(shè)計(jì)哲學(xué)是“代價(jià)從低到高逐層過濾”先用一次stat判斷時(shí)間再用多次stat數(shù)會(huì)話最后才嘗試搶鎖。這個(gè)順序不是隨便排的它直接決定了模塊在高頻調(diào)用下的開銷。2. 三重門控與分布式鎖autoDream 的觸發(fā)條件與并發(fā)控制要讀懂 autoDream核心就兩件事什么時(shí)候觸發(fā)以及多個(gè)進(jìn)程怎么不打架。前者是三重門控后者是consolidationLock.ts里的文件鎖。我們逐個(gè)拆。2.1 三重門控為什么順序是時(shí)間→會(huì)話→鎖源碼autoDream.ts開頭的注釋把設(shè)計(jì)意圖寫得很直白門控按代價(jià)從低到高排列。// 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第一道時(shí)間門只做一次stat讀鎖文件的mtime也就是上次整合時(shí)間lastConsolidatedAt。如果距離現(xiàn)在不足minHours直接返回連會(huì)話目錄都不掃。默認(rèn)值是 24 小時(shí)const DEFAULTS: AutoDreamConfig { minHours: 24, // 至少 24 小時(shí) minSessions: 5, // 至少 5 個(gè)新會(huì)話 }第二道會(huì)話門要掃多個(gè)轉(zhuǎn)錄文件的mtime代價(jià)高一些。它統(tǒng)計(jì)“修改時(shí)間晚于lastConsolidatedAt”的會(huì)話數(shù)量達(dá)到minSessions才繼續(xù)。這里有個(gè)細(xì)節(jié)值得注意為什么用mtime而不是ctime因?yàn)閙time是文件內(nèi)容修改時(shí)間會(huì)話有新消息時(shí)轉(zhuǎn)錄文件會(huì)被寫入mtime能準(zhǔn)確反映“這個(gè)會(huì)話最近活躍過”而ctime是 inode 屬性變化時(shí)間權(quán)限、重命名都會(huì)動(dòng)它噪聲太大。第三道鎖門最貴因?yàn)樗婕皩懳募托r?yàn)所以放最后。只有前兩道都過了才去tryAcquireConsolidationLock()。2.2 鎖文件設(shè)計(jì)一個(gè)文件承載三種語義consolidationLock.ts里的鎖非常輕量就是一個(gè)文件const LOCK_FILE .consolidate-lock function lockPath(): string { return join(getAutoMemPath(), LOCK_FILE) }這個(gè)文件同時(shí)承載三種信息文件內(nèi)容存當(dāng)前持有者的PID文件的mtime就是lastConsolidatedAt文件放在 memory 目錄下跟隨 git-root 分目錄。注釋里解釋了為什么放 memory 目錄而不是項(xiàng)目根目錄——即使項(xiàng)目目錄不可寫memory 目錄通常也可寫而且它和記憶文件同目錄管理起來一致。讀取上次整合時(shí)間就一行statexport async function readLastConsolidatedAt(): Promisenumber { try { const s await stat(lockPath()) return s.mtimeMs } catch { return 0 } }文件不存在時(shí)返回 0表示“從未整合過”這為后面的回滾邏輯埋了伏筆。2.3 獲取鎖讀→判活→寫→驗(yàn)證tryAcquireConsolidationLock()是整個(gè)模塊最精彩的一段它用“寫后驗(yàn)證”解決了文件系統(tǒng)沒有原子 CAS 的問題export async function tryAcquireConsolidationLock(): Promisenumber | null { const path lockPath() // 1. 讀取現(xiàn)有鎖 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 — 沒有現(xiàn)有鎖 } // 2. 檢查鎖是否有效 if (mtimeMs ! undefined Date.now() - mtimeMs HOLDER_STALE_MS) { if (holderPid ! undefined isProcessRunning(holderPid)) { return null // 鎖被活躍進(jìn)程持有 } } // 3. 嘗試獲取鎖 await mkdir(getAutoMemPath(), { recursive: true }) await writeFile(path, String(process.pid)) // 4. 驗(yàn)證是否獲取成功可能被其他進(jìn)程搶走 const verify await readFile(path, utf8) if (parseInt(verify.trim(), 10) ! process.pid) { return null } return mtimeMs ?? 0 }第 2 步有兩個(gè)判斷時(shí)間上是否新鮮HOLDER_STALE_MS默認(rèn) 1 小時(shí)以及持有者 PID 是否還活著。為什么要兩個(gè)都判因?yàn)镻ID 會(huì)被操作系統(tǒng)回收復(fù)用。如果持有者崩潰了新進(jìn)程可能拿到同一個(gè) PID光看 PID 會(huì)誤判成“鎖還活著”。所以即使 PID 存在超過 1 小時(shí)也視為僵尸鎖可以搶占。第 4 步的“寫后驗(yàn)證”是關(guān)鍵兩個(gè)進(jìn)程可能同時(shí)讀到舊鎖、同時(shí)寫入自己的 PID最后誰的名字留在文件里誰贏輸?shù)哪莻€(gè)讀到別人的 PID 就返回null。這是一種樂觀并發(fā)策略不需要任何鎖庫。2.4 鎖的語義與回滾把狀態(tài)整理成一張表你排查問題時(shí)對照著看狀態(tài)含義操作文件不存在從未整合過直接獲取鎖PID 不存在持有者已退出可以搶占PID 存在且運(yùn)行中正在整合等待超過 STALE 時(shí)間可能是僵尸鎖可以搶占獲取鎖時(shí)返回的priorMtime是“上一次整合時(shí)間”它的用途是失敗回滾。如果整合中途出錯(cuò)要把鎖文件的mtime恢復(fù)成原值否則下次時(shí)間門會(huì)誤以為剛整合過export async function rollbackConsolidationLock(priorMtime: number): Promisevoid { if (priorMtime 0) { await unlink(lockPath()).catch(() {}) } else { await utimes(lockPath(), priorMtime, priorMtime) } }priorMtime 0說明之前根本沒有鎖文件那就刪掉否則用utimes把時(shí)間戳改回去。這套“記錄舊值→失敗恢復(fù)”的模式和數(shù)據(jù)庫 MVCC 里的版本號(hào)思路是一致的。3. 可復(fù)制配置本地復(fù)現(xiàn) autoDream 的完整 settings 片段光讀源碼不夠我們得能跑起來。這一節(jié)給你一份可復(fù)制的配置把 autoDream 相關(guān)的參數(shù)、記憶目錄、以及接入 Claude Code 所需的 Base URL / Key / Model ID 三件套都擺清楚。3.1 autoDream 參數(shù)配置autoDream 的配置通過 feature flag 讀取帶默認(rèn)值兜底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, } }如果你想在本地快速驗(yàn)證不想等 24 小時(shí)最直接的辦法是把minHours調(diào)小。在項(xiàng)目根目錄的.claude/settings.json里寫入{ env: { CLAUDE_CODE_AUTO_DREAM_MIN_HOURS: 0, CLAUDE_CODE_AUTO_DREAM_MIN_SESSIONS: 1 } }注意不同版本的 Claude Code 讀取環(huán)境變量的鍵名可能不同如果上面的鍵不生效優(yōu)先以你本地config.ts里實(shí)際讀取的鍵為準(zhǔn)。改配置前先備份原文件。3.2 記憶目錄與鎖文件位置autoDream 的所有產(chǎn)物都在 memory 目錄下路徑由getAutoMemPath()決定通常跟隨 git-root。你可以手動(dòng)確認(rèn)# 進(jìn)入你的項(xiàng)目根目錄 cd /path/to/your/project # 查看 memory 目錄不同版本路徑可能略有差異 ls -la .claude/ 2/dev/null || ls -la ~/.claude/ # 查看鎖文件 find . -name .consolidate-lock 2/dev/null鎖文件.consolidate-lock的內(nèi)容就是 PIDmtime就是上次整合時(shí)間。你可以用一條命令同時(shí)看到兩者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 } }三個(gè)字段缺一不可ANTHROPIC_BASE_URL指向接口地址ANTHROPIC_API_KEY是身份憑證ANTHROPIC_MODEL指定模型 ID。少任何一個(gè)請求都會(huì)在鑒權(quán)或路由階段失敗。密鑰可以在控制臺(tái)創(chuàng)建具體入口見文末 CTA。3.4 整合提示詞的約束consolidationPrompt.ts決定了整合結(jié)果寫到哪、怎么寫export function buildConsolidationPrompt(sessions: Session[]): string { return 分析以下會(huì)話記錄提取有價(jià)值的信息 ${sessions.map(s s.transcript).join(\n\n)} 請將重要信息分類整理到 1. CLAUDE.md - 項(xiàng)目規(guī)范團(tuán)隊(duì)共享 2. CLAUDE.local.md - 個(gè)人偏好僅自己可見 3. Team Memory - 團(tuán)隊(duì)知識(shí)跨項(xiàng)目 }提示詞里還有一條重要約束不要直接應(yīng)用改動(dòng)而是提出建議供用戶審核。這解釋了為什么 autoDream 不會(huì)悄悄改你的CLAUDE.md——它只生成提案最終落地需要你確認(rèn)。這個(gè)設(shè)計(jì)避免了自動(dòng)寫入不準(zhǔn)確信息污染長期記憶。4. 驗(yàn)證請求本地復(fù)現(xiàn) autoDream 并觀察成功結(jié)果配置好了接下來驗(yàn)證它真的會(huì)觸發(fā)。我們分三步造會(huì)話、看門控、觀察整合。4.1 制造足夠的會(huì)話轉(zhuǎn)錄autoDream 的會(huì)話門要求“修改時(shí)間晚于lastConsolidatedAt的會(huì)話數(shù) ≥ minSessions”。所以先制造幾個(gè)新會(huì)話文件。最省事的辦法是連續(xù)開幾個(gè) Claude Code 會(huì)話每個(gè)里隨便問一句然后退出。轉(zhuǎn)錄文件通常落在會(huì)話目錄下# 找到會(huì)話轉(zhuǎn)錄目錄路徑隨版本變化先定位 find ~ -type d -name *transcript* 2/dev/null | head find ~ -type d -name *session* 2/dev/null | head找到后確認(rèn)文件數(shù)量和修改時(shí)間ls -lt 會(huì)話目錄 | head -204.2 手動(dòng)觸發(fā)并觀察日志把minHours設(shè)為 0、minSessions設(shè)為 1 后重啟 Claude Code。觸發(fā)時(shí)你會(huì)看到調(diào)試日志[autoDream] lock held, skipping或者整合啟動(dòng)的日志。如果看到lock held, skipping說明鎖被別人占著屬于正常并發(fā)保護(hù)不是 bug。4.3 用腳本模擬鎖競爭想親眼看到“寫后驗(yàn)證”生效可以寫個(gè)小腳本模擬兩個(gè)進(jìn)程搶鎖#!/bin/bash LOCK.claude/.consolidate-lock mkdir -p .claude # 進(jìn)程 A 寫入自己的 PID echo $$ $LOCK sleep 0.1 # 讀回驗(yàn)證 CURRENT$(cat $LOCK) if [ $CURRENT $$ ]; then echo 進(jìn)程 $$ 成功持有鎖 else echo 進(jìn)程 $$ 搶鎖失敗當(dāng)前持有者 $CURRENT fi同時(shí)開兩個(gè)終端跑你會(huì)看到只有一個(gè)打印“成功持有鎖”另一個(gè)打印“搶鎖失敗”。這就是tryAcquireConsolidationLock()第 4 步驗(yàn)證邏輯的簡化版。4.4 確認(rèn)整合產(chǎn)物整合成功后檢查記憶文件是否被更新ls -lt CLAUDE.md CLAUDE.local.md 2/dev/null git diff CLAUDE.md 2/dev/null如果提示詞約束生效你應(yīng)該看到的是提案式改動(dòng)而不是直接覆蓋。確認(rèn)無誤后再手動(dòng)接受。5. 本篇常見錯(cuò)排查401、local proxy failed 與鎖相關(guān)報(bào)錯(cuò)跑不通的時(shí)候報(bào)錯(cuò)信息往往指向不同層。這一節(jié)按真實(shí)報(bào)錯(cuò)逐條對照。5.1 401 Unauthorized最常見。原因通常是 Key 沒配、配錯(cuò)或者 Base URL 和 Key 不匹配。檢查順序echo $ANTHROPIC_API_KEY echo $ANTHROPIC_BASE_URL如果 Key 為空說明環(huán)境變量沒加載。確認(rèn)settings.json的env段被正確讀取或者直接在 shell 里 export 一次測試。注意 Key 和 Base URL 必須成對用 A 平臺(tái)的 Key 打 B 平臺(tái)的地址必然 401。5.2 local proxy failed這個(gè)報(bào)錯(cuò)通常出現(xiàn)在網(wǎng)絡(luò)層表示本地代理或轉(zhuǎn)發(fā)環(huán)節(jié)沒起來。先確認(rèn)你的ANTHROPIC_BASE_URL寫的是完整可訪問地址沒有多余斜杠或路徑。然后單獨(dú)測連通性curl -sS -o /dev/null -w %{http_code}\n https://taotoken.net/api返回 200 或 401 都說明網(wǎng)絡(luò)通返回超時(shí)或連接拒絕就是鏈路問題。注意不要配置任何非官方的網(wǎng)絡(luò)轉(zhuǎn)發(fā)工具直接用標(biāo)準(zhǔn) HTTPS 訪問即可。5.3 reading choices 相關(guān)報(bào)錯(cuò)這類報(bào)錯(cuò)一般出現(xiàn)在響應(yīng)解析階段說明返回體結(jié)構(gòu)和客戶端預(yù)期不一致。常見原因是 Model ID 寫錯(cuò)或者 Base URL 指向了不兼容的端點(diǎn)。核對三件套{ ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-..., ANTHROPIC_MODEL: claude-sonnet-4-20250514 }Model ID 必須和平臺(tái)支持的列表一致拼錯(cuò)一個(gè)字符就會(huì)走到錯(cuò)誤分支。5.4 OAuth 相關(guān)報(bào)錯(cuò)如果你用的是 OAuth 登錄方式而非 API Key報(bào)錯(cuò)會(huì)提示 token 過期或 scope 不足。處理方式是重新登錄刷新憑證。注意 OAuth 和 API Key 兩種模式不要混用混用會(huì)導(dǎo)致鑒權(quán)頭沖突。5.5 鎖相關(guān)整合一直不觸發(fā)如果日志里反復(fù)出現(xiàn)lock held, skipping說明鎖文件一直被認(rèn)為有效。檢查cat .claude/.consolidate-lock ps -p $(cat .claude/.consolidate-lock) 2/dev/null如果 PID 對應(yīng)的進(jìn)程早就不存在但文件還在且mtime在 1 小時(shí)內(nèi)就會(huì)被判為“可能還活著”。等超過HOLDER_STALE_MS1 小時(shí)后會(huì)自動(dòng)可搶占。想立即恢復(fù)可以手動(dòng)刪鎖文件rm -f .claude/.consolidate-lock注意刪鎖前確認(rèn)沒有正在運(yùn)行的整合任務(wù)否則可能造成并發(fā)寫入。5.6 整合失敗后時(shí)間戳沒回滾如果整合報(bào)錯(cuò)但mtime被更新了下次時(shí)間門會(huì)誤判。檢查rollbackConsolidationLock()是否被調(diào)用。正常情況下失敗路徑會(huì)執(zhí)行回滾把mtime恢復(fù)成priorMtime。如果沒恢復(fù)手動(dòng)改回去touch -d 2025-01-01 00:00:00 .claude/.consolidate-lock6. 把 autoDream 用起來從源碼理解到長期編碼實(shí)踐讀到這里你應(yīng)該已經(jīng)能把 autoDream 的完整鏈路串起來了時(shí)間門一次stat過濾掉絕大多數(shù)調(diào)用會(huì)話門數(shù)文件確認(rèn)有足夠新內(nèi)容鎖門用“讀→判活→寫→驗(yàn)證”保證多進(jìn)程安全整合失敗還有mtime回滾兜底。這套設(shè)計(jì)沒有引入任何外部依賴全靠文件系統(tǒng)語義非常適合本地優(yōu)先的工具。如果你打算長期用 Claude Code 做項(xiàng)目我的建議是把 autoDream 和 Coding Plan 結(jié)合起來用。前者負(fù)責(zé)把跨會(huì)話的經(jīng)驗(yàn)沉淀到CLAUDE.md后者負(fù)責(zé)在長任務(wù)、Agent 場景下保持穩(wěn)定的編碼節(jié)奏。兩者配合你就不用每次開新會(huì)話都重新交代一遍項(xiàng)目背景。具體操作上先把接入三件套配好密鑰在 API Keys 頁面創(chuàng)建配置方法參考接入文檔。想先驗(yàn)證模型對話是否正??梢匀ツP蛯υ掜撁姘l(fā)一條測試請求。確認(rèn)鏈路通了再回到 autoDream 的驗(yàn)證步驟把minHours調(diào)小觀察一次完整整合。等看到CLAUDE.md里出現(xiàn)你昨天強(qiáng)調(diào)過的規(guī)范這套記憶管理策略就算真正跑通了。最后留一個(gè)實(shí)用技巧把.consolidate-lock加進(jìn).gitignore。它是本地運(yùn)行時(shí)產(chǎn)物提交上去只會(huì)給團(tuán)隊(duì)其他人添亂。記憶文件CLAUDE.md則相反應(yīng)該提交讓團(tuán)隊(duì)共享同一份項(xiàng)目規(guī)范。