Guardian:讓一個模型去審批另一個模型
審批彈窗多到人開始無腦點同意時,Codex 把決定權交給一次鎖死的審查會話。超時、壞 JSON、連續拒絕,各有各的收場。
on-request 審批,用一次鎖死的模型會話代替使用者彈窗。審查材料按不可信證據處理,超時、跑崩、壞 JSON 一律停住動作。明確拒絕才記入熔斷器,連拒到閾值就打斷當前 turn。
- 判斷本輪審批是否交給 Guardianreview.rs L186
- 擴充若宣告接管則不再開審查會話review.rs L313
- 過濾 transcript,只留使用者意圖和工具證據prompt.rs L506
- 按 Full 或 Delta 拼審查材料prompt.rs L89
- 克隆父配置,鎖成只讀並寫成 Neverreview_session.rs L1407
- trunk 空閒則追加,忙碌則 forkreview_session.rs L617
- 等待 JSON,超時上限 90 秒mod.rs L53
- 沒有合法 JSON 就解析失敗,可重試後關閘prompt.rs L714
- allow 放行,deny 寫上禁止改道的指示review.rs L717
- 只有顯式 Deny 才記入熔斷器review.rs L711
- 達到連續或視窗閾值,就 InterruptTurnmod.rs L176
policy.md 的 Outcome rule 推演,用於展示四步法與熔斷器的結構差異。邏輯軌跡右側行號對應 openai/codex 倉庫 commit 4f39251a01。你開著 Codex 改倉庫。模型先跑 git status,再讀兩個檔案,然後要 git push。下一分鐘它要 curl,再下一分鐘要往 /tmp 寫備忘,再下一分鐘要刪掉那份備忘。彈窗疊彈窗。人開始只看命令頭兩個詞,拇指已經形成肌肉記憶。
這就是審批疲勞。防線還在,判斷已經不在。迴路退化成一個總是亮綠燈的按鈕。這時如果把自動放行交給剛寫出這條命令的主模型,申請人兼審批人。如果失敗時放行,超時和壞 JSON 就變成免費通行證。
Codex 把該不該自動放行抽成一次專職審查。它不替代沙箱,也不替代網路代理。它只接管一類本來會彈給使用者的 on-request 審批。路由很窄:策略必須是 OnRequest 或 Granular,審查員必須是 AutoReview。預設審查員仍是使用者。出處:codex-rs/core/src/guardian/review.rs 第 186 至 210 行
模組頭把四步寫死。
只留使用者原話和近期工具痕跡。合成腳手架、普通 developer 訊息丟掉。超長內容打上 truncated 標記。
克隆父配置,繼承已經建好的 managed network 與 allowlist。審查員必須吐出嚴格 JSON。
超時、跑崩、壞 JSON,執行一律停住。中間態按拒絕處理。
合同要的是 allow 或 deny。產品要的是可執行判定。
合同只有四個欄位。三個軸的列舉寫在協議層的 approvals.rs,不在 protocol.rs。outcome 是唯一必填。低風險允許可以只回 {"outcome":"allow"},預設的風險補成 Low,授權補成 Unknown。
/// Structured output contract that the guardian reviewer must satisfy.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub(crate) struct GuardianAssessment {
pub(crate) risk_level: codex_protocol::protocol::GuardianRiskLevel,
pub(crate) user_authorization: codex_protocol::protocol::GuardianUserAuthorization,
pub(crate) outcome: GuardianAssessmentOutcome,
pub(crate) rationale: String,
}
openai/codex,核對檔案 codex-rs/core/src/guardian/mod.rs,commit 4f39251a01,核對日期 2026-08-22。GuardianRiskLevel、GuardianUserAuthorization、GuardianAssessmentOutcome 的窮盡列舉在 codex-rs/protocol/src/approvals.rs 第 85 至 109 行。審批疲勞是人因問題,審查員自己也會格式漂移。把沒給出可執行答案收成拒絕,換語言重寫也成立。輸入是不可信證據加一條確切動作,輸出是窮盡的 allow 或 deny,解析失敗就停。
主模型剛寫出這條要執行的命令。再讓它審自己,迴路是通的。工具輸出裡再夾一句請批准我,審查材料就被汙染。審查權如果落回申請人,專職審查員這個角色就不存在。
審查會話克隆父配置後立刻改寫。審批策略鎖成 Never,權限剖面與只讀相交,MCP 伺服器清成空表,技能、記憶、Apps 指令關掉,再禁用一串非必要特性。它還丟掉父級傳入的 exec-policy 規則,改用空的預設管理器。父級規則可能反過來塑造審查員,甚至讓審查員自己跑的只讀命令被靜默放行。出處:codex-rs/core/src/guardian/review_session.rs 第 1406 至 1448 行;codex-rs/core/src/session/mod.rs 第 544 至 548 行
審查員可以跑只讀命令,用來核實本地狀態,比如刪除目標在不在、是不是空目錄。它不會彈出新的審批,也不會再開一層 Guardian。配置裝配時顯式禁用了 GuardianV2。
trunk 空閒時,後續審批追加到同一條會話,prompt cache 穩定。trunk 忙碌時,從上次提交的 rollout 長出一間臨時小屋,審完就拆,不回寫。
給不受信任的審查權一個儘量小的環境,是安全設計的通用形狀。換模型、換提示詞、換能力面,該問的還是同一句:審查員到底需要哪幾樣能力,其餘的能不能一樣都不給。
審查員和執行模型卡在同一條被拒動作上,人看到的是一輪空轉。如果把超時也算進連續拒絕,基礎設施抖動會被誤判成模型連續作惡。
超時走 ReviewDecision::TimedOut,明確拒絕走 Denied。對模型的文案也拆開。超時告訴它不要僅因超時認定不安全,可以再試一次或去問使用者。明確拒絕拼上禁止改道繞過的指示,只允許實質更安全的替代,或使用者知情後的明確批准。出處:codex-rs/protocol/src/protocol.rs 第 3909 至 3910 行;codex-rs/core/src/guardian/review.rs 第 70 至 74 行
熔斷器按 turn 記帳,視窗長度 50。Standard 的閾值是連續 3 次,或視窗內累計 10 次。CyberModel 緊到第一次拒絕就停。只有評估結果為 Deny 才計入。超時和 fail-closed 走 record_non_denial,連續計數清零。觸發後發 GuardianWarning,再 abort_turn_if_active。出處:codex-rs/core/src/guardian/mod.rs 第 53 至 59 行、第 157 至 194 行
安全閘門需要把沒做完和明確說不分開記帳。混成一句沒通過,會把瞬時故障訓練成繞過行為。按 turn 計數、按視窗計數,兩套閾值即可。面向高對抗場景的策略可以緊到第一次拒絕就停。
DeepSeek Harness:兩個旋鈕,決定權留在人
在 DSH 倉庫裡檢索 reviewer、guardian、auto-approve,沒有獨立審查會話這種實現。它把同一問題收成兩個旋鈕。審批策略只有 ask 和 never。ask 把問題交給 answerer 鏈,鏈上沒人接就 unavailable,呼叫方 fail closed。never 直接 rejected。授權粒度是一次性的 allowed-once。
DSH 可以沒有 Guardian,因為它把誰來審固定成人,再用預設降低切換成本。代價是審批疲勞原樣存在。ask 模式下彈窗仍然全數到達使用者,never 則把判斷權整段關掉。
Claude Code:分類器給彈窗搶時間
在 Claude Code 的還原原始碼裡同樣沒有 Guardian 那種獨立審查會話、風險分類學 JSON 合同,或按 turn 計數的熔斷器。找到的是 Bash 工具上的一條分類器旁路。分類器在使用者彈窗已經顯示時後臺跑。高置信且使用者還沒動手,才替使用者點允許。它匹配的是 prompt rule,範圍是 bash 命令。彈窗仍在。失敗時使用者繼續自己點。
差別可以收成一句話。Claude Code 用分類器給彈窗搶時間,Codex 用一次鎖死會話把彈窗從主路徑拿掉。前者省的是等待,後者改的是誰來做決定。
已核對 bashPermissions.ts 分類器旁路 · 2026-08-22同一次 turn,熔斷器怎麼記帳
同一 turn 裡,審查員先超時一次,再明確拒絕兩次。問:下一次再明確拒絕時,Standard 熔斷器會不會打斷這一輪。把第二次改成壞 JSON,再算一遍。
進階一問:使用者後來用人工覆蓋批准了那條被拒動作。下次審查會把它當成可信授權,還是只當上下文。