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,再算一遍。
进阶一问:用户后来用人工覆盖批准了那条被拒动作。下次审查会把它当成可信授权,还是只当上下文。