Skip to content

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):

text
tenant_bind → REVIEW_GATE → timeout → audit → ratelimit → metric → <inner tool>

Por que exatamente nessa ordem:

  • Fora de timeout. O reviewer é em si um graph.Invoke com 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 em chat_mutating_proposals em 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 o user_id do operador, que tenant_bind resolve a partir do ctx — o gate precisa rodar depois que tenant_bind populou.

Condição de trigger

O decorator inspeciona a Class da tool envolvida:

ClassComportamento 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:

json
{
  "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):

yaml
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; o task_notification da 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 *_skill em disallowed_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, sem host_restart_service, sem kill_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:

  1. Encontrar o SOP. get_sop_text(action) para o procedimento oficial. Sem SOP → reject com "no SOP for action <X>".
  2. Checar estado do alvo. get_edge_summary(device_id). Device offline / em loop de restart / última ação mutadora há menos de 5min → reject.
  3. Checar operações paralelas. query_logql por entradas audit: tocando o mesmo alvo nos últimos 10 minutos. Ação paralela → reject com "parallel operation detected; wait for X to complete".
  4. Validar a razão contra os alertas. get_incident_detail para o incidente referenciado. Razão contradiz o conteúdo do alerta → reject ("operador pode ter lido errado o sintoma").
  5. 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}.

A regra three-of-three

O critical reminder é explícito:

reject 是默认选项,approve 必须三条都满足:

  1. 找得到对应 SOP 且明确覆盖此场景
  2. 当前没有并行的同类操作(看告警 / 看运维窗口)
  3. 回滚路径已知

"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>):

markdown
**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:

  1. Escreve uma linha em chat_mutating_proposals com status=approved, o texto da decisão do reviewer, e o SOP id.
  2. Deixa a chamada cair na tool interna.
  3. O decorator de audit (downstream na cadeia) escreve a linha de execução.
  4. 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:

  1. Escreve uma linha em chat_mutating_proposals com status=rejected, a razão do reviewer, e os gates faltantes.
  2. Retorna ErrReviewRejected envolvendo a razão do reviewer.
  3. 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 ReviewGate se 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 *_skill em disallowed_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.