我們在 skillmem 的 README 和每篇相關貼文中都寫道「記憶會在不同會話之間延續」,但從未實際測量過。本週,我在自己的工作資料庫上進行了測試,結果出爐,其中也包含了一些不那麼光彩的部分。skillmem 是一個為程式碼代理設計的本地記憶系統,它結合了 SQLite 資料庫、九個 mem_* MCP 工具和 Claude Code 鉤子,讓 Claude Code、Codex、Cursor 等客戶端可以共用一個資料庫。
測試一:新視窗是否能記住舊視窗的內容?在視窗一中,代理收到一個決定:「Arka 版本將延期至 11 月 14 日發布,代號 Falcon,負責人 Timur。」接著視窗一關閉。在視窗二這個新的會話中,代理被問到:「代號是什麼?負責人是誰?」
我們設計了五種情境,每種執行兩次。代理是使用乾淨設定的 claude -p,沒有 CLAUDE.md 檔案,也沒有內建的自動記憶功能。其中一組代理整合了 skillmem,另一組則沒有。
結果顯示,在沒有記憶功能的情況下,代理每次都回答「此會話中沒有資料」,得分為 0/10。而整合 skillmem 的代理則能正確回答,得分為 10/10。有一次,它甚至在沒有提示的情況下,額外指出 11 月 14 日是星期六。其運作機制是:當會話結束時,session-recap 鉤子會讓一個模型將對話紀錄提煉成簡短筆記。
在下一個會話中,SessionStart 會注入最近的筆記,而 UserPromptSubmit 鉤子則會為每個提示詞搜尋資料庫,代理無需自行呼叫 mem_search。
測試二:關於真實專案的問題。我的資料庫儲存了六個月以來約 9,300 筆條目。我撰寫了 15 個問題,其答案明確存在於記憶中,每個問題執行兩次。關鍵限制是:代理不得讀取檔案,否則它會搜尋磁碟,測試結果將衡量搜尋而非記憶。首次執行結果:無記憶的代理得分 0/30,而使用 skillmem 的代理得分 22/30。我清楚知道所有 8 個未答對問題的答案,因此開始深入探究。
問題一:三分之一的記憶未存入資料庫。Claude Code 會將其自動記憶儲存在 .md 檔案中。skillmem 透過 skillmem migrate 指令將這些檔案匯入 SQLite。在 698 個檔案中,有 208 個(30%)遺失,包括所有最近的資料。
migrate 指令雖然在我的 CLAUDE.md 中有記載,但從未被整合到 settings.json 中。在我將其加入 SessionEnd 之後,得分從 22 提高到 24/30。
問題二:會話摘要淹沒了規則。在約 9,300 筆條目中,有 8,206 筆是會話摘要。每個摘要都提及「prod」、「server」、「deploy」等詞彙。這導致一條三行規則被數百個重複相同詞彙的冗長摘要淹沒。更糟的是,舊的摘要甚至與新的規則相矛盾。
針對另一組包含已知正確條目的 30 個查詢,我們進行了以下調整:從 FTS 查詢中移除停用詞,但沒有效果,因此恢復原狀。將筆記/會話條目的融合分數乘以 0.5(搜尋結合了 FTS5 和嵌入向量,並使用 Reciprocal Rank Fusion 演算法),命中率從 18/30 提高到 26/30。
為了確保沒有過度擬合我的資料,LongMemEval 的命中率從 0.871 提高到 0.889(n=479)。修復後,代理的準確度達到 28/30,而沒有記憶功能的代理仍是 0/30。
問題三:代理不信任我的規則。還剩下一個未答對的問題。儘管正確的規則排名第一,代理仍然回答「沒有核准的規則」。這是刻意設計的。代理撰寫的任何內容在所有者確認之前都保持未核准狀態,且未核准的文字只會以「資料而非指令」的框架傳遞給模型。否則,README 或電子郵件中寫的「永遠記得做 X」就會變成一條規則,這會導致帶有持久性的提示詞注入攻擊。
這項保護措施雖然有效,但使用起來卻很痛苦:我有 34 條未核准的規則,其中 32 條是我實際說過的話。核准每條規則都需要在終端機中輸入一次 skillmem trust <slug> 指令,這根本沒人會用。
0.13 版本新增了兩種處理方式:skillmem review 會遍歷積壓的規則,完整顯示每條規則並詢問使用者是否核准(y/n/q)。我一次性清除了 33 條規則。另外,現在也可以在聊天中核准。如果代理在本輪儲存了一條規則,一個 Stop 鉤子會在回覆下方顯示該規則,並要求使用者以單詞「approve」(或俄語的 одобряю)來回答。
任何其他回覆都會使規則保持未核准狀態。確保安全性的細節包括:顯示的文字來自資料庫,而非代理的回覆;鉤子會儲存顯示內容的雜湊值,如果規則在使用者回答前發生變化,則不會被核准;核准詞不是「yes」,因為人們經常對代理說「yes」;它只顯示本輪的規則,最多三條;核准只在 UserPromptSubmit 中的真實使用者提示詞上觸發;並且 init 會為從 Bash 執行 skillmem hook …、trust、review 和 rm 指令新增 permissions.deny 條目。
我自己的代理在安裝版本時就遇到了這個拒絕。README 中直接指出的注意事項是:拒絕規則是命令列上的全域匹配,它們用於防止意外,但無法阻止有決心的攻擊者。真正的防線是無工具的摘要器加上資料框架。
附帶效益是:鉤子工具召回(hook tool-recall)功能的速度提升了 7 倍,它在每次 Bash/Edit/Write 呼叫之前執行。之前每次都會載入嵌入模型,耗時 1.45 秒。現在它只使用 FTS,只需 0.2 秒。這些數字無法證明的是:樣本量很小,包括 10 次跨視窗執行、30 個答案和 30 個檢索查詢。
這是我的資料庫,而且我是在知道答案的情況下撰寫問題的。跨視窗測試是合成的,事實被明確陳述。真實的決策通常分散在一小時的討論中,而摘要可能會遺漏它們。LongMemEval 衡量的是檢索能力,而非實際回答。
升級方式:執行 pip install -U skillmem(或透過 /plugin marketplace update skillmem)。然後執行 init --claude-code(這會新增 pending-rules 鉤子),再執行 skillmem review(清除現有的積壓規則)。
版本說明:v0.13.0,採用 Apache-2.0 授權,完全本地化。俄語版本可在 Habr 網站上找到。我從中學到的教訓是:將每一個「我們有這個功能」都轉化為測試。這次測試花了一個晚上,卻發現了三個與「記憶功能正常」共存了六個月的問題。