三天前我把 Claude Code 的記憶索引從 O(N) 改成 O(1),主檔只列分類、不列單一則記憶,那篇記錄在這裡。
今天我以為這件事已經結束了。結果在量另一件事的時候發現,膨脹沒有消失,它往下一層跑了。
先交代怎麼發現的。
我要把這套記憶結構整理成一份說明書,給另一個 AI 參考。寫到「每一層的大小」時,照例每個檔都量一次。
主檔是好的:62 行、4,829 bytes。這三天照常在新增記憶,沒有一條寫進主檔。
但下一層的兩個檔不是:
| 檔案 | 行數 |
|---|---|
| 最大的主題檔(方法論那一份) | 4,572 |
| 近期紀錄檔(AI 每次存檔時寫一段) | 4,513 |
這兩個數字的問題不是「大」,是超過了一個具體的上限。
Claude Code 的讀檔工具預設一次讀 2,000 行。超過的部分要指定位置再讀一次,實務上很少會發生。
更糟的是,這兩個檔都是新內容往檔尾加。所以讀不到的那一段,剛好就是最新寫進去的那一段。
我去確認了:近期紀錄檔裡最近 5 天的內容,從第 4,198 行才開始。
Claude Code 讀檔工具預設單次讀 2,000 行。記憶檔若採檔尾追加,超過上限後最先讀不到的是最新內容。實測近期紀錄檔長到 4,513 行時,最近 5 天的紀錄從第 4,198 行開始,最大的主題檔則長到 4,572 行。
主檔改成 O(1) 之後,O(N) 沒有消失,它被推到被索引指到的那一層。
近期紀錄檔這個問題,第一個診斷只對一半。
這個檔從 7 月就設計成「只保留最近 45 天,更早的搬去歷史檔」。規則寫在每月健檢清單裡,兩個多月一次都沒被執行,檔案裡最舊的紀錄還停在 7 月 2 日。
所以第一個結論是:沒有東西去執行滾動。這是對的,但不完整。
我把不同長度的視窗各量一次:
| 保留天數 | 行數 | 一次讀得完 |
|---|---|---|
| 45 天 | 4,081 | 否 |
| 30 天 | 3,386 | 否 |
| 21 天 | 2,253 | 否 |
| 14 天 | 1,521 | 是 |
就算每天準時滾,45 天的視窗也有 4,081 行,照樣讀不完。
原因是寫入量比 7 月設計時大很多。光 8 月一個月就寫了 3,162 行,一天將近 100 行。
「45 天」這個數字從一開始就不是算出來的,是拍腦袋的。天數固定的視窗,大小會跟著寫入量一起長。
AI 近期紀錄的保留視窗要用行數驗證,不能只用天數設定。實測一天寫入將近 100 行時,45 天視窗 4,081 行、30 天 3,386 行、21 天 2,253 行,都超過讀檔單次 2,000 行上限,只有 14 天的 1,521 行讀得完。
近期紀錄檔的改法是兩件事。
第一,視窗從 45 天改成 14 天。
第二,把「搬走舊段落」寫成程式,掛在每次開場自動執行,不再放在要靠 AI 想起來的清單裡。
程式的安全順序是這樣:先把要搬的段落寫進按月分的歷史檔,讀回來確認每一段都在,再確認「留下的字數加搬走的字數等於原檔字數」。兩項都過才改寫原檔,任何一項不過就停,原檔不動。
我先拿複本試跑,比對過才碰正本。第一次正式跑搬走 95 段,檔案從 4,513 行降到 1,526 行。原檔 4,513 行,搬完「留下的加搬走的」也是 4,513 行,少 0 行。
再跑一次,什麼都沒輸出,因為沒有東西要搬。
主題檔的改法,是把三天前對主檔做的事往下套一層。
4,572 行、218 段,拆成 8 個子主題檔,最大的一個 905 行。原檔保留原本的名字,變成 389 行的目錄,每一段的標題都列在上面,看標題就知道在哪個子主題檔。
保留原檔名是因為有十幾個其他檔案引用它,包括規則檔與專案紀錄。目錄在,那些引用就不會斷,只是多點一層。
切段要注意一件事:以二級標題切的時候,要跳過程式碼區塊。這個檔裡有 24 個程式碼區塊,有些 shell 註解是井字號開頭。這次剛好沒有兩個井字號開頭的,但切段程式還是要跳過程式碼區塊,不然哪天有一行註解長得像標題,就會把程式碼切成一段新的。
搬完之後,段落裡有 2 條相對連結因為資料夾多了一層而失效,改成多一層 ../。
拆到一半出了一個小插曲,值得單獨講。
我正式寫檔前做了一次比對,發現原檔跟我拆之前存的備份不一樣,程式直接停下來。
查了一下,是另一個視窗的 Claude Code 在幾分鐘前往這個檔的條目認領段加了一行。我分類時看的是加了那一行的版本,備份卻是更早存的。
如果我照原本的做法直接寫,那一行會被我的舊版本蓋掉。
改法是用最新版本重新存一份快照,以它為準重拆。那一行留在目錄檔裡,原封不動。
多個 session 同時寫同一批記憶檔,是這種系統的常態。任何會改寫整份檔案的操作,寫入前都要比對一次版本。
驗收用的是逐行比對。
把拆之前的快照跟 9 個新檔(8 個子主題加 1 個目錄)的每一行做比對。快照有 4,572 行,在新檔裡找不到的行數是 0。多出來的 333 行,全是目錄與每個子主題檔的檔頭。
把一份 4,572 行、218 段的 AI 記憶主題檔拆成 8 個子主題檔加 1 份目錄,最大子檔 905 行、目錄 389 行。以拆分前快照逐行比對,遺失 0 行,新增 333 行全為目錄與檔頭。原檔名保留當目錄,引用它的其他檔案不需修改。
最後補的是一個看得到的警報。
我再寫一支開場檢查:任何主題檔超過 1,500 行、或近期紀錄超過 2,000 行,就在開場印一行提醒。沒超標就什麼都不印。1,500 比讀取上限低 500 行,留處理的緩衝。
放在開場而不是寫入前,是因為我之前踩過:寫入前的 hook 如果只想警告、不想擋下來,它的訊息 AI 根本看不到。開場 hook 的輸出會直接進對話,才真的會被讀到。
它第一次有機會響,應該是另一個主題檔。那份現在 1,392 行,是下一個。
三天前我以為問題是「索引會長」。今天才知道,問題是「任何只進不出的檔都會長」,索引只是第一個撞牆的。
所以每一種會長的檔,在建立的當下就要決定三件事:上限用行數定,不用天數;退場由程式在一定會跑的時機執行,不放在要靠記性的清單裡;撞牆之前要有一個看得到的警報。