不是每條規矩都要寫成 hook:從一句話到硬攔截的四層階梯
最近盤點了一下自己的 settings.json,赫然發現光是在全域層級,就有 53 筆 hook 註冊。這不是 53 支不同腳本,也不含專案層另外掛的 hook。
每次看到 agent 犯了同一個蠢錯,我的第一個直覺都是:「那我寫支 hook 把它擋死好了。」但強制執行點越堆越多,每多一個就多一份長期維護成本。之前在第二十八篇〈防得了失誤,防不住意圖:給 AI 的 shell 裝一把鎖〉和第二十九篇〈我裁掉了兩個 AI 檢查員,留下的那個能者過勞〉裡,正常指令被誤擋、每個回合都派模型檢查所製造的噪音,都是把防線設得太激進要付的代價。
但有一條規則,我當初只在 CLAUDE.md 裡寫了一句話。在那次為期 8 天的觀察窗、955 則使用者訊息裡,它繳出了零復發,全程一行程式碼都沒寫。這個「零」只描述那 8 天,不是終身保固。
先補一條範圍限制:另一組實測裡,三條常駐規則的字串只在 1% 到 2% 的 session 裡實際出現。這條零復發是正向例外,絕對不是說以後規矩隨便寫一句話就天下太平。
既然一句話有機會搞定,為什麼我們總想著大費周章寫程式?面對 agent 的失誤,一條規矩到底該用多強的手段去執行?
強度的四層階梯
這裡的「強度」指的是執行的確定性:從靠模型自己遵守,一路走到程式阻擋與人類授權。這不是四種都能百分之百約束 agent 的機制,而是四種越來越難繞過的執行方式。每一層在我的日常環境裡都有實際住戶:
- 第一層:一句話規則。直接寫進 CLAUDE.md 的純文字提示詞,例如「答案必須落在回合最終訊息」。零開發成本,但結果仍靠模型自身的遵循能力。
- 第二層:流程檢查清單。封裝成 skill 的標準作業流程,把多步驟要求結構化,讓 agent 逐項對照。它比一句話多了結構,仍沒有決定論的阻擋能力。
- 第三層:程式攔截。寫成 hook 腳本掛在工具執行前後,用決定論邏輯檢查參數或檔案狀態,不符合條件就直接阻擋或警告。
- 第四層:人類放行碼。最硬的一層閘門,例如整合用來攔截危險 shell 指令的 destructive_command_guard。在我目前整合的流程裡,指令被攔後,agent 重試也沒用,必須由人類手動輸入一次性授權碼才能放行。
往上爬每一層都要付代價:彈性下降、維護負擔加重、誤攔機會增加。如果第一層就能解決問題,硬升到第三層就是純粹製造自我麻煩。
但這張階梯只回答「手段要多硬」。後面的離線批次與換攔截位置不是第五、第六層,而是另外兩個判斷:如果位置選錯,或根本綁不到即時觸發點,繼續往上爬也不會有用。
升級門票是先寫好的復發門檻
我以前決定要不要寫 hook,常依據「這個錯誤聽起來多嚴重」或「我當下有多焦慮」。但焦慮不能當成系統架構的準據。
一條規則要不要往上爬,先看可數的復發次數有沒有越過事先寫好的門檻。復發是升級的必要證據,但不是「出現一次就全部自動升級」。每條規則開案時先寫清楚:什麼算一筆、觀察多久、幾筆觸發升級。
例如某個重要文件流程的門檻,預先就定成「勸告被繞過一次便升級」。agent 修改了應該附測試收據的檔案,卻沒有留下收據就直接結案,這一筆復發便跨過門檻。後來的硬閘會要求 agent 在收工前列出已完成的檢查;收據不完整,就不准結案。
相對地,另一條關於論證與證據層級的規則,在調整文字後,違規頻率下降但沒有歸零。它當初登記的判準容許這種結果停在原層繼續觀察,所以不需要為了追求完美的零錯誤而盲目升級。
中間層是安全網,不是強制層
即使第二層的 skill 漏接一步,也不能直接判定整套機制失敗。
某個流程檢查清單型 skill 在結案對照時曾出現漏接,但那些漏接沒有對應到可觀察的錯誤產出。依它開案時寫好的判準,這種結果代表保留為設計階段的安全網,而不是升成硬閘。
它的價值在於用極低的摩擦力提供結構化引導,而不是變成另一個隨時把工作流程卡死的硬閘門。
側出口:不進即時路徑,改走離線挖掘
並不是所有規矩都適合塞進即時互動的階梯裡。
如果一條紀律同時具備三個條件:綁不到穩定的 hook 觸發點、發生頻率低、又需要讀完整語境才能判斷,那麼在我的環境裡,每個回合都派模型即時審查,實際結果就是拖慢速度並製造大量誤報。
這時候不要往上爬,直接「往側走」:移出即時路徑,改走每日離線挖掘搭配人工拍板。
這不是臨時想出的捷徑。我原本就把低頻又吃算力的工作放在每日批次,而不是每個回合執行。背景排程會掃描過去的 session 紀錄,把疑似違規案例寫進帳本;實務上大約每週浮現一個候選,再由我手動決定採納或拒絕。
這條流程結案時記下的最弱環節,不是背景挖掘的精準度,而是人類有沒有檢視帳本並完成拍板。
第二維度:強度不變,換個攔截位置
第一個維度是執行強度,第二個維度是攔截位置。前一節的離線批次是整條離開即時路徑;這一節談的則是留在即時路徑裡,只把同一種防線搬到更有用的位置。
換位置只問兩題:這個攔截點看不看得到你要檢查的狀態?看到了之後,還來不來得及改變 agent 的行為?
在過去的兩篇文章裡,我提過兩個已結案的遷移案例:測試品質防線從 commit 當下的文字提醒,搬到 git push 前檢查完整狀態(詳見〈AI 寫的測試全綠,但可能什麼都沒測〉);skill 的回歸測試防線則從編輯檔案後提醒,搬到工作結案時核對收據(詳見〈skill 也要有考卷:我給 skill 上了回歸測試〉)。兩案都把檢查搬到看得到必要狀態、又還能阻止結案的位置,詳細結果留在原文。
目前手邊還有另外兩個仍在觀察中、尚未結案的遷移嘗試:
一個是搜尋姿勢的提醒。原本掛在 agent 準備結束回合時才觸發的 Stop 事件,違規後只能等到下一回合才看到提醒;現在改成 grep 執行後立刻提示。它不能撤回已經發生的那一次搜尋,但來得及影響同一回合的下一個動作。
另一個則是常駐規則的措辭調整無效後,我不再修改提示詞,也不再多掛一個只看單一訊息的審查 agent,而是直接修改會產生錯誤選項的上游 command,讓 command 不再產生那個選項。
兩條關鍵的判讀紀律
在管理這套階梯的過程中,有兩條極容易被誤讀的紀律必須先講清楚:
第一,不能單憑 hook 零觸發,就判定它沒用。 先確認 hook 確實有在執行,再看觀察期裡有沒有出現它要攔的事件。如果掛載正常、目標事件沒發生,零觸發只代表這次沒被考到。這條判讀規則要在開案第一天就寫進決策邏輯,否則時間一到,很容易誤殺原本用來守底線的機制。
第二,prompt 可以引導行為,但不是決定論的約束。 這不代表第一層沒有用。如果復發仍低於預先登記的門檻,靠模型遵循就已經夠便宜;只有當復發越過門檻、原層已經不夠時,才需要換成程式機制。
例如某個 skill 只在步驟說明裡列出四個必讀來源。實測跑了 8 次,agent 只有 3 次真正讀完整份清單;後來改成四欄收據契約,收據不完整就不能結案。另一個例子是在 prompt 裡寫「建議不要載入本機模型」,agent 依然照樣載入,造成當次執行環境的共用記憶體耗盡。資源互斥如果真的不能被繞過,就要寫進腳本,不能靠一句建議去賭模型的自律。
判準的自我考驗
最後,必須回頭講講開場提到的那條一句話規則。
它在那次 8 天觀察窗、955 則訊息裡零復發;觀察窗結案後,卻又陸續復發了 5 次。兩個窗口的量法不同,我不把它們接成「效果逐漸變差」的趨勢,只取一個足夠做決策的事實:復發不再是零。
那份專門記錄觀察窗與升級條件的試用帳本,在開案時就寫了「一旦復發,升級成 Stop hook」。這條規則判斷的正好是 agent 能不能正確收尾,所以在 agent 準備結束回合時攔截並不算太晚;同一個 Stop 位置對前面那種發生在回合中途的搜尋行為,就已經來不及。
按我前面訂的準據,5 次復發早已越過預先登記的門檻。但截至整理這篇素材時,那支 Stop hook 都還沒有被實作出來。
這就是真實工程環境裡最荒謬的現場:方法很清楚,升級門檻也寫得合情合理,不代表人類的執行力隨時都能跟上。階梯如果只停留在文章和筆記裡,規矩就依然只是一句隨時會被拋到腦後的空話。🤣
收尾的決策順序
面對 agent 再次犯錯時,不要急著打開編輯器寫 hook,照著以下順序做決定:
- 開案先寫清楚:什麼算一筆復發、觀察多久、幾筆觸發升級。
- 預設從最低層的一句話規則起步,能靠輕量手段解決就不要引入程式負擔。
- 數字越過預先登記的門檻,才考慮往上升級;符合原本的保留判準,就留在原層繼續觀察。
- 升級之前先看第二維度:強度不變只換攔截位置,能不能看見必要狀態,又來得及阻止行為?
- 一條規矩如果同時低頻、綁不到穩定觸發點、又需要完整語境判斷,就移出即時路徑,交給離線批次與人工拍板。
- 門檻一旦真的被跨過,就完成預先登記的升級動作。