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