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) :
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 ungraph.Invokeavec 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 lignechat_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érateuruser_id, quetenant_bindrésout depuis le ctx — le gate doit tourner après quetenant_bindl'ait peuplé.
Condition de déclenchement
Le décorateur inspecte la Class de l'outil enveloppé :
| Class | Comportement 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 :
{
"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) :
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 ; letask_notificationde 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
*_skilldansdisallowed_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 dehost_restart_service, pas dekill_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 :
- Trouver le SOP.
get_sop_text(action)pour la procédure officielle. Pas de SOP → reject avec"no SOP for action <X>". - 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. - Vérifier les opérations parallèles.
query_logqlpour les entréesaudit:touchant la même cible dans les 10 dernières minutes. Action parallèle → reject avec"parallel operation detected; wait for X to complete". - Valider la raison contre les alertes.
get_incident_detailpour 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 »). - 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}.
- Approve quand les trois gates principaux passent. Sortie
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 :
- Un SOP correspondant existe et couvre clairement ce scénario
- Pas d'opération parallèle du même type en cours (regarder alertes / fenêtre d'ops)
- 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>) :
**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 :
- Écrit une ligne
chat_mutating_proposalsavec status=approved, le texte de décision du reviewer, et l'id de SOP. - Laisse l'appel retomber vers l'outil interne.
- Le décorateur d'audit (en aval dans la chaîne) écrit la ligne d'exécution.
- 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 :
- Écrit une ligne
chat_mutating_proposalsavec status=rejected, la raison du reviewer, et les gates manquants. - Renvoie
ErrReviewRejectedenveloppant la raison du reviewer. - 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
ReviewGatesi 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
*_skilldansdisallowed_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.