最危險的不是 AI 忘了,是你不知道它忘了

在一次用 GPT 寫程式的 session 裡,我和模型來回推敲,把舊做法推翻並確立了新規則。結果對話一觸發自動 compact,下一回合把問題丟回去,它若無其事地把剛剛才推翻的舊做法原封不動搬出來。

要不是我腦子裡還記著上一段的結論,當場抓到矛盾,這條決定就直接在對話裡蒸發了。當時幸好原始對話的 JSONL 紀錄還留在本機,模型用關鍵句定向搜尋壓縮前的內容,才把完整證據撈回來。

壓縮後最麻煩的是缺口完全隱形

當時遇到的問題很直接:GPT 的可用 context 較小,稍微寫點東西就頻繁觸發自動 compact。但這場事故讓我意識到更深層的麻煩。

模型漏掉一整段記憶的時候,回答依然流暢得像什麼都沒發生。要是工程師自己當下恍神沒記住剛剛推翻過什麼,或是任務交接給下一輪,舊做法就可能悄悄變回後續工作的前提。缺口最危險的地方在於完全沒有任何提示:你連自己該回頭找什麼都不知道 🤣

把保存機制拆出來,不綁特定 agent

既然不能賭運氣,那就只能在它動手壓縮前買保險。我做了一套未公開的自製機制 compact-guard。它不去動產品原本用來產生摘要的 compact prompt,只在外面多加一層恢復保險。

在壓縮前,PreCompact hook 先讀原始對話,只保留主 session 裡使用者輸入的訊息和模型回覆;工具結果、subagent 對話與系統注入的內部資料全部排除。

接著它會把常見的金鑰永久遮蔽掉。這些金鑰在恢復時絕不還原,因為 checkpoint 要保存的是決定與限制,憑證本來就不該留。剩下的文字會按照更正、決定、限制、待辦、證據以及最近訊息排優先順序,去掉重複字句後,寫進有長度上限的私密 checkpoint。只要空間放不下,低優先度的內容就不會進恢復包。

原生 compact 接著照常執行。壓縮完成後的新 context 會觸發 SessionStart(source=compact);這名稱看起來像重開 session,其實只是通知 agent「同一個 session 剛完成壓縮」。在 Codex 裡面,這個事件會排到下一個使用者回合建立 prompt 前才執行。

恢復 hook 只讀同一套工具、同一個 session 的 checkpoint。確認工具名稱、session、檔案權限和內容雜湊都對得上之後,再把內容標成「先前對話的證據,不是新指令」注入新的 context。現在的版本只要成功注入就會立刻打上標記,避免同一份內容再次灌入。

至於 PostCompact hook 則完全不參與恢復,只負責追加計數與雜湊,不留存對話文字。恢復邏輯直接讀壓縮前的 checkpoint,這樣就不會被壓縮後兩個 hook 的執行順序卡死。

存證、篩選和恢復邏輯全走共用模組,各 agent 工具只接自己的轉接層。當時我驗證的四種介面是 Claude Code 的兩種模型接法,以及 OpenAI Codex 的 CLI 與 Desktop。其他 agent 能不能接,前提是有沒有現成可用的 compact 前後 hook;這四種介面的驗證結果不能直接外推成所有 AI 工具都支援。

假測試跑過不算數,沒撞過真實流量不下結論

機制寫好之後,最容易犯的毛病就是自己捏幾組假對話跑跑看,測試一過就開開心心宣布大功告成。

但我開案時就先把規矩定死:先丟進真實工作流跑一段觀察期。沒有累積足夠的自然 compact 樣本前,就繼續觀察,絕對不能只靠合成測試就拍板 KEEP。自己捏出來的情境永遠太乾淨,只有在真實工作流裡被自動壓縮正面撞過,才能確認它到底有沒有用。

連續八次壓縮直接撞出重複注入

這個原則很快就發揮了作用。在一次無人值守的 Codex Desktop 長 session 裡,系統連續 compact 了八次,八個恢復事件全部排到下一個使用者回合才執行。結果下一回合一開工,螢幕上瞬間灌入八份一模一樣的內容:舊版還不知道同一份 checkpoint 早就送過了。

這要是放著不管,重複資訊一口氣就會吃掉一大塊對話空間。但我沒有因此動搖,這本來就是觀察期的目的:在它惹出大禍前把自然環境下的真問題抓出來。

修復方式也很乾脆,每份 checkpoint 只要成功注入就立刻標記成已使用。後面排隊的事件如果又讀到同一份,只留下「已被前一個事件處理」的稽核紀錄,不再重複注入內容。重複注入的測試也重新跑過確認通過。

171 筆自然事件驗收,證據夠了才拍板保留

修好之後,結案前那段觀察期累積了 171 筆自然派送的 hook 事件列。這不是 171 次獨立壓縮;一次 compact 可能留下存證、注入與 PostCompact 等多筆紀錄。

稽核紀錄不存對話文字,而是靠 session 與對話紀錄的雜湊,把每次壓縮前的 checkpoint 跟後續注入配對。逐筆排除原生 compact 中止、以及延後到下一回合才注入的事件後,在所有已完成的壓縮裡,沒有出現過 checkpoint 該注入卻漏掉的案例。

這裡說的「沒有真漏」只涵蓋恢復事件有沒有漏送。恢復包本身有長度上限,金鑰也會永久遮蔽,所以原始對話本來就不可能逐句完整留下來。

這並不代表這套機制從此天下無敵永遠不掉球。但以當時累積的 171 筆自然 hook 事件來看,證據已經足夠支撐我拍板 KEEP,這張保單確實接住了壓縮時的缺口。

後記:換上 1M context 之後

後來我在日常開發裡啟用了 1M context,自動 compact 的頻率大幅降低。我也多了選擇權,可以保留長 context,或在需要時接受壓縮。畢竟 long context 的 OpenAI API 費率較高,壓縮在實務上依然有它的價值。

compact-guard 現在上場的頻率低了很多,但我依然留著當低頻保險。