Skip to content

Reviewer

reviewer est la persona qui contresigne chaque opération mutante. Contrairement aux autres workers, elle n'est jamais spawnée par le coordinator — le catalogue l'exclut délibérément. Le seul spawner est le décorateur ReviewGate, qui enveloppe chaque outil dont la Class est "write" ou "destructive".

Le reviewer est la réponse à une question : « qui surveille le surveillant ? »

Ne désactivez pas ceci.

Le reviewer est ce qui rend Ongrid sûr pour donner host_restart_service ou execute_skill du tout. Sauter le gate est un choix de configuration explicite qui apparaît dans la piste d'audit. Si vous forkez la persona pour « toujours approuver », l'audit log montrera chaque décision — les auditeurs le remarqueront.

Le décorateur ReviewGate

Position dans la chaîne de décorateurs d'outils (chain.go) :

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

Pourquoi exactement cet ordre :

  • À l'extérieur de timeout. Le reviewer est lui-même un graph.Invoke avec son propre budget de tours. L'envelopper à l'intérieur du timeout de 15s de l'outil interne forcerait le reviewer à finir en 15s — irréaliste. Le gate porte son propre plafond indépendant de 60s (DefaultReviewerTimeout).
  • À l'extérieur d'audit. Les rejets ne devraient pas écrire une ligne d'exécution synthétique. Le gate écrit une ligne chat_mutating_proposals à la place. L'audit ne logge que l'exécution de l'outil interne, ce qui n'arrive que sur approve.
  • À l'intérieur de tenant_bind. Le payload de proposition inclut l'opérateur user_id, que tenant_bind résout depuis le ctx — le gate doit tourner après que tenant_bind l'ait peuplé.

Condition de déclenchement

Le décorateur inspecte la Class de l'outil enveloppé :

ClassComportement du gate
"read"Passe outre. L'outil interne tourne.
"write"Intercepte. Spawn reviewer. L'interne ne tourne que sur approve.
"destructive"Idem que "write". Les deux classes passent par le gate.

Outils concrets de Class: "write" dans le sac aujourd'hui :

  • host_restart_service — le skill mutant canonique.
  • execute_skill — le shim marketplace qui exécute n'importe quel skill installé via marketplace (le gate ne peut pas lire le corps du skill pour le classer, donc il les gate tous).
  • AgentTool — oui, la primitive de dispatch elle-même est "write" parce qu'elle spawne un worker et engage donc des ressources. Le reviewer gate rarement les appels AgentTool en pratique cependant — voir la whitelist de la persona ci-dessous.

Le payload de proposition

À l'interception, le gate construit :

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 est l'un de single_device | cluster | tenant_wide, dérivé des métadonnées de l'outil. Le reviewer s'en sert pour pondérer la décision « est-ce raisonnable » — une action tenant_wide fait face à plus de scrutin qu'une action single_device.

La persona reviewer

Points forts du 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. 回滚路径已知

Observations clés :

  • max_turns: 5. Les reviewers ne tournent pas autour du pot. Cinq tours pour décider ; le prompt de la persona dit explicitement « 看不清就 reject ».
  • model: anthropic/claude-opus-4-7. L'appel sur le chemin critique reçoit le modèle le plus fort, indépendamment du défaut de l'organisation. Les revues ne devraient pas être dégradées vers un modèle rapide — le coût d'une approbation erronée pèse largement plus que celui d'une revue lente.
  • background: true. Le reviewer tourne en async. L'UI du coordinator ne bloque pas sur la revue ; le task_notification de la revue poste en retour quand c'est fini. (Dans le câblage actuel, l'outil interne bloque jusqu'à ce que le reviewer revienne — mais pas l'UI, donc l'utilisateur voit « review pending » pendant qu'il tourne.)
  • Wildcard *_skill dans disallowed_tools. Le reviewer ne peut pas exécuter de skill. Il peut lire le contexte (get_incident_detail, get_edge_summary, requêtes de télémétrie) mais jamais agir.
  • Pas de run_shell, pas de host_restart_service, pas de kill_process. Même si le corps de la persona lui disait de le faire, le filtre du runtime retire ces outils. Défense en profondeur.

Le workflow en 5 étapes

Tiré du corps de la persona, le reviewer déroule :

  1. Trouver le SOP. get_sop_text(action) pour la procédure officielle. Pas de SOP → reject avec "no SOP for action <X>".
  2. Vérifier l'état cible. get_edge_summary(device_id). Device offline / dans une boucle de redémarrage / dernière action mutante dans les 5 dernières min → reject.
  3. Vérifier les opérations parallèles. query_logql pour les entrées audit: touchant la même cible dans les 10 dernières minutes. Action parallèle → reject avec "parallel operation detected; wait for X to complete".
  4. Valider la raison contre les alertes. get_incident_detail pour l'incident référencé. Raison contredit le contenu de l'alerte → reject (« l'opérateur a peut-être mal interprété le symptôme »).
  5. Décider.
    • Approve quand les trois gates principaux passent. Sortie {decision: approve, sop_id, rollback_path, gates_passed}.
    • Reject sinon. Sortie {decision: reject, reason, missing_gates}.

La règle des trois sur trois

Le rappel critique est explicite :

reject est l'option par défaut, approve nécessite que les trois conditions soient satisfaites :

  1. Un SOP correspondant existe et couvre clairement ce scénario
  2. Pas d'opération parallèle du même type en cours (regarder alertes / fenêtre d'ops)
  3. Le chemin de rollback est connu

« Pas sûr » équivaut à « reject ». C'est une calibration délibérée — l'asymétrie de coût entre une approbation erronée (dégâts en production) et un rejet erroné (un aller-retour de plus) favorise lourdement le second.

Format de sortie

La réponse finale du reviewer (postée au 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.}

Le décorateur parse pour Decision: approve (insensible à la casse, doit apparaître à la première ligne non vide de la réponse). Tout autre — y compris du phrasé ambigu comme « approve with caveats » — est traité comme reject.

Ce qui se passe sur approve

Le décorateur :

  1. Écrit une ligne chat_mutating_proposals avec status=approved, le texte de décision du reviewer, et l'id de SOP.
  2. Laisse l'appel retomber vers l'outil interne.
  3. Le décorateur d'audit (en aval dans la chaîne) écrit la ligne d'exécution.
  4. L'outil interne renvoie son résultat normal au LLM du coordinator.

Le LLM du coordinator voit un résultat d'outil normal et procède.

Ce qui se passe sur reject

Le décorateur :

  1. Écrit une ligne chat_mutating_proposals avec status=rejected, la raison du reviewer, et les gates manquants.
  2. Renvoie ErrReviewRejected enveloppant la raison du reviewer.
  3. Le décorateur d'audit n'écrit pas de ligne d'exécution (aucune exécution n'a eu lieu).

Le LLM du coordinator voit un message d'erreur — review rejected: <reviewer reason> — et est censé expliquer la situation à l'utilisateur. Il ne doit pas retenter le même appel d'outil ; la protection de dédoublonnage plus un LRU à court terme sur les raisons de rejet empêchent le LLM de boucler.

Cas limites

Et si l'utilisateur est l'opérateur ET admin ?

Le reviewer tourne quand même. Il n'y a pas de flag « skip review parce que tu es admin ». L'auditabilité est le sujet — la ligne de décision du reviewer est la trace papier qui dit « cette action a été approuvée contre SOP-X avec rollback Y au temps T ». Un override admin effacerait la trace.

Et s'il n'existe pas de SOP get_sop_text ?

Aujourd'hui le corpus SOP est clairsemé — get_sop_text est un outil planifié (HLD-003 PR-D). Dans le MVP actuel, le reviewer utilise un placeholder et retombe sur une heuristique : les actions critiques sur des devices tagués production nécessitent un force_approve_no_sop: true explicite dans le contexte de la proposition, que le coordinator ne peut poser qu'après confirmation utilisateur explicite. Ceci sera remplacé par le corpus SOP une fois celui-ci peuplé.

Et si le reviewer dépasse le timeout ?

Le timeout de 60s du gate se déclenche. Le décorateur renvoie ErrReviewTimeout. Le LLM du coordinator voit une erreur de timeout et doit la rapporter à l'utilisateur — pas réessayer. Des timeouts persistants du reviewer sont un problème côté manager (probablement latence du modèle) ; le log d'audit laisse le SRE voir le pattern.

Et concernant les outils en lecture seule des workers ?

Les appels d'outils en lecture seule des workers (query_promql, get_host_load, etc.) ont Class: "read" et ne déclenchent jamais le gate. Seuls les outils "write" / "destructive" le font. Le reviewer lui-même ne porte que des outils Class: "read", donc un worker reviewer ne peut pas lui-même déclencher un ReviewGate imbriqué.

Personnaliser le gate

Ce que vous pourriez changer dans un fork :

  • Le modèle du reviewer. Si votre organisation n'a pas accès à Anthropic, réécrivez le model: du frontmatter vers le modèle le plus fort dont vous disposez. Le gate utilise ce que la persona déclare.
  • Le plafond de 60s. Override via une option du constructeur ReviewGate si votre reviewer prend régulièrement plus longtemps. Ne descendez pas sous 30s ; le reviewer a besoin de marge pour 5 tours × LLM + aller-retours d'outils.
  • Le corps de la persona reviewer. Ajoutez des checklists spécifiques à l'équipe (scope PCI, fenêtres de gel de changement, calendrier on-call). Le gate ne se soucie pas de ce qu'il y a dans le corps tant que la ligne de décision finale est parseable.

Ce que vous ne devez pas changer :

  • Le name: reviewer — le gate le cherche par nom (DefaultReviewerAgent).
  • Le wildcard *_skill dans disallowed_tools — le reviewer ne doit pas lui-même effectuer d'actions ; la défense en profondeur dit que le filtre du runtime l'impose, pas le prompt.

Pour un reviewer personnalisé par outil (par ex. un spécifique à la DB), le constructeur du décorateur accepte un override reviewerAgent pour que différents outils puissent router vers différentes personas reviewer. Utilisez-le avec parcimonie — plusieurs reviewers fragmentent la piste d'audit.