Skip to content

附录 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_TIMEOUT 90 秒(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 报告。