Skip to content

Reviewer

reviewer es la persona que segunda-firma cada operación mutadora. A diferencia de los otros workers nunca la lanza el coordinator — el catálogo la excluye deliberadamente. El único spawner es el decorator ReviewGate, que envuelve cada tool cuya Class es "write" o "destructive".

El reviewer es la respuesta a una pregunta: "¿quién vigila al vigilante?"

No deshabilites esto.

El reviewer es lo que hace seguro darle a Ongrid host_restart_service o execute_skill siquiera. Saltarse el gate es una elección explícita de configuración y aparece en la pista de auditoría. Si forkeas la persona a "siempre aprueba", el audit log mostrará cada decisión — los auditores lo notarán.

El decorator ReviewGate

Posición en la cadena de decorators de tools (chain.go):

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

Por qué este orden exacto:

  • Fuera de timeout. El reviewer es por sí mismo un graph.Invoke con su propio presupuesto de turnos. Envolverlo dentro del timeout de 15s del tool interior obligaría al reviewer a terminar en 15s — irrealista. El gate lleva su propio techo independiente de 60s (DefaultReviewerTimeout).
  • Fuera de audit. Los rechazos no deberían escribir una fila de ejecución sintética. El gate escribe una fila chat_mutating_proposals en su lugar. El audit solo loguea la ejecución del tool interior, que solo ocurre en approve.
  • Dentro de tenant_bind. El payload de propuesta incluye el user_id del operador, que tenant_bind resuelve desde ctx — el gate debe correr después de que tenant_bind lo haya poblado.

Condición de trigger

El decorator inspecciona la Class del tool envuelto:

ClassComportamiento del gate
"read"Pasa. El tool interior corre.
"write"Intercepta. Lanza reviewer. El interior corre solo en approve.
"destructive"Igual que "write". Ambas clases pasan por el gate.

Tools concretos Class: "write" en el bag hoy:

  • host_restart_service — el skill mutador canónico.
  • execute_skill — el shim del marketplace que ejecuta cualquier skill instalado desde el marketplace (el gate no puede leer el cuerpo del skill para clasificar, así que los gatea todos).
  • AgentTool — sí, la propia primitiva de dispatch es "write" porque lanza un worker y por lo tanto compromete recursos. El reviewer raramente gatea llamadas a AgentTool en la práctica — ver la allowlist de la persona abajo.

El payload de propuesta

Cuando intercepta, el gate construye:

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 es uno de single_device | cluster | tenant_wide, derivado de la metadata del tool. El reviewer lo usa para pesar la decisión "es esto razonable" — una acción tenant_wide enfrenta más escrutinio que una single_device.

La persona reviewer

Highlights del 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. 回滚路径已知

Observaciones clave:

  • max_turns: 5. Los reviewers no andan con rodeos. Cinco turnos para decidir; el prompt de la persona dice explícitamente "看不清就 reject".
  • model: anthropic/claude-opus-4-7. La llamada de ruta crítica obtiene el modelo más fuerte, sin importar el default de la organización. Las reviews no deberían degradarse a un modelo rápido — el costo de un approve equivocado supera con creces el costo de una review lenta.
  • background: true. El reviewer corre async. La UI del coordinator no bloquea por la review; el task_notification de la review postea de vuelta cuando termina. (En el cableado actual, el tool interior bloquea hasta que el reviewer devuelve — pero la UI no, así que el usuario ve "review pending" mientras corre.)
  • Wildcard disallowed_tools *_skill. El reviewer no puede ejecutar ningún skill. Puede leer contexto (get_incident_detail, get_edge_summary, queries de telemetría) pero nunca actuar.
  • Sin run_shell, sin host_restart_service, sin kill_process. Incluso si el cuerpo de la persona se lo dijera, el filtro del runtime elimina esas tools. Defensa en profundidad.

El workflow de 5 pasos

Del cuerpo de la persona, el reviewer corre:

  1. Encuentra el SOP. get_sop_text(action) por el procedimiento oficial. Sin SOP → reject con "no SOP for action <X>".
  2. Verifica el estado del target. get_edge_summary(device_id). Device offline / en restart loop / última acción mutadora en los últimos 5min → reject.
  3. Verifica operaciones paralelas. query_logql para entradas audit: tocando el mismo target en los últimos 10 minutos. Acción paralela → reject con "parallel operation detected; wait for X to complete".
  4. Valida la razón contra alertas. get_incident_detail para el incidente referenciado. La razón contradice el contenido de la alerta → reject ("el operador puede haber leído mal el síntoma").
  5. Decide.
    • Approve cuando los tres gates principales pasan. Output {decision: approve, sop_id, rollback_path, gates_passed}.
    • Reject en caso contrario. Output {decision: reject, reason, missing_gates}.

La regla tres-de-tres

El critical reminder es explícito:

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

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

"No estoy seguro" equivale a "reject". Esta es una calibración deliberada — la asimetría de costo entre un approve equivocado (daño en producción) y un reject equivocado (un round-trip extra) favorece fuertemente a este último.

Formato de salida

La respuesta final del reviewer (posteada de vuelta al coordinator vía <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.}

El decorator parsea por Decision: approve (case-insensitive, debe aparecer en la primera línea no en blanco de la respuesta). Cualquier otra cosa — incluyendo redacciones ambiguas como "approve con caveats" — se trata como reject.

Qué pasa en approve

El decorator:

  1. Escribe una fila chat_mutating_proposals con status=approved, el texto de decisión del reviewer y el id de SOP.
  2. Deja que la llamada caiga al tool interior.
  3. El decorator de audit (downstream en la cadena) escribe la fila de ejecución.
  4. El tool interior devuelve su resultado normal al LLM del coordinator.

El LLM del coordinator ve un resultado de tool normal y procede.

Qué pasa en reject

El decorator:

  1. Escribe una fila chat_mutating_proposals con status=rejected, la razón del reviewer y los gates faltantes.
  2. Devuelve ErrReviewRejected envolviendo la razón del reviewer.
  3. El decorator de audit no escribe una fila de ejecución (no ocurrió ninguna ejecución).

El LLM del coordinator ve un mensaje de error — review rejected: <reviewer reason> — y se espera que explique la situación al usuario. No debería reintentar la misma llamada de tool; la protección de dedupe más un LRU de corto plazo sobre razones de rechazo mantienen al LLM fuera del loop.

Edge cases

¿Y si el usuario es el operador y el admin?

El reviewer sigue corriendo. No hay flag "skip review porque soy admin". La auditabilidad es el punto — la fila de decisión del reviewer es la pista en papel que dice "esta acción fue aprobada contra SOP-X con rollback Y en el tiempo T". Un override de admin borraría la pista.

¿Y si no existe SOP de get_sop_text?

Hoy el corpus de SOPs es escaso — get_sop_text es un tool planeado (HLD-003 PR-D). En el MVP actual el reviewer usa un placeholder y cae en una heurística: acciones críticas en devices con tag de producción necesitan force_approve_no_sop: true explícito en el contexto de propuesta, que el coordinator solo puede setear tras confirmación explícita del usuario. Esto será reemplazado por el corpus de SOPs cuando se pueble.

¿Y si el reviewer hace timeout?

Dispara el timeout de gate de 60s. El decorator devuelve ErrReviewTimeout. El LLM del coordinator ve un error de timeout y debería reportárselo al usuario — no reintentar. Los timeouts persistentes del reviewer son un problema del lado del manager (probablemente latencia del modelo); el audit log deja que el SRE vea el patrón.

¿Y las llamadas read-only del worker?

Las llamadas de tool read-only de los workers (query_promql, get_host_load, etc.) tienen Class: "read" y nunca disparan el gate. Solo los tools "write" / "destructive" lo hacen. El reviewer mismo solo lleva tools Class: "read", así que un worker reviewer no puede disparar un ReviewGate anidado.

Customizando el gate

Cosas que podrías cambiar en un fork:

  • El modelo del reviewer. Si tu organización no tiene acceso a Anthropic, reescribe el model: del frontmatter al modelo más fuerte que sí tengas. El gate usa lo que declare la persona.
  • El techo de 60s. Sobrescribe vía la opción de constructor de ReviewGate si tu reviewer regularmente toma más. No bajes de 30s; el reviewer necesita espacio para 5 turnos × LLM + round-trips de tools.
  • El cuerpo de la persona reviewer. Añade checklists específicos del equipo (scope PCI, ventanas de freeze de cambios, calendario de on-call). Al gate no le importa qué hay en el cuerpo siempre que la línea final de decisión sea parseable.

Cosas que no debes cambiar:

  • El name: reviewer — el gate lo busca por nombre (DefaultReviewerAgent).
  • El wildcard disallowed_tools *_skill — el reviewer no debe realizar acciones por sí mismo; la defensa en profundidad dice que el filtro del runtime lo refuerza, no el prompt.

Para un reviewer custom por-tool (p. ej. uno específico de db), el constructor del decorator acepta un override reviewerAgent para que distintos tools puedan rutear a distintas personas reviewer. Úsalo con moderación — múltiples reviewers fragmentan la pista de auditoría.