Votre alerte est partie dans le chat de groupe — et maintenant une question de suivi y fonctionne vraiment


Il est 2 h du matin, le pipeline de déploiement passe au rouge, et le webhook pousse l’alerte dans votre Telegram. Vous la voyez le lendemain matin, vous répondez « est-ce la même panne qu’hier ? » — et vous recevez « à qui faites-vous référence ? ». Vous venez pourtant de le lui dire. Il n’en a aucune idée. Ce n’est pas le modèle qui est lent ; c’est ainsi que les livraisons webhook sont conçues : chaque événement va au bout dans sa propre session, et le message qui a été livré n’existe pas du tout dans la transcription du chat cible. Ce billet porte sur l’interrupteur ajouté en v0.21.5, et sur ce qu’il corrige et ne corrige pas.

D’où vient le trou

Une route webhook fonctionne ainsi : un service externe POSTe un événement, la charge utile est rendue dans un prompt via votre template, un run d’agent a lieu, et le résultat est livré dans un chat. Pendant tout ce temps, la session webhook:<route>:<delivery_id> est éphémère — elle existe pour le run et disparaît ensuite.

Quand vous parlez à l’agent dans ce chat Telegram, il charge la session de ce chat-là. Les deux transcriptions ne se croisent jamais, donc :

  • il ne sait pas qu’il vient de vous envoyer une alerte ;
  • « la panne d’hier » ne pointe vers rien dans son historique ;
  • votre seule option est de reformuler l’alerte, ou de renoncer au suivi.

Activer l’interrupteur

Une route peut désormais activer la mise en miroir. Ajoutez une option là où vous créez ou mettez à jour l’abonnement :

hermes webhook subscribe ci-alerts \
  --prompt "build {route} failed: {payload}" \
  --deliver telegram \
  --mirror-to-session

La sortie de l’abonnement gagne une ligne :

  Created webhook subscription: ci-alerts
  URL:    https://…/webhooks/ci-alerts
  Profile: default
  Events: (all)
  Deliver: telegram
  Replies: each delivery is mirrored into the target chat's session

Les routes existantes peuvent être modifiées via la config à la place :

platforms:
  webhook:
    extra:
      routes:
        ci-alerts:
          prompt: "build {route} failed: {payload}"
          deliver: telegram
          mirror_to_session: true

Le contrôle de valeur est volontairement strict ici : seul un vrai booléen true l’active. La chaîne "true" ne suffit pas — le miroir écrit dans l’historique de votre conversation, donc l’auteur de la route doit le demander explicitement.

Ce qui est réellement reflété

Pas la requête HTTP brute. Le message livré lui-même, avec une étiquette :

[Webhook delivery: ci-alerts]
build 4821 failed on main

Il est ajouté à la session du chat cible comme un tour user disposant de l’autorité user, via le même chemin de code et la même convention de rôle que les cron briefs reprenables. Pourquoi user et pas assistant ? Parce que ce texte n’est pas l’agent de la session cible qui parle — c’est vous à qui l’on a dit quelque chose. Stocké comme tour assistant, il produirait assistant → assistant dans l’historique, ce qui casse d’emblée les providers à alternance stricte ; stocké comme user, deux tours user consécutifs fusionnent sans risque et la forme de la conversation survit.

Le bénéfice : répondez « est-ce la même panne qu’hier ? » et l’agent voit l’étiquette et le texte livré dans son propre historique, donc il peut réellement répondre.

Trois limites à connaître avant de l’activer

1. C’est opt-in, désactivé par défaut

Les routes sans mirror_to_session se comportent exactement comme avant. Même logique que le mirror_delivery du cron : rien n’est écrit dans une session sans demande explicite.

2. C’est limité au profile

Le miroir s’exécute à l’intérieur de _profile_scope(profile), donc il écrit dans le state.db du profile qui possède la route. Ce détail n’est pas cosmétique : le chat_id d’un DM Telegram est l’identifiant utilisateur de la même personne sur tous les bots, donc un miroir non limité atterrirait dans le DM d’un autre profile avec cette même personne. À garder en tête, surtout sur les routes /p/<profile>/.

3. C’est du best-effort

Un échec de miroir ne fait jamais échouer une livraison — l’implémentation ne lève jamais — et le miroir est ignoré quand le chat cible n’a pas encore de session gateway (personne n’a parlé à l’agent là-bas). Autrement dit : activer l’interrupteur ne garantit pas que vous pourrez faire un suivi. Si le groupe n’a jamais eu de conversation avec l’agent, il n’y a aucune session dans laquelle écrire.

Quand ne pas l’activer

Attention avec les routes deliver_only. Celles-ci sautent complètement l’agent — le template rendu est le corps littéral du message, à coût de token nul — et le texte mis en miroir est donc la charge utile brute rendue.

Donc tout ce qui peut influencer ce texte atterrit dans l’historique de votre conversation comme un tour user :

  • le webhook d’un tracker d’issues public : tout ce qu’un inconnu peut écrire dans le titre d’une issue peut apparaître dans l’historique de votre conversation ;
  • les corps d’alerte bruts d’un système de supervision : vrai s’ils contiennent des champs contrôlés par l’utilisateur (messages d’erreur, chemins de requête, chaînes user-agent).

La règle est simple : ne mettez en miroir que des sources dont vous maîtrisez le contenu — vos propres services, vos propres pipelines. Pour tout ce dans quoi un tiers peut écrire, réfléchissez à deux fois avant de basculer l’interrupteur.

Confirmer que c’est actif

hermes webhook list

La liste affiche les détails de chaque route, et créer ou mettre à jour un abonnement imprime la ligne Replies: each delivery is mirrored into the target chat's session.

Pour vérifier le comportement réel, le test le moins coûteux est un événement hermes webhook test vers la route, suivi d’une question dans le chat cible — « qu’est-ce que tu viens de m’envoyer ? ». S’il répond au lieu de demander ce que vous voulez dire, le miroir fonctionne.

Comment cela s’articule avec le reste

Ceci et le ledger de livraison anti-doublons sont les deux faces d’un même problème au niveau du gateway : l’un empêche une réponse d’être livrée deux fois, l’autre empêche une livraison de devenir invisible à la conversation par la suite.

Si vous lancez déjà des vérifications périodiques via cron, les cron briefs disposent depuis un moment de l’équivalent avec l’option mirror_delivery. Les livraisons webhook s’alignent désormais dessus, et les deux chemins utilisent la même convention (une étiquette plus un tour user), donc le suivi se ressent de façon identique dans les deux cas.

En bref

  • Le comportement par défaut ne change pas : les livraisons webhook sont fire-and-forget et le chat cible n’en sait rien.
  • Avec mirror_to_session: true (ou --mirror-to-session), le contenu livré est écrit dans la session du chat cible sous la forme [Webhook delivery: <route>] suivi d’un tour user.
  • Trois limites : désactivé par défaut, limité au profile, best-effort (ignoré quand il n’y a pas de session).
  • Sur les routes deliver_only, le texte mis en miroir est la charge utile brute — ne l’activez que pour des sources de confiance.

C’est arrivé dans v0.21.5 (tag v2026.9.24), dans le cadre de cette fenêtre corrective sans release notes — pour les autres choses déjà utilisables qui en viennent, voir le guide d’auto-vérification de la fenêtre v0.21.5 et notre page de release v0.21.5. Le tableau complet des options se trouve dans la référence des commandes.