Reviewer
reviewer 是给每个改动型操作二签的 persona。和别的 worker 不一样, 它从来不被 coordinator 派出 —— 目录里故意把它排除了。唯一的派发方 是 ReviewGate 装饰器,它会包住每一个 Class 为 "write" 或 "destructive" 的工具。
reviewer 回答的是这个问题:"谁来监督监督者?"
别把它关掉。
正是 reviewer 让 Ongrid 敢把 host_restart_service 或 execute_skill 这种 东西放出来给你用。跳过这道门是一种明确的配置选择,在审计链路里会暴露。 你要是 fork 出一个"永远批准"的 persona,审计日志 里每一次决策都会留痕 —— 审计员一眼就看到。
ReviewGate 装饰器
在 tool 装饰器链里的位置 (chain.go):
tenant_bind → REVIEW_GATE → timeout → audit → ratelimit → metric → <inner tool>为什么是这个顺序:
- 在
timeout外面。 reviewer 本身是一次带自己 turn 预算的graph.Invoke。把它包到内层 tool 的 15s 超时里,就强迫 reviewer 15s 干完 —— 不现实。这道门有自己独立的 60s 上限 (DefaultReviewerTimeout)。 - 在
audit外面。 拒绝时不应该写一条假的执行行。这道门写的是一行chat_mutating_proposals。audit 只记录内层 tool 的执行,而执行只在 批准时才发生。 - 在
tenant_bind里面。 proposal 载荷包含操作者的user_id,由tenant_bind从 ctx 里解出来 —— 所以这道门必须晚于tenant_bind跑。
触发条件
装饰器检查被包工具的 Class:
| Class | 门的行为 |
|---|---|
"read" | 放行。内层 tool 直接跑。 |
"write" | 拦截。派出 reviewer。只有批准时内层才跑。 |
"destructive" | 同 "write"。两种 class 都走这道门。 |
今天工具包里具体的 Class: "write" 工具:
host_restart_service—— 经典的改动型 skill。execute_skill—— 跑任何 marketplace 安装的 skill 的 shim(这道门看不到 skill 正文做分类,索性把它们全拦下来)。AgentTool—— 对,派发原语本身就是"write",因为它会派出一个 worker 并因此提交资源。不过实际上 reviewer 很少拦 AgentTool 调用 —— 看下面 persona 的允许列表。
proposal 载荷
拦截时,门会构造:
{
"action": "host_restart_service",
"target": {"device_id": 7, "service": "nginx"},
"reason": "User reported 502; nginx error log shows OOM",
"blast_radius": "single_device",
"operator": "user_42",
"context_summary": "<coordinator's running summary of the conversation>"
}blast_radius 是 single_device | cluster | tenant_wide 三选一,从工具 metadata 推出来。reviewer 用它给"这事合理吗"的决定加权 —— tenant_wide 的操作面临的审查比 single_device 严。
reviewer persona
frontmatter 重点(原文摘自 agents/reviewer.md):
name: reviewer
description: SOP 二审 reviewer worker,对 mutating / destructive 提案做静态审查
permission_mode: read-only
max_turns: 5
model: anthropic/claude-opus-4-7 # 关键路径用最强
background: true # async: spawn returns immediately
tools:
- get_incident_detail
- get_edge_summary
- query_promql
- query_logql
- get_sop_text # planned (HLD-003 PR-D)
disallowed_tools:
- "*_skill" # wildcard — no skill execution
- run_shell
- execute_skill
- host_restart_service # explicitly: reviewer cannot itself act
- kill_process
critical_reminder: |
你是高危操作二审 reviewer。reject 是默认选项,approve 必须三条都满足:
1. 找得到对应 SOP 且明确覆盖此场景
2. 当前没有并行的同类操作(看告警 / 看运维窗口)
3. 回滚路径已知要点:
max_turns: 5。 reviewer 不绕弯子。5 轮内拍板;persona prompt 里明说 "看不清就 reject"。model: anthropic/claude-opus-4-7。 关键路径用最强的模型,不管组织 默认是什么。review 不该被降级到便宜模型 —— 一次错批的代价远高于一次慢审。background: true。 reviewer 异步跑。coordinator 的 UI 不会阻塞在 review 上;review 完了通过task_notification回推。(目前的接线下,内层 tool 会一直阻塞到 reviewer 返回 —— 但 UI 不阻塞,所以用户看到 "review pending" 在跑。)disallowed_tools通配符*_skill。 reviewer 不能执行任何 skill。它 可以读上下文(get_incident_detail、get_edge_summary、遥测查询),但 绝不动手。- 没有
run_shell、没有host_restart_service、没有kill_process。 即便 persona 正文叫它做,运行时过滤器也会把这些 tool 剥掉。纵深防御。
5 步工作流
按照 persona 正文,reviewer 跑的步骤是:
- 找 SOP。
get_sop_text(action)拿到官方流程。没 SOP → reject, reason 是"no SOP for action <X>"。 - 看目标状态。
get_edge_summary(device_id)。设备离线 / 正在重启循环 / 5 分钟内有过上一次改动 → reject。 - 看有没有并行操作。
query_logql查最近 10 分钟里碰过同一目标的audit:条目。有并行动作 → reject,reason 是"parallel operation detected; wait for X to complete"。 - 核对 reason 跟告警。
get_incident_detail拿到引用的 incident。reason 跟告警内容矛盾 → reject("operator may have misread the symptom")。 - 做决定。
- Approve 当三个核心 gate 全过。输出
{decision: approve, sop_id, rollback_path, gates_passed}。 - Reject 其他情况。输出
{decision: reject, reason, missing_gates}。
- Approve 当三个核心 gate 全过。输出
三条全通过规则
critical reminder 写得很明确:
reject 是默认选项,approve 必须三条都满足:
- 找得到对应 SOP 且明确覆盖此场景
- 当前没有并行的同类操作(看告警 / 看运维窗口)
- 回滚路径已知
"不确定"等于"reject"。这是有意校准的 —— 错批(生产损失)跟错拒(多跑一趟)的 代价不对称,强烈偏向后者。
输出格式
reviewer 的最终回复(通过 <task-notification> 回传给 coordinator):
**Decision: approve | reject**
**Gates**
- ✓ SOP-007 covers restart nginx
- ✓ node-01 status online; last mutating 17min ago
- ✓ No parallel operation
- ✓ Rollback: `systemctl start nginx`
**Notes**
{1-2 sentence risk note; included even on approve.}装饰器查 Decision: approve(大小写不敏感,必须出现在回复的第一个非空 行)。其他任何写法 —— 包括 "approve with caveats" 这种模糊表述 —— 都按 reject 处理。
approve 时发生什么
装饰器:
- 写一行
chat_mutating_proposals,status=approved,reviewer 的决定文本, SOP id。 - 让调用落到内层 tool。
- audit 装饰器(链下游)写执行行。
- 内层 tool 返回正常结果给 coordinator LLM。
coordinator LLM 看到的就是一次正常的 tool 结果,继续走。
reject 时发生什么
装饰器:
- 写一行
chat_mutating_proposals,status=rejected,reviewer 的理由, missing gates。 - 返回
ErrReviewRejected包着 reviewer 的理由。 - audit 装饰器不写执行行(什么都没执行)。
coordinator LLM 看到一条错误 —— review rejected: <reviewer reason> —— 然后 应该把情况解释给用户。它不应该重试同一个 tool 调用;去重保护加上对拒绝理由 的短期 LRU 让 LLM 不会进死循环。
边界情况
操作者就是管理员怎么办?
reviewer 照样跑。没有"你是 admin 所以跳过 review"的开关。可审计性才是 重点 —— reviewer 的决定行就是"这个操作在 SOP-X 下被批准、回滚是 Y、时间是 T"的纸面证据。Admin 越权会抹掉这条证据。
没有 get_sop_text SOP 怎么办?
今天 SOP 语料还稀疏 —— get_sop_text 是计划中的 tool(HLD-003 PR-D)。 当前 MVP 里 reviewer 用占位实现,回退到一个启发式:生产标签的设备上的关键 操作需要 proposal 上下文显式带 force_approve_no_sop: true,而 coordinator 只能在用户显式确认后才设。等 SOP 语料填充好了就替换掉。
reviewer 超时怎么办?
60s 的门超时触发。装饰器返回 ErrReviewTimeout。coordinator LLM 看到一条 超时错误,应该把情况报告给用户 —— 不要重试。reviewer 反复超时是 manager 侧 问题(多半是模型延迟);审计日志能让 SRE 看出这个模式。
worker 的只读工具调用呢?
worker 的只读 tool 调用(query_promql、get_host_load 等)Class 是 "read",永远不会触发这道门。只有 "write" / "destructive" 工具会。 reviewer 自己只带 Class: "read" 工具,所以 reviewer worker 自己不会触发 嵌套 ReviewGate。
自定义这道门
在 fork 里可以改的东西:
- reviewer 模型。 如果你的组织没有 Anthropic 访问权限,把 frontmatter 的
model:改成你拥有的最强模型。这道门用 persona 声明的那个。 - 60s 上限。 通过
ReviewGate构造函数选项覆盖,如果你的 reviewer 经常 跑更久。别压到 30s 以下;reviewer 需要 5 turn × LLM + tool 往返的空间。 - reviewer persona 正文。 加团队特有的清单(PCI 范围、change-freeze 窗口、 值班日历)。这道门不关心正文里写什么,只要最后那行决定可解析就行。
绝不能改的东西:
name: reviewer—— 这道门按名字查 (DefaultReviewerAgent)。disallowed_tools里的通配符*_skill—— reviewer 自己绝不能动手; 纵深防御要求运行时过滤器强制这点,而不是靠 prompt。
按工具定制 reviewer(比如一个 db 专属的),装饰器构造函数收 reviewerAgent override,让不同的 tool 路由到不同的 reviewer persona。少用 —— 多个 reviewer 会把审计链路打碎。