Reviewer
reviewer é a persona que assina em segundo toda operação mutadora. Diferente dos outros workers ela nunca é spawned pelo coordinator — o catálogo deliberadamente a exclui. O único spawner é o decorator ReviewGate, que envelopa toda tool cuja Class seja "write" ou "destructive".
O reviewer é a resposta à pergunta: "quem vigia o vigia?"
Não desabilite isso.
O reviewer é o que torna o Ongrid seguro para dar host_restart_service ou execute_skill. Pular o gate é uma escolha de configuração explícita e aparece no audit trail. Se você forkar a persona para "sempre aprovar", o audit log vai mostrar cada decisão — auditores vão notar.
O decorator ReviewGate
Posição na cadeia de decorators de tool (chain.go):
tenant_bind → REVIEW_GATE → timeout → audit → ratelimit → metric → <inner tool>Por que exatamente nessa ordem:
- Fora de
timeout. O reviewer é em si umgraph.Invokecom seu próprio budget de turns. Envolvê-lo dentro do timeout de 15s da tool interna forçaria o reviewer a terminar em 15s — irrealista. O gate carrega seu próprio teto independente de 60s (DefaultReviewerTimeout). - Fora de
audit. Rejeições não devem escrever uma linha de execução sintética. O gate escreve uma linha emchat_mutating_proposalsem vez disso. Audit só loga a execução da tool interna, que só ocorre em approve. - Dentro de
tenant_bind. O payload da proposta inclui ouser_iddo operador, quetenant_bindresolve a partir do ctx — o gate precisa rodar depois quetenant_bindpopulou.
Condição de trigger
O decorator inspeciona a Class da tool envolvida:
| Class | Comportamento do gate |
|---|---|
"read" | Passa direto. Tool interna roda. |
"write" | Intercepta. Dá spawn no reviewer. Interna só roda em approve. |
"destructive" | Igual a "write". Ambas as classes passam pelo gate. |
Tools concretas Class: "write" na bag hoje:
host_restart_service— a skill mutadora canônica.execute_skill— o shim do marketplace que roda qualquer skill instalada via marketplace (o gate não consegue ler o corpo da skill para classificar, então ele faz gate em todas).AgentTool— sim, a primitiva de dispatch em si é"write"porque dá spawn em um worker e portanto compromete recursos. Na prática o reviewer raramente faz gate em chamadas AgentTool — veja a allowlist da persona abaixo.
O payload da proposta
Ao interceptar, o gate monta:
{
"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 é um de single_device | cluster | tenant_wide, derivado dos metadados da tool. O reviewer usa para ponderar a decisão "isto é razoável" — uma ação tenant_wide enfrenta escrutínio mais alto que uma single_device.
A persona reviewer
Destaques do frontmatter (verbatim de 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. 回滚路径已知Observações-chave:
max_turns: 5. Reviewers não rodeiam o assunto. Cinco turns para decidir; o prompt da persona diz explicitamente "看不清就 reject".model: anthropic/claude-opus-4-7. A chamada de caminho crítico recebe o modelo mais forte, independentemente do padrão da org. Reviews não devem ser rebaixadas para um modelo rápido — o custo de uma aprovação errada supera de longe o custo de uma revisão lenta.background: true. Reviewer roda async. A UI do coordinator não bloqueia na revisão; otask_notificationda revisão posta de volta quando termina. (No wiring atual, a tool interna bloqueia até o reviewer retornar — mas a UI não, então o usuário vê "review pending" enquanto roda.)- Wildcard
*_skillemdisallowed_tools. O reviewer não pode executar nenhuma skill. Pode ler contexto (get_incident_detail,get_edge_summary, queries de telemetria) mas nunca agir. - Sem
run_shell, semhost_restart_service, semkill_process. Mesmo que o corpo da persona dissesse para chamar, o filtro do runtime remove essas tools. Defense in depth.
O workflow de 5 passos
Do corpo da persona, o reviewer roda:
- Encontrar o SOP.
get_sop_text(action)para o procedimento oficial. Sem SOP → reject com"no SOP for action <X>". - Checar estado do alvo.
get_edge_summary(device_id). Device offline / em loop de restart / última ação mutadora há menos de 5min → reject. - Checar operações paralelas.
query_logqlpor entradasaudit:tocando o mesmo alvo nos últimos 10 minutos. Ação paralela → reject com"parallel operation detected; wait for X to complete". - Validar a razão contra os alertas.
get_incident_detailpara o incidente referenciado. Razão contradiz o conteúdo do alerta → reject ("operador pode ter lido errado o sintoma"). - Decidir.
- Approve quando todos os três gates centrais passam. Saída
{decision: approve, sop_id, rollback_path, gates_passed}. - Reject caso contrário. Saída
{decision: reject, reason, missing_gates}.
- Approve quando todos os três gates centrais passam. Saída
A regra three-of-three
O critical reminder é explícito:
reject 是默认选项,approve 必须三条都满足:
- 找得到对应 SOP 且明确覆盖此场景
- 当前没有并行的同类操作(看告警 / 看运维窗口)
- 回滚路径已知
"Não sei" equivale a "reject". É uma calibração deliberada — a assimetria de custo entre uma aprovação errada (dano em produção) e uma rejeição errada (um round-trip extra) favorece pesadamente a segunda.
Formato de saída
A resposta final do reviewer (postada de volta ao coordinator via <task-notification>):
**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.}O decorator parseia por Decision: approve (case-insensitive, precisa aparecer na primeira linha não-vazia da resposta). Qualquer outra coisa — incluindo frases ambíguas como "approve with caveats" — é tratada como reject.
O que acontece em approve
O decorator:
- Escreve uma linha em
chat_mutating_proposalscom status=approved, o texto da decisão do reviewer, e o SOP id. - Deixa a chamada cair na tool interna.
- O decorator de audit (downstream na cadeia) escreve a linha de execução.
- A tool interna retorna seu resultado normal ao LLM do coordinator.
O LLM do coordinator vê um resultado normal de tool e prossegue.
O que acontece em reject
O decorator:
- Escreve uma linha em
chat_mutating_proposalscom status=rejected, a razão do reviewer, e os gates faltantes. - Retorna
ErrReviewRejectedenvolvendo a razão do reviewer. - O decorator de audit não escreve uma linha de execução (nenhuma execução aconteceu).
O LLM do coordinator vê uma mensagem de erro — review rejected: <reviewer reason> — e deve explicar a situação ao usuário. Ele não deve tentar a mesma chamada de tool de novo; a proteção de dedupe mais um LRU de curto prazo nas razões de rejeição evita que o LLM faça loop.
Casos extremos
E se o usuário é o operador e o admin?
Reviewer roda assim mesmo. Não existe flag "pula a review porque você é admin". Auditabilidade é o ponto — a linha de decisão do reviewer é o paper trail que diz "essa ação foi aprovada contra o SOP-X com rollback Y no instante T". Override de admin apagaria o trail.
E se não existe SOP get_sop_text?
Hoje o corpus de SOP é esparso — get_sop_text é uma tool planejada (HLD-003 PR-D). No MVP atual o reviewer usa um placeholder e cai em uma heurística: ações críticas em devices marcados como produção exigem force_approve_no_sop: true explícito no contexto da proposta, que o coordinator só pode setar após confirmação explícita do usuário. Isso vai ser substituído pelo corpus de SOP assim que for populado.
E se o reviewer der timeout?
O timeout de 60s do gate dispara. O decorator retorna ErrReviewTimeout. O LLM do coordinator vê um erro de timeout e deve reportar ao usuário — não tentar de novo. Timeouts persistentes de reviewer são problema do lado do manager (provavelmente latência de modelo); o audit log permite ao SRE ver o padrão.
E sobre tools read-only de worker?
Chamadas read-only de worker (query_promql, get_host_load, etc.) têm Class: "read" e nunca disparam o gate. Só tools "write" / "destructive" disparam. O reviewer em si só carrega tools Class: "read", então um worker reviewer não pode disparar um ReviewGate aninhado.
Customizando o gate
Coisas que você pode mudar num fork:
- O modelo do reviewer. Se sua org não tem acesso ao Anthropic, reescreva o
model:no frontmatter para o modelo mais forte que você tem. O gate usa o que a persona declarar. - O teto de 60s. Sobrescreva via opção do construtor
ReviewGatese seu reviewer regularmente leva mais. Não baixe abaixo de 30s; o reviewer precisa de espaço para 5 turns × LLM + round-trips de tool. - O corpo da persona reviewer. Adicione checklists específicas de equipe (escopo PCI, janelas de change-freeze, calendário de on-call). O gate não se importa com o que está no corpo desde que a linha final de decisão seja parseável.
Coisas que você não deve mudar:
- O
name: reviewer— o gate o localiza por nome (DefaultReviewerAgent). - O wildcard
*_skillemdisallowed_tools— o reviewer não pode ele próprio realizar ações; defense in depth diz que o filtro de runtime impõe isto, não o prompt.
Para um reviewer custom por tool (ex.: um específico para db), o construtor do decorator aceita um override reviewerAgent de modo que tools diferentes possam rotear para personas reviewer diferentes. Use com parcimônia — múltiplos reviewers fragmentam o audit trail.