驗收安全閘,要刻意送入受限制的工具呼叫,檢查拒絕回應、攔截紀錄與資料狀態;空的紀錄檔不足以證明它正常運作。
需求釐清 agent 整理 ticket 時,可以更新目標、範圍與問題標註,但不能自行覆寫需求方原本的驗收條件 AC。現有安全閘會阻止負責覆寫的 replace_acceptance_criteria,問題是實跑一直沒有用到這個工具。
翻查這個版本的執行紀錄,71 份 trajectory.json 都沒有覆寫工具呼叫。其中 65 份執行另有 gate-blocks.json,內容全是空陣列;另外 6 份早期紀錄沒有這個檔案,連 trajectory 裡也沒有 blocks 欄位。
這些執行沒有留下攔截事件,卻也沒有提供「呼叫真的送來時能否擋住」的證據,所以要另外安排測試,直接送入覆寫呼叫。
其中一次執行,模型在文字裡表示要先取得同意,接著只呼叫 update_ticket,寫回整理後的內容。這次沒有提出覆寫呼叫,紀錄中的 blocks 也是空的。
工具說明確實要求先取得同意,system prompt 也寫了不要覆寫原始 AC。但只憑這段對話,還不能確定是哪一段指示讓模型避開覆寫,更不能把模型自行避開的行為當成 gate 已攔截成功。
負責執行工具呼叫的 dispatch,會在處理寫入工具前先呼叫 checkGate。如果得到攔截紀錄,就加入 blocks 陣列,回傳拒絕原因並提前結束(原始碼):
const block = checkGate(call);
if (block) {
blocks?.push(block);
return block.reason;
}
因此,呼叫 update_ticket 時仍會經過 checkGate,只是沒有進入拒絕覆寫的分支。「檢查函式被呼叫過」和「拒絕分支被驗證過」,需要不同的證據。
攔截紀錄的型別是 GateBlock,保留 gate 名稱、工具名、參數、拒絕原因與時間。這些資料可以用來追查哪一次呼叫被擋、當時要求改什麼,而不只是看到一句拒絕訊息。
現有的測試先用 seed() 準備只有兩條 AC 的卡片,再交給 FakeStore 在記憶體保存。overwrite 是事先寫好的覆寫工具呼叫,參數包含準備換上的 AC。
接著直接呼叫 dispatch,這一步沒有使用模型:
test('the blocked call leaves the requester AC untouched', async () => {
const store = FakeStore.fromTicket(seed());
const blocks: GateBlock[] = [];
const out = await dispatch(overwrite, store, undefined, blocks);
assert.match(out, /拒絕/);
assert.equal(blocks.length, 1);
assert.deepEqual((await store.getTicket('T-1')).acceptanceCriteria, [
'一人只能用一次',
'可以無限次使用',
]);
});
三個 assertion 分別確認拒絕理由有回傳、攔截紀錄多了一筆,以及 AC 原文沒有改變。如果只查最後一項,工具根本沒有執行,也可能通過測試。
這個版本的 dispatch 裡沒有覆寫 AC 的分支,即使跳過 gate,也只會得到找不到工具的錯誤,原始 AC 仍不會改變。因此,拒絕回應與攔截紀錄才有助於確認這次確實經過 gate。
同一個測試檔另有 ScriptedLlm,用預先安排的回覆代替模型,讓 runAgent 收到覆寫呼叫,再檢查回傳的 trajectory 是否帶有攔截紀錄。另一條只安排 post_comment 留言,要求攔截紀錄為空,確認這個呼叫沒有被誤攔。
直接測 dispatch,確認工具執行前會拒絕;經過 agent loop 再測,則確認 loop 有把攔截紀錄帶回來。兩者都不需要等真實模型剛好選到覆寫工具。
runAgent 把 blocks 傳給每次工具執行,再放進 trajectory 回傳。執行程式接著把它寫成 gate-blocks.json,沒有事件也寫入 []。
固定保存這個檔案,方便收集每次執行的攔截結果,也能分辨「明確寫出空陣列」和「沒有提供這項紀錄」。但空陣列只代表沒有保存攔截事件,不能證明攔截分支曾執行;缺檔則可能是舊版本或執行中斷,也不能直接判成 gate 沒有接上。
前面的測試涵蓋到 trajectory,還沒檢查檔案寫入。若要驗收完整的紀錄流程,還要讓測試產生一次攔截,經過實際寫檔步驟,再讀回檔案確認事件有保存,不能只檢查檔案存在。
要驗收電話 agent 的 email 確認,也可以在測試中安排未取得確認就要求寄送,檢查寄信工具沒有執行、拒絕紀錄有留下。取得有效確認後能否寄送,則需要另一條測試,否則一律拒絕也會看似正常。
沒有攔截紀錄時,先確認資料是否完整,再用測試明確送入受限制的呼叫。這個 gate 的驗收同時檢查拒絕回應、攔截紀錄和 AC 原文,並用假模型確認紀錄能經由 agent loop 回傳;卡片整理得對不對,仍由另一組檢查負責。
目前驗證的是這個版本會阻止覆寫。紀錄能否完整寫入檔案,以及未來取得同意後如何放行,都還需要各自的測試,不能從空陣列或原始 AC 沒變推論已經完成。