附录 A|Guardian:隔离的同步评审员,不是第四套沙箱
先别把 auto-review 写成 if policy == never
B3 把 Guardian 放在同意链上:问谁,不是能不能。正文故意没拆评审员 Session。没有这一附录,会把 --approve-for-me 理解成关审批,或让评审员继承父 thread 的 MCP 和 spawn_agent。
判断先说清楚:Guardian 是另一条减权 Session,同步挡住父工具,直到它给出 Allow / Deny / 升级给人。 扩展选政策和证据;core 强制权限和必审项(guardian/mod.rs:1-3)。没有 contributor 不是 隐式允许(decision.rs:50)。超时、预算、熔断都是一等公民,证据太大就 compact 或挂起,不许静默截断。
它不是 /review(E6 评 diff),不是 Codex Security(E11 找洞),不是 ExecPolicy(spawn 前前缀)。
1. 何时上场
ApprovalsReviewer::auto_review(B3)。decide_approval 还会看企业 requirements:模型是否必须审、管理员是否禁止把 reviewer 拧回 User(decision.rs:64-71)。升级沙箱、重试、外部已取消,会要求 新鲜评审,不能拿缓存当过(:72-84)。
请求种类在 GuardianApprovalRequest:exec、网络、MCP、elicitation、Unix execve 拦截等。Computer Use / node_repl 走独立 Guardian scope(E4),不要和 shell 批混成一个 bool。
/approve 是对 最近一次自动拒绝 再给人批一次(E10),不是重跑 ReviewTask。
2. 隔离:另一条 Session,工具集极瘦
捕获评审输入时 不保留父 step 的 MCP 绑定和 tool router(mod.rs:74-75)。is_basic_session_source 为真时,plan 只给 exec_command / write_stdin / view_image,且要求 managed sandbox 能执行父级文件系统限制(spec_plan.rs:975-978,B2 已引)。评审员不能 spawn 子代理、不能拉插件 MCP。
Session 来源是 Guardian 专用标签(review.rs:133,GUARDIAN_REVIEWER_NAME = "guardian")。C2 跳过非 root 抽记忆;Phase 2 整合工人同样关 Collab / MemoryTool。评审员自己变成会记笔记、会开 worker 的 agent,就失去「隔离」二字。
ext/guardian-reviewer 拥有同步评审循环和连接池(review.rs:1-2,crate lib.rs:1-2)。core 的 review_session.rs 只把父动作、环境、鉴权、上下文快照绑上去。政策在 extension,执行约束在 core。
没有隔离会怎样?评审员 exec 了它正在审的那条危险命令;或 MCP 工具自己批准自己。
3. 预算、超时、熔断:不许悄悄截断
- 输入预算: pending 评审上下文是 一次飞行中的请求,记历史前消费;预算失败要留下给 compact 重试,成功才消费(
input_budget.rs:1-4, 29-31)。进不了窗口的证据,先可行性检查,再等第一步完整开销。单条 root 消息顶 900 token,tool 条目另有顶(mod.rs:67-72)。 - 超时:
REVIEW_TIMEOUT90 秒(guardian-reviewer/src/lib.rs:36)。超时有专门文案,不是当 Allow。 - 重试: 最多 3 次 attempt(
MAX_REVIEW_ATTEMPTS)。 - 熔断:
GuardianRejectionCircuitBreaker在短窗口内拒绝过多时换策略,避免评审员卡死整条 turn。 - 决策线程:
spawn_approval_decision开名为codex-approval-review的线程跑同步评审(decision.rs:23-42),避免把评审阻塞在 Session 的 async worker 上却又必须在工具 spawn 前返回。
Abort 顺序(B1)要让 in-flight 审批先被理解成中断,而不是模型看见一次拒绝再 TurnAborted。Guardian 的 oneshot 和 CancelToken 接在同一条中断链上。
没有预算会怎样?把整段 shell 输出塞进评审 prompt,或超时当默许。那比没有 Guardian 更危险。
4. 和人、和沙箱怎么共存
| 结果 | 含义 |
|---|---|
| Allow | 这次动作过同意链;能力旋钮仍在,read-only 不会因为 Guardian 点头变成全盘写 |
| Deny | 回模型;/approve 可升级给人 |
| 升级给人 | None from decide_approval → 走原来的用户审批 UI |
| 必审 | requirements 锁死:人不能把 reviewer 拧走 |
Never 政策下 ExecPolicy 的 prompt 变 Forbidden(B3),Guardian 根本不会被请来「灵活一下」。--approve-for-me 和 --yolo / --sandbox 互斥:yolo 已经拆掉同意,评审员无事可做。
Guardian 拒绝之后那份记录不是就地销毁。feedback crate 在内存里滚动保留最近几条 record_guardian_review_failure(feedback/src/guardian.rs),guardian_review_failures 按 thread id 取快照。这些记录只在用户主动 /feedback 上报时才作为附件打包上传(FeedbackDelivery),不是自动遥测——和 B5 denylist 里「项目不能决定遥测打到哪」是同一条纪律:上报是人手动点的,Guardian 拒绝历史默认只留在本机内存里。
5. 结语:带走一句话
Guardian 是减权、有预算、同步挡住父工具的评审 Session——扩展出政策,core 强制执行;它替代的是「人点 Allow」里的一部分,不是沙箱,也不是 /review 那份 diff 报告。