Claude Code 的 MEMORY.md 清過還是會肥回來:我掃了 85 個 session,發現 77% 的記憶從沒被讀過
直接回答
AI 記憶索引清完又肥回來,是因為清理只改了起點沒改斜率。我用 git 還原自己兩次瘦身:第一次從 44,304 bytes 砍到 3,629,成長率也從每天 1,150 bytes 降到 256,但斜率還是正的,118 天後又長回 33,804 bytes。真正的解法是讓索引大小跟記憶總數脫鉤,主檔只列固定分類不列單一則記憶,這樣新增第 558 則記憶時主檔增加 0 bytes。
我的 AI 記憶索引今天第二次快撞牆。上一次是 2026 年 5 月,我把它從 44,304 bytes 砍到 3,629,砍掉 92%。
四個月後,它是 22,693 字,離讀取上限只剩大概 13 天。
我原本要再清一次。清到一半停下來,因為我意識到自己在做一件四個月前做過、而且明顯沒有用的事。
—
先講這個系統長什麼樣,不然後面的數字沒有意義。
Claude Code 有一個檔案叫 MEMORY.md,每個 session 開始都會載入。它是一份索引,一行一則記憶,告訴我硬碟上有哪些筆記存在。真正的內容在別的檔案裡,按需要才讀。
這個設計本身沒問題,問題是它會長。到今天為止我累積了 557 個記憶檔,索引 156 條。
—
第一件事是不要急著清,先去量。
我的設定資料夾有每日 git 備份,所以索引檔的每一個版本都還在。我把每一天的大小還原出來,得到兩段曲線。
第一段,2026 年 4 月 25 日到 5 月 9 日:163 條長到 237 條,28,197 bytes 長到 44,304 bytes。每天 1,150 bytes。
5 月 13 日那次重構砍到 3,629 bytes、23 條。
第二段,從那天到今天 118 天:長回 33,804 bytes、156 條。每天 256 bytes。
兩次 AI 記憶索引瘦身的實測對比:第一次重構把成長率從每天 1,150 bytes 壓到 256 bytes,降了 4.5 倍,但斜率仍為正。118 天後索引從 3,629 bytes 長回 33,804 bytes。清理改的是起點,不是斜率,所以撞牆只是被延後而不是被解決。
而且不只條數在長,每一條也在變胖。平均一條從 158 bytes 長到 217 bytes,胖了 37%。成長是條數乘以每條長度,兩個乘數都在漲。
—
第二件事是搞清楚這些記憶到底有沒有人用。
Claude Code 的每個 session 都會在 projects 資料夾留一份完整的 jsonl 逐字稿,裡面每一次讀檔的路徑都有記錄。我本機有 85 個檔、1.1 GB。這份資料躺在那裡半年,我從來沒跑過。
一個 rg 指令掃完,結果比我預期難看很多。
掃 85 個 Claude Code session 的實際讀取紀錄:557 個記憶檔只有 127 個在 30 天內被打開過,430 個(77%)完全沒被碰。索引的 156 條裡有 72 條(46%)指向沒人開的檔案,佔索引 41% 的位元組。30 個檔案(5%)吃掉全部 1,603 次讀取的 80%。
還有一個數字反過來很有意思:有 43 個檔案被讀取了,但它們根本不在索引裡。是靠主題檔或直接路徑找到的。
也就是說,不靠這份索引,路一樣走得通。
—
到這裡就可以問那個真正的問題了:這份索引為什麼要存在。
它的工作是讓我知道有某個記憶存在。但 77% 的記憶已經 30 天沒被打開,這個工作對絕大多數內容早就失效了。它實際只覆蓋到我本來就記得的那一小撮頭部,卻要我每個 session 付 22,693 字的成本。
再把式子寫出來:開機成本等於條數乘以每條長度。條數每天加 1.13,長度也在漲。只要成本正比於記憶總數,就永遠會再滿一次。
結論不是「這次要清得更用力」,是「索引的大小不能跟記憶總數綁在一起」。
—
改法很簡單,簡單到有點反高潮。
主檔的結構裡,不再有「單一則記憶」這種列。它只列固定的分類:10 個主題檔、7 個使用者檔、5 個外部參照、1 行指向專案索引,加一段告訴未來的自己新記憶該寫去哪。
原本 156 條專案與筆記條目,88 條搬進一個獨立的專案索引,43 條掛進對應主題檔的認領段。這兩層都是按需讀取,愛長多大都行,因為它們不佔開機成本。
主檔從 22,693 字變成 3,339 字,砍 85%。
但重點不是這個數字。重點是新增第 558 則記憶時,主檔增加 0 bytes。斜率是零,不是變小。
—
搬的過程差點出事,這段值得單獨講。
我有一條規則是「動手前必先 diff,沒被新位置涵蓋的先搬再刪」。我照做了,寫了一個檢查去確認每一條在新位置找得到。
第一版的檢查是拿去掉副檔名的前綴去 grep。它回報 47 條裡有 13 條已經被涵蓋。
搬完之後我換一種寫法重掃,才發現那 13 條裡有 9 條是假的。因為 grep 會比對到主題檔內文裡剛好提到那個字串的散文,那是「提過」不是「認領」。去掉副檔名讓字串更短、更容易誤中。
如果我沒有重掃,那 9 則記憶會直接變孤兒。
教訓是模糊比對只能用來找候選,不能用來宣告安全。要下「已涵蓋、可以刪」這種結論,比對條件必須嚴格到不可能誤中,而且驗收要跑兩次、第二次換一種寫法。
—
最後一段是整件事裡最意外的發現。
我本來以為索引是四個月慢慢長回來的。查下去不是。
7 月 1 日 42 條,7 月 3 日 45 條,7 月 5 日 71 條。兩天內加了 26 條。
那兩天我在做記憶系統的健檢,並且把健檢流程寫成一份每月要跑的維護程序。那份程序裡有一條:索引對帳,新檔沒進索引就補一行。
而另一份更早的存檔流程文件,從頭到尾寫著「主檔永遠 thin,不寫 index」。
索引回流的實際起點可以定位到單一天:2026 年 7 月 3 日索引 45 條,7 月 5 日 71 條,兩天內增加 26 條。原因是同期寫下的月度維護程序要求「新檔沒進索引就補一行」,直接牴觸另一份文件裡「主檔永遠 thin,不寫 index」的原則。兩份制度文件互相矛盾時,可執行的程序打贏了原則。
這個解釋比「我不夠自律」精確得多,而且可以修。我把那份維護程序的兩行改掉了,不然下個月的健檢會叫我去做一件現在會被擋下來的事。
—
所以最後我加了一支 hook。
不是因為結構不夠,是因為我需要一個證據。我在分析這個膨脹問題的同一天,一邊分析一邊往主檔塞了一條 372 bytes 的紀錄,全檔第二長。我知道規則,還是照犯。
那支 hook 掛在寫入前,偵測到單一記憶條目或檔案超過行數上限就直接擋,而且會在錯誤訊息裡告訴我正確位置在哪。這一點很重要:只擋不指路的檢查會讓人卡住,卡住之後就會有人去繞過它。
裝完我真的去試著寫一條進去,被擋下來,主檔一個字都沒改到。然後我把今天這個 grep 誤判的教訓寫成第 558 則記憶,走新流程掛進主題檔,主檔還是 3,339 字。
—
如果只留一句:清理是狀態,結構是流程。你清掉的東西會照原本的速率長回來,除非你把生產它的那條路本身改掉。
判斷自己是在改哪一個,只要問一句話:這次改完之後,斜率會變成幾?