Répétition des Déclencheurs
Ce que c'est
Un agent qui se réveille sur des événements est aussi sûr que ses règles de déclenchement. L'agent lui-même peut être prudent et brûler le budget d'une journée, car le problème se situe généralement un niveau au-dessus :
- une règle réagit à un événement que son propre travail produit, donc elle se déclenche encore et encore ;
- deux règles s'alimentent mutuellement via un second système (l'agent commente, le commentaire se synchronise, la synchronisation déclenche un webhook) ;
- une source bruyante envoie dix modifications en deux minutes et chacune lance une nouvelle exécution ;
- une ligne cron prévue pour "vérifier de temps en temps" se déclenche 288 fois par jour ;
- deux règles traitent le même événement et effectuent le travail deux fois ;
- une règle qui rembourse, supprime ou envoie des messages s'exécute sans demander à personne.
Aucun de ces problèmes n'apparaît lorsque vous testez l'agent sur un exemple. Ils apparaissent lorsque le trafic réel arrive. Trigger Rehearsal fait arriver le trafic réel tôt, sur le papier : vous écrivez les règles dans un format neutre, rejouez un jour ou une semaine d'événements à travers elles, et lisez la chronologie avant que quoi que ce soit ne soit armé.
Quand l'utiliser
- Avant d'activer une règle qui permet à un agent de commencer un travail sans qu'une personne ne tape une invite.
- Lors de l'ajout d'une règle à un ensemble existant (une nouvelle règle peut fermer une boucle qui était ouverte auparavant).
- Lorsqu'un agent en arrière-plan s'exécute beaucoup plus souvent, ou beaucoup moins souvent, que prévu.
- Lors du choix des fenêtres de debounce, des limites de parallélisme et des plafonds quotidiens, et que vous voulez des chiffres plutôt que des suppositions.
- Lorsqu'un réviseur demande "que se passe-t-il si ce dépôt reçoit 50 problèmes en une heure ?"
Pas pour : vérifier ce que l'agent fait à l'intérieur d'une exécution (c'est une question d'invite ou de compétence), ou tester la charge de la source d'événements elle-même.
La feuille de règles
Tout est traduit en rules.json. Gardez la traduction honnête : une règle de feuille par règle réelle, et notez les gardes qui existent vraiment, pas celles que vous avez l'intention d'ajouter.
{
"tz": "Europe/Paris",
"run_s": 180,
"external_sources": ["github"],
"budget": {"runs_per_day": 40, "cost_per_run": 0.15, "cost_per_day": 6},
"rules": [
{
"name": "tri-nouveau-probleme",
"on": {"source": "github", "types": ["issues.opened", "issues.edited"]},
"where": {"repo": "moi/app"},
"key": "subject",
"debounce_s": 300,
"parallel": 1,
"max_per_day": 20,
"unless_actor": ["mon-bot"],
"unless_label": "trié",
"emits": [{"source": "github", "types": ["issues.labeled"], "actor": "mon-bot", "labels": ["trié"]}],
"sensitive": [],
"asks": false
},
{"name": "digest-matin", "on": {"cron": "0 9 * * 1-5"}, "max_per_day": 1}
]
}
| Champ | Signification |
|---|---|
on.source + on.types | Quels événements démarrent la règle. Laissez types de côté pour correspondre à tous les types de cette source. |
on.cron | Une ligne cron à cinq champs dans le fuseau horaire de la feuille. Une règle a soit source soit cron. |
where | Les champs d'événement doivent être égaux à une valeur (ou faire partie d'une liste). |
key | Champ d'événement nommant le sujet (numéro de problème, identifiant de ticket, URL). Utilisé pour fusionner les répétitions. |
debounce_s | Attendez cette durée après le dernier événement pour un sujet, puis exécutez une fois. |
parallel | Combien d'exécutions de cette règle peuvent être actives en même temps. Les exécutions supplémentaires attendent. |
max_per_day | Plafond strict par jour local. Les exécutions supplémentaires sont abandonnées. |
run_s | Durée typique d'une exécution, utilisée pour la mise en file d'attente. Par défaut, la valeur de la feuille, puis 120. |
unless_actor, unless_label | Gardes : ignorer les événements effectués par ces acteurs, ou portant cette étiquette. |
emits | Événements que l'exécution elle-même produit (commentaires, étiquettes, changements de statut, événements de suivi). Incluez l'acteur et les étiquettes qu'ils porteront. |
sensitive + asks | Actions qui nécessitent une personne (supprimer, dépenser, redémarrer, envoyer). asks: true signifie que l'exécution s'arrête et demande d'abord. |
external_sources | Sources alimentées par le monde extérieur. Tout le reste doit être émis par une règle. |
budget | Optionnel : exécutions par jour, coût par exécution et coût par jour utilisés pour le verdict. |
Étape par étape
- Collectez les vraies règles. Lisez la configuration réelle, pas la description. Fichiers de règles du hub d'agents, routines sauvegardées, blocs
on:du workflow CI, lignes crontab, écrans d'automatisation sans code. Notez tout ce qui écrit dans une source qu'une autre règle surveille. - Traduisez en
rules.json. Commencez parpython3 scripts/rehearse.py new rehearsal/. Pour chaque règle, posez trois questions et remplissez les champs : qu'est-ce qui la réveille, qu'écrit une exécution, et qu'est-ce qui l'empêche de se réveiller sur ses propres écritures. Si la réponse à la troisième est "rien", laissez la garde de côté. La répétition est conçue pour attraper cela. - Analysez.
python3 scripts/rehearse.py lint rehearsal/rules.json. Corrigez chaque HOLD avant de rejouer ; les éléments REVIEW peuvent attendre les chiffres. - Rassemblez les événements. Le mieux : exportez une vraie journée chargée de la source (journal de livraison webhook, chronologie de problème, journal d'audit du service d'assistance) dans
events.jsonl, un objet JSON par ligne avec au moinst,source,type, plus les champs utilisés par votrekeyetwhere. Si aucun historique n'existe, écrivez intentionnellement une courte rafale synthétique : dix modifications en trois minutes, deux sujets à la fois, un événement créé par votre propre bot. - Rejouez sur des journées entières.
python3 scripts/rehearse.py replay rehearsal/rules.json rehearsal/events.jsonl --from 2026-11-02T00:00:00Z --to 2026-11-03T00:00:00Z --md rehearsal/report.md. Passez--from/--topour que les chiffres par jour ne soient pas gonflés par une courte fenêtre. - Lisez la chronologie, pas seulement le verdict. Recherchez des exécutions sur le même sujet à quelques minutes d'intervalle (debounce manquant ou trop court), des chaînes de profondeur croissante (boucles), de longues attentes (parallélisme trop faible), des abandons (plafond trop bas pour le travail réel, ou signe que la règle se déclenche sur du bruit).
- Ajustez et répétez. Modifiez un paramètre à la fois et rejouez les mêmes événements, afin que la différence provienne clairement de ce changement.
- Décidez. ARMEZ seulement lorsque la dernière répétition est ARM sur une journée réaliste. Conservez le
rules.jsonfinal, le fichier d'événements etreport.mdensemble comme preuve de pourquoi les règles ont été activées.
Verdicts
- ARM : aucun résultat HOLD ou REVIEW. Sûr à activer tel quel.
- TRIM : quelque chose gaspille des exécutions ou peut sauter du travail (pas de clé, pas de plafond, chevauchement, le plafond a abandonné des événements réels, proche du budget). N'activez qu'après avoir réduit, ou avec une raison écrite.
- HOLD : n'activez pas. Auto-boucle, cycle non protégé, chaîne ayant dépassé la profondeur 5, action sensible sans demander, dépassement du budget, ou ligne cron cassée.
Le code de sortie suit le verdict (0, 1, 2 ; 3 signifie une mauvaise entrée), de sorte qu'une répétition peut bloquer un changement de configuration dans le CI.
Exemple
Une équipe de support souhaite qu'un agent réponde aux tickets du service d'assistance. Leur feuille (dans examples/loopy/) a une règle de réponse qui publie une réponse, ce qui met à jour le ticket, ce qui correspond à la même règle. Une relecture d'une journée ordinaire donne :
HOLD self-loop repondre-ticket : la propre sortie de la règle correspond à son propre déclencheur sans garde unless_actor/unless_label
HOLD runaway repondre-ticket : la chaîne a continué à se déclencher au-delà de la profondeur 5 (3 coupures)
HOLD sensitive-unasked verifier-remboursement : déclare des actions sensibles ['refund'] mais 'asks' n'est pas vrai
HOLD over-runs-budget - : 100,0/jour contre un budget de 30/jour
REVIEW cron-tight boite-reception : jusqu'à 720 déclenchements/jour
VERDICT HOLD
Trois mises à jour de ticket se sont transformées en 18 exécutions de réponse. Le correctif est petit : ajoutez "unless_actor": ["support-bot"] à la règle de réponse, définissez "asks": true sur la règle de remboursement, et remplacez la sonde de deux minutes par un script qui émet inbox.changed uniquement lorsque la boîte de réception a vraiment changé (voir examples/loopy/rules.fixed.json). La prochaine relecture de la même journée passe de 100 exécutions à 4 et le verdict passe à TRIM. Le seul résultat restant est que la réponse et la vérification du remboursement voient toutes deux un ticket de remboursement, ce qui est intentionnel ici, donc il est accepté avec une raison écrite d'une ligne à côté du rapport.
Conseils
- La meilleure garde anti-boucle est une condition qui devient fausse après l'exécution : une étiquette que l'exécution ajoute et que la règle exclut, ou un statut que la règle ne correspond plus.
- Les boucles inter-systèmes sont les plus faciles à manquer. Si l'agent écrit dans le système A et que le système A se synchronise avec B, listez l'événement synchronisé sous
emitsavec l'acteur qu'il affichera. - Le debounce ajoute un délai par conception. Choisissez la fenêtre en fonction du temps qu'une personne met pour terminer une rafale de modifications, et gardez-la courte là où quelqu'un attend une réponse.
- Le debounce n'est pas une déduplication des actions. Pour tout ce qui est irréversible, l'exécution elle-même doit toujours vérifier "déjà fait ?" avant d'agir.
- Un plafond quotidien est un fusible, pas un plan. Si la relecture d'une journée normale l'atteint, la règle est trop large.
- Sonder toutes les quelques minutes est généralement moins cher sous la forme d'un petit script qui compare l'état et émet un événement lors du changement ; l'agent ne se réveille que lorsqu'il y a du nouveau.
- Relancez la répétition à chaque fois qu'une règle est ajoutée. La plupart des boucles sont créées par la deuxième règle, pas par la première.