實(shí)踐)
如果你用 Claude 做過長期項(xiàng)目一定被同一個(gè)問題折磨過它記不住昨天的事。今天聊的這個(gè)項(xiàng)目 claude-mem就是專門給 Claude 加一塊“外置記憶”的 CLI 工具。它把每一次會話中值得保留的信息抽出來存進(jìn)本地 SQLite下次對話啟動時(shí)把相關(guān)記憶自動注入到語境里讓模型真的能“想起來”你上個(gè)禮拜交代過的偏好和約束。我用了大概一周時(shí)間把 claude-mem 接進(jìn)了日常開發(fā)流也順手翻了它的源碼。這個(gè)項(xiàng)目解決的痛點(diǎn)非常明確Claude 本身是無狀態(tài)的每開一次新對話之前的上下文全部歸零。你當(dāng)然可以手動把舊內(nèi)容貼回去但對話一長就復(fù)制到崩潰。claude-mem 的思路是“記憶外置”——不依賴模型自身長上下文而是用工具幫你把記憶沉淀下來按需取用。這篇文章我會從設(shè)計(jì)思路、原理拆解、實(shí)操接入一直講到踩坑記錄適合正在用 Claude API 或 Claude Code 寫項(xiàng)目的人參考也適合所有對“LLM 記憶增強(qiáng)”感興趣的人。不管你懂不懂代碼看完都能明白它是怎么轉(zhuǎn)的。1. 項(xiàng)目核心思路為什么要給 Claude 做一塊外置記憶1.1 模型無狀態(tài)是痛點(diǎn)的根源大模型本身就像一個(gè)大腦清零的天才知識豐富推理快但聊完就忘。每次會話結(jié)束剛才的上下文就被丟掉了。這在閑聊場景沒事但在真實(shí)項(xiàng)目里非常致命。比如我在維護(hù)一個(gè)開源文檔項(xiàng)目里面有幾十個(gè)文件的命名約定、術(shù)語表、目錄結(jié)構(gòu)。每次我開新對話跟 Claude 說“接著改一下第三章節(jié)的措辭”它根本不知道第三章節(jié)在哪也不知道“術(shù)語表”里那幾個(gè)詞是什么。我試過幾種土辦法把整個(gè)項(xiàng)目背景復(fù)制粘貼進(jìn)每條新消息把常用約束寫進(jìn)系統(tǒng)提示詞文件甚至有人把歷史對話導(dǎo)出成 Markdown再手動喂給模型。這些方案都能跑但維護(hù)成本極高——背景一長token 就爆文件一多模型就分不清哪些是當(dāng)前任務(wù)、哪些是歷史包袱。這時(shí)候就需要一個(gè)能自動沉淀、自動檢索、自動注入記憶的工具。claude-mem 做的就是這個(gè)。1.2 記憶放本地不折騰云服務(wù)市面上很多“記憶增強(qiáng)”方案會把記憶存到云端用向量數(shù)據(jù)庫、Embedding API、云函數(shù)聽著高大上實(shí)際用起來配置一大堆。claude-mem 的思路是本地優(yōu)先一個(gè) SQLite 文件搞定存儲FTS5 全文索引搞定檢索。你不需要起任何服務(wù)不需要注冊任何云賬號裝好直接用。SQLite 這個(gè)選擇我覺得很聰明。Python 自帶的 sqlite3 就能操作Go、Rust 也都有官方庫部署時(shí)零依賴。FTS5 的全文檢索配合 BM25 排序在幾萬條記憶的規(guī)模下響應(yīng)速度毫秒級完全夠用。很多項(xiàng)目一上來就上向量檢索本質(zhì)上是對記憶檢索的復(fù)雜度誤判了個(gè)人項(xiàng)目的記憶量級根本到不了需要向量數(shù)據(jù)庫的地步關(guān)鍵詞匹配加上時(shí)間衰減排序效果已經(jīng)不錯(cuò)。等真到了幾十萬條再遷移也不是難事。1.3 整體工作流程先寫庫再讀庫claude-mem 的運(yùn)轉(zhuǎn)邏輯可以概括成兩條鏈路。寫入鏈路一次對話結(jié)束后你或一個(gè)鉤子腳本把對話內(nèi)容喂給 claude-mem它會對原始記錄做拆解提取問題、回答、結(jié)論、約束、待辦壓成一條結(jié)構(gòu)化的記憶記錄然后寫進(jìn) SQLite。讀取鏈路新對話開始前claude-mem 會拿當(dāng)前對話的開場信息去庫里檢索把命中條目按相關(guān)度和時(shí)間排序拼成一個(gè)“記憶上下文塊”注入到系統(tǒng)提示詞或首條用戶消息里。這兩條鏈路一個(gè)向東、一個(gè)向西互不干涉但組合起來就形成了一個(gè)完整閉環(huán)。你可能會問為什么不直接把整個(gè)歷史對話塞進(jìn)上下文答案是 token 預(yù)算不允許而且噪音太大。一個(gè)項(xiàng)目聊了 200 輪里面八成是寒暄和過程性內(nèi)容真正有價(jià)值的信息可能只占兩成。claude-mem 做的事情本質(zhì)上是“蒸餾”把兩成的價(jià)值提出來把八成的噪音扔掉。這樣既保留了記憶又不會擠占當(dāng)前任務(wù)的上下文空間。2. 核心機(jī)制拆解記憶是怎么被寫入、檢索和注入的2.1 寫入鏈路從流水賬到結(jié)構(gòu)化記憶claude-mem 的寫入不是簡單把聊天記錄整段存進(jìn)去而是做了結(jié)構(gòu)化處理。一條記憶記錄我打開數(shù)據(jù)庫看大概長這樣CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, project TEXT NOT NULL, -- 所屬項(xiàng)目 session_id TEXT, -- 來源會話 created_at TEXT NOT NULL, -- 時(shí)間戳 updated_at TEXT, -- 更新時(shí)間 question TEXT, -- 觸發(fā)場景 answer TEXT, -- 關(guān)鍵回答 summary TEXT NOT NULL, -- 蒸餾后的摘要 importance INTEGER DEFAULT 3, -- 重要度 1-5 tags TEXT DEFAULT [] -- 標(biāo)簽 JSON );寫入時(shí)它會做三件事。第一給原始對話分段拆成“問題-回答”對第二對每一對做摘要把長度壓到 200 字以內(nèi)第三按規(guī)則打上重要度和標(biāo)簽。你可以手動調(diào)用claude-mem add添加記憶也可以在腳本里讓它自動抽取。我建議在對話結(jié)束后自己快速過一遍抽取結(jié)果——AI 自動摘要偶爾會把關(guān)鍵約束漏掉手動糾偏值得的。2.2 檢索鏈路SQLite FTS5 幫了大忙讀取時(shí)最怕的就是找不到。claude-mem 的檢索默認(rèn)走 FTS5 全文索引。關(guān)鍵詞先被分詞器切碎然后走 BM25 算法打分返回一條條候選記錄。我翻源碼時(shí)看到查詢大概長這樣SELECT id, project, summary, importance, created_at, bm25(memories_fts) AS score FROM memories_fts JOIN memories ON memories_fts.rowid memories.id WHERE memories_fts MATCH ? ORDER BY score DESC LIMIT 20;這個(gè)MATCH ?支持布爾語法比如含義 AND 目錄或章節(jié) OR 目錄。BM25 打分是全文檢索的事實(shí)標(biāo)準(zhǔn)它會把“詞頻高但常見詞”降權(quán)把“稀疏但精準(zhǔn)的詞”提高權(quán)重。claude-mem 在基礎(chǔ)上還做了二次排序把importance和時(shí)間衰減算進(jìn)去新近且重要的記憶排前面這樣即便關(guān)鍵詞匹配一般也能靠時(shí)間權(quán)重抬上來。2.3 記憶注入的模板設(shè)計(jì)光檢索出來還不夠怎么塞回模型上下文也有講究。claude-mem 默認(rèn)的注入模板長這樣以下是過去關(guān)閉的項(xiàng)目記憶中可能與本次對話相關(guān)的部分。 請?jiān)诨卮饡r(shí)優(yōu)先參考以下事實(shí)如果它們與本次對話沖突請明確指出沖突點(diǎn)。 memories memory time2025-01-12 projectdocs-site importance5 用戶堅(jiān)持目錄文件命名統(tǒng)一使用 kebab-case不要駝峰。 /memory memory time2025-01-10 projectdocs-site importance4 術(shù)語表中“回調(diào)”統(tǒng)一翻譯為 callback不要寫“回到調(diào)用”。 /memory /memories用memories這種邊界標(biāo)簽包起來模型能明確區(qū)分“這是歷史記憶”和“這是當(dāng)前對話”。我實(shí)測下來加了這個(gè)邊界之后模型對歷史內(nèi)容的引用準(zhǔn)確率明顯提升不會把記憶當(dāng)成新的用戶指令執(zhí)行。這個(gè)設(shè)計(jì)代價(jià)很小收益卻很實(shí)在。2.4 上下文窗口的預(yù)算管理Claude 的上下文窗口雖然長但也不是無限的。claude-mem 在注入時(shí)會對結(jié)果做一步截?cái)嘞仍O(shè)置一個(gè)總預(yù)算比如 1024 token再按排序結(jié)果逐條塞塞到預(yù)算用完為止。為什么不是一次塞 20 條因?yàn)槊織l記憶平均占 150~200 token20 條就是 4000 token直接吃掉整個(gè)窗口的 2%~4%。對于大批量任務(wù)這無所謂但你要是有很長的工具定義和示例要放記憶部分就該壓縮。我自己的經(jīng)驗(yàn)預(yù)算設(shè)成 512~1024 token 比較平衡。太低的話模型容易忽略記憶太高的話主任務(wù)上下文被擠壓。檢索結(jié)果寧可少而精不要多而雜。claude-mem 的配置里可以直接改inject_token_budget按項(xiàng)目需求調(diào)整。3. 實(shí)操接入從零開始跑通 claude-mem3.1 安裝與初始化claude-mem 是命令行工具安裝方式看項(xiàng)目 README 為主。一般就兩種預(yù)編譯二進(jìn)制或者源碼編譯。我用的是從源碼編譯的方式git clone https://github.com/your-local-clone/claude-mem.git cd claude-mem make build sudo install -m 0755 build/claude-mem /usr/local/bin/裝好之后先初始化配置。初始化會生成一個(gè)默認(rèn)配置文件同時(shí)創(chuàng)建一個(gè)空的 SQLite 庫。第一次跑claude-mem init claude-mem status如果初始化成功status會顯示數(shù)據(jù)庫路徑、記憶條數(shù)、當(dāng)前項(xiàng)目名。我建議把數(shù)據(jù)庫單獨(dú)放一個(gè)目錄比如~/.local/state/claude-mem/memories.db這樣備份和遷移都方便不會跟項(xiàng)目代碼混在一起。3.2 配置文件修改初始化生成的配置文件是 TOML 格式我在~/.claude-mem/config.toml里改了幾個(gè)關(guān)鍵項(xiàng)[storage] db_path /home/you/.local/state/claude-mem/memories.db [inject] enabled true token_budget 768 max_results 10 min_importance 2 [retrieval] match_limit 100 min_score 12.0 [project] name default auto_tag truemin_importance 2意味著重要度低于 2 的記憶不參與注入過濾掉大部分過程性記錄min_score 12.0是檢索分?jǐn)?shù)閾值低于這個(gè)分?jǐn)?shù)說明匹配度太低不值得占用預(yù)算。這兩個(gè)閾值是暴力調(diào)參調(diào)出來的不同項(xiàng)目建議自己跑幾次claude-mem search 關(guān)鍵詞看看分?jǐn)?shù)分布再定。3.3 把 claude-mem 接進(jìn) Claude Code我日常主力是用 Claude Code 寫代碼所以最關(guān)心怎么把 claude-mem 嵌進(jìn)去。Claude Code 支持 hook 機(jī)制可以在會話啟動和會話結(jié)束時(shí)執(zhí)行外部命令。我在項(xiàng)目根目錄配了一個(gè).claude/settings.json{ hooks: { PreToolUse: [ { matcher: UserPrompt, command: claude-mem inject --format compact } ] } }這樣每輪用戶提問之前claude-mem inject會先把相關(guān)記憶查出來以緊湊格式追加到對話開頭。格式如果選compact注入內(nèi)容會短很多適合高頻輪次如果選full會把完整時(shí)間戳和標(biāo)簽也帶進(jìn)去信息量更足但 token 消耗也更高。3.4 用 Python 接入 Claude API如果你不是用 Claude Code而是直接調(diào) API那接 claude-mem 更簡單。核心邏輯就是調(diào) API 之前先claude-mem search把結(jié)果拼進(jìn) system prompt 或者首條 messages 里。我寫了一個(gè)最簡單的工作流示例import subprocess import anthropic def get_memories(query: str) - str: result subprocess.run( [claude-mem, search, query, --limit, 5], capture_outputTrue, textTrue, checkTrue ) return result.stdout system_prompt ( 你是一個(gè)可靠的技術(shù)文檔助手。\n 以下是歷史記憶請優(yōu)先參考\n get_memories(術(shù)語表 目錄命名) ) client anthropic.Anthropic() resp client.messages.create( modelclaude-sonnet-4-0, max_tokens2048, systemsystem_prompt, messages[{role: user, content: 幫我檢查第三章節(jié)的措辭}] ) print(resp.content[0].text)這里有幾個(gè)細(xì)節(jié)要注意。第一search的查詢詞別太長一兩句話足矣第二--limit 5是取前五條配合 token 預(yù)算剛好第三最好在每次對話開始前搜不要在每輪都搜——每輪都搜會增加不少 API 延遲。3.5 常用命令速查命令作用常用參數(shù)claude-mem add 約束 --tags project,style手動添加一條記憶--importance 1-5claude-mem search 關(guān)鍵詞檢索相關(guān)記憶--limit N、--project Pclaude-mem inject將相關(guān)記憶注入上下文--format full/compactclaude-mem list --project P查看某個(gè)項(xiàng)目的記憶列表--sort timeclaude-mem status查看庫狀態(tài)無claude-mem compact壓縮舊記憶合并重復(fù)項(xiàng)--dry-run先看效果compact這個(gè)命令我每兩周跑一次。它會把重要度低、時(shí)間久、內(nèi)容重復(fù)的記憶合并成幾個(gè)大的“檔案條目”庫就不會無限膨脹。4. 進(jìn)階調(diào)整記憶分級、團(tuán)隊(duì)共享與防“中毒”4.1 記憶分級短期、中期、長期分開管理默認(rèn)配置下所有記憶一視同仁但用久了你會發(fā)現(xiàn)這不夠。有些記憶是要長期遵守的規(guī)范比如“項(xiàng)目命名統(tǒng)一 kebab-case”有些記憶只是臨時(shí)的比如“今天下午三點(diǎn)要測一下接口”過倆小時(shí)就沒用了。claude-mem 支持用importance字段做分級。我按自己的習(xí)慣分了三檔重要度 5永久規(guī)范項(xiàng)目核心約束如命名規(guī)范、術(shù)語表。重要度 3中期事實(shí)如當(dāng)前模塊狀態(tài)、負(fù)責(zé)人分工。重要度 1臨時(shí)代辦過幾天就刪。配合清理策略重要度 1 的記憶 3 天自動清理重要度 3 的 30 天歸檔重要度 5 的永不自動刪。這個(gè)策略不是內(nèi)置功能是我寫了個(gè) cron 腳本調(diào)compact和delete --old實(shí)現(xiàn)的效果很好。庫不會泛濫注入的質(zhì)量也更高。4.2 多項(xiàng)目隔離一個(gè)庫還是多個(gè)庫我一開始把所有項(xiàng)目的記憶都塞進(jìn)同一個(gè)庫結(jié)果災(zāi)難了——搜“目錄”這個(gè)詞文檔項(xiàng)目和前端項(xiàng)目的結(jié)果混在一起注入一堆無關(guān)記憶。后來改成按項(xiàng)目名隔離每個(gè)項(xiàng)目建一個(gè)獨(dú)立 SQLite 文件或者在同一庫里用project字段過濾。更推薦后一種方案因?yàn)閟earch命令天然支持--project參數(shù)claude-mem search 目錄 --project docs-site同一個(gè)庫里跨項(xiàng)目搜索偶爾也有用比如查一個(gè)技術(shù)方案的往事能搜到其他項(xiàng)目里的類似經(jīng)驗(yàn)。共用庫讓數(shù)據(jù)可復(fù)用按項(xiàng)目濾噪又保證精準(zhǔn)兩全其美。4.3 防“記憶中毒”沖突與過時(shí)的處理記憶系統(tǒng)最大的坑是舊記憶跟新對話沖突。比如項(xiàng)目改版了命名規(guī)范從 kebab-case 改成了 snake_case但庫里還殘留舊記憶。如果 claude-mem 注入的是舊規(guī)則模型就會犯錯(cuò)。我的解決辦法分三層。第一層檢索時(shí)加時(shí)間衰減超過 90 天的記憶分?jǐn)?shù)打折降低被注入的概率。第二層注入模板里明確寫了“如果歷史記憶與本次對話沖突請明確指出沖突點(diǎn)”給模型一個(gè)“抗命授權(quán)”。第三層主動更新每次改規(guī)范我會手動刪掉舊記憶寫一條新記憶。刪舊用claude-mem delete --id寫新用claude-mem add --importance 5一分鐘搞定。4.4 團(tuán)隊(duì)共享記憶把知識沉淀下來單人用 claude-mem 解決的是“失憶”問題團(tuán)隊(duì)用解決的是“重復(fù)造輪子”問題。我把團(tuán)隊(duì)的 SQLite 庫放在一個(gè)共享目錄里大家讀寫同一個(gè)文件。SQLite 對這種并發(fā)場景天然支持 WAL 模式讀寫不會鎖庫。我實(shí)測過三個(gè)同事同時(shí)寫入沒有出現(xiàn)問題。如果是異地團(tuán)隊(duì)也可以定期導(dǎo)出、合并、再導(dǎo)入。claude-mem 支持 JSON 導(dǎo)出claude-mem export --format json --output backup.json claude-mem import --file backup.json團(tuán)隊(duì)共享的關(guān)鍵是給記憶打上“作者”標(biāo)簽比如--tags author:zhang這樣后續(xù)看到一條陌生記憶能知道是誰寫的也方便私下確認(rèn)。5. 常見問題與踩坑記錄我一個(gè)月里踩過的坑5.1 高頻問題速查表現(xiàn)象原因解決方案搜不到昨天的記錄分詞器對中文支持弱配置 FTS5 trigram 分詞或?qū)懭霑r(shí)額外生成英文別名標(biāo)簽注入后占用太多 tokentoken_budget 設(shè)太大調(diào)低inject_token_budget同時(shí)調(diào)高min_score多客戶端并發(fā)寫入報(bào)錯(cuò)默認(rèn) rollback journal 模式在 SQLite 打開時(shí)設(shè)置PRAGMA journal_modeWAL記憶注入太舊回答錯(cuò)誤時(shí)間衰減不足打開時(shí)間衰減縮短保留期啟動時(shí)注入慢數(shù)據(jù)庫體量過大且無索引給project、created_at建索引定期 compact5.2 中文搜索匹配的坑這個(gè)我折騰了兩天。claude-mem 默認(rèn)的 FTS5 分詞器是 unicode61它把中文按“整句單詞”處理所以搜“目錄”搜不到“目錄結(jié)構(gòu)”因?yàn)樗鼈冊谠~法上是完全不同的 token。解決方法是給記憶表單獨(dú)建一個(gè) trigram 分詞索引。SQLite 從 3.34 開始支持 trigram tokenizer把每一個(gè)連續(xù)三個(gè)字符當(dāng)一個(gè) token中文搜索效果立竿見影。CREATE VIRTUAL TABLE memories_trigram USING fts5(summary, content, tokenizetrigram);配上 trigram 之后英文的精確匹配依然走原表中文的模糊匹配走 trigram 表兩個(gè)結(jié)果做合并取并集后統(tǒng)一排序。數(shù)據(jù)量不大時(shí)這個(gè)“雙通道”方案非??孔V。5.3 記憶條目爆炸式增長人懶的時(shí)候會特別夸張我有一陣子每天加 20 條記憶兩周就攢了 300 多條。里面混合著核心規(guī)范、臨時(shí)任務(wù)、甚至還有心情記錄。庫一膨脹檢索速度不明顯變慢但注入質(zhì)量明顯下降——因?yàn)樵肼暥嗔苏嬲杏玫挠洃洷粩D掉。這個(gè)問題的根源不是 claude-mem 設(shè)計(jì)不好而是輸入質(zhì)量失控?,F(xiàn)在我給自己訂了個(gè)規(guī)矩手動加記憶必須寫“可執(zhí)行的約束”而不是“想法記錄”。比如“我覺得目錄結(jié)構(gòu)可以更扁平”不算一條記憶“目錄層級不超過三層新增文件優(yōu)先放根目錄”才算。質(zhì)量把關(guān)是用戶自己的事工具只能幫你存不能幫你判斷哪些值得存。5.4 API 報(bào) 400 錯(cuò)誤注入內(nèi)容格式問題有段時(shí)間我的 Python 腳本總是偶發(fā)報(bào) 400查日志發(fā)現(xiàn) system prompt 里混入了從記憶里帶過來的非法字符。原因是我導(dǎo)入過一條帶 HTML 標(biāo)簽的舊記憶注入模板包上memories標(biāo)簽之后就變成了多級標(biāo)簽嵌套導(dǎo)致消息格式解析出問題。解決辦法是在檢索結(jié)果輸出給模型之前做一個(gè)轉(zhuǎn)義把、轉(zhuǎn)成全角字符或者刪掉記憶內(nèi)容中的 XML 標(biāo)簽。claude-mem 后續(xù)版本在inject時(shí)可能已經(jīng)處理了但自定義接入時(shí)一定要自己加一道過濾。6. 我的最終體會記憶工具的邊界和取舍不多說最終聊點(diǎn)實(shí)在的用 claude-mem 跑了一個(gè)多月最大的感受是項(xiàng)目連續(xù)性確實(shí)改善了。以前跨會話協(xié)作基本靠我的“復(fù)制粘貼毅力”現(xiàn)在靠工具自動完成。這不代表它完美——檢索準(zhǔn)確性、注入質(zhì)量、記憶的時(shí)效性之間始終存在張力需要根據(jù)項(xiàng)目實(shí)際反復(fù)調(diào)整參數(shù)。我花了一天調(diào)min_score和token_budget從最初每次對話注入 10 條、每次消耗 2000 token收斂到注入 4~5 條、消耗 600 token模型對歷史規(guī)范的遵守率反而提升了。原因很簡單少而精的記憶更容易被模型注意到。最后再分享一個(gè)我養(yǎng)成的習(xí)慣每天結(jié)束工作前跑一次claude-mem list --sort time | tail -20掃一眼今天寫了哪些記憶發(fā)現(xiàn)寫歪了就順手改掉。這個(gè)動作花不了兩分鐘卻能在第二天開新會話之前把“記憶庫”清理干凈。如果你也想把 LLM 真正用進(jìn)長期項(xiàng)目從一件外置記憶工具開始是性價(jià)比最高的路徑。