Observer une boucle : le compte à rebours en direct qui vient d'arriver sur le CLI, le TUI et le Desktop


Vous avez réglé une boucle pour surveiller un déploiement toutes les trois minutes, puis vous êtes retourné à votre travail. Dix minutes plus tard, vous jetez un œil au terminal et il affiche toujours la même chose qu’au départ — next in 3m — et vous vous posez la question que tout utilisateur de boucles finit par se poser : est-ce que ça tourne encore, ou est-ce que ça s’est arrêté sans rien dire ? C’est exactement cette ambiguïté qu’une PR fusionnée le 15 octobre avait pour mission de faire disparaître, et elle l’a fait sur les trois surfaces d’un coup.

Une boucle permanente, en un paragraphe

/loop relance un prompt dans votre session en cours selon une cadence que vous choisissez, ou selon une cadence qu’il choisit lui-même :

/loop 5m check the deploy status and tell me if it's live yet

Chaque réveil est un tour d’agent ordinaire : il lit donc l’état courant — le dernier résultat de CI, la profondeur de file, le fichier tel qu’il est maintenant — plutôt que de rejouer un résumé. Il fonctionne sur le CLI, le TUI, le chat du dashboard, l’application desktop et toutes les plateformes gateway, et il vit dans cette session : les conditions d’arrêt sont LOOP_COMPLETE émis par l’agent, --times N, --until <condition>, loops.max_ticks comme budget de secours (100 par défaut), ou vous, avec /loop stop et /loop pause.

Le problème : un compte à rebours affiché une seule fois

L’ancien comportement tenait en une seule ligne de transcript — ↻ Loop: next in 3m. — imprimée à l’acceptation de la boucle. Elle ne bougeait jamais. Sur l’application desktop, c’était pire d’une manière plus discrète : le next ~45m de la carte de boucle était calculé une fois par rendu, donc le nombre restait figé à la valeur qu’il avait au premier dessin de la carte. L’information dont vous aviez besoin (combien de temps avant le prochain réveil, est-ce que ça tourne en ce moment, est-ce en pause) existait bien dans la session, mais rien ne la redessinait.

Ce que les trois surfaces affichent maintenant

CLI. Le dock live-work — le panneau qui suit déjà les goals, les subagents, les processus et la file — gagne une ligne loop :

↻ loop · every 3m · 1 run · next in 2m · <prompt>

Le compte à rebours avance minute par minute et passe aux secondes dans la dernière minute, si bien qu’un prompt inactif se redessine au maximum une fois par minute au lieu d’une fois par seconde. Pendant un réveil, il affiche running now, et quand la boucle est en pause il indique la raison de la pause. Repliez le dock et l’en-tête porte un badge compact — ↻ loop next in 2m — vous obtenez donc toujours la réponse sans rien déplier. Quand le dock est visible, l’ancienne ligne de transcript statique ↻ Loop: next in 3m. disparaît, parce que garder les deux n’était que du bruit en double.

TUI. hermes --tui reçoit une LoopBar placée sous la GoalBar, avec le même texte, alimentée par le snapshot de session et une horloge d’une seconde qui ne redessine que lorsque le libellé change réellement.

Desktop. La carte de boucle partage désormais l’horloge de compte à rebours à la seconde du heartbeat — next 00:02:09 — et une fois l’échéance dépassée, elle affiche due — waiting for idle au lieu d’un nombre passé en négatif. Cette dernière chaîne est partagée avec la carte heartbeat, c’est pourquoi elle a été renommée en clé commune plutôt que dupliquée.

La partie que vous ne remarqueriez pas : le gateway devait la publier

Un compte à rebours sur le CLI, c’est simple — ce processus possède son terminal. Les barres Ink du TUI et la carte de l’application desktop sont différentes : elles lisent un snapshot de session que le gateway publie sous session.control.update. Or cette mise à jour n’était publiée que lorsque quelqu’un cliquait sur un bouton d’une carte desktop. Tapez /loop 3m … dans un chat et l’état n’atteignait jamais les surfaces censées le rendre.

Le correctif déplace la publication vers les endroits où l’état de boucle change réellement : le propriétaire publie après une slash command d’automatisation, après les hooks de goal et de loop post-tour d’une session locale, et après le déclenchement d’un réveil de boucle ou la stabilisation d’un tick slash. Le chemin legacy tui_gateway reçoit les deux mêmes publications, pour que les clients plus anciens voient eux aussi les réveils.

Voilà la leçon intéressante de cette PR : ajouter un affichage à une valeur vivante n’est que rarement un simple changement d’affichage. Si la valeur est publiée par quelqu’un d’autre, le correctif suppose de le convaincre de publier aux bons moments — sinon vous livrez une barre de progression qui n’est honnête que sur la première image.

Comment savoir si vous l’avez

Le changement a été fusionné le 15 octobre à 15:34 UTC, et la v0.22.2 avait déjà été publiée le matin même : il est donc uniquement sur main pour l’instant — le travail disponible seulement sur main arrive avec la v0.23.0. Vérification pratique :

hermes version

Si vous êtes sur une build de release, le dock continuera d’afficher l’ancienne ligne statique. Si vous avez besoin de visibilité sur les boucles aujourd’hui et que vous ne pouvez pas exécuter main, le substitut praticable est /loop status, qui indique à la demande la cadence, les ticks déclenchés et le temps avant le prochain réveil — une frappe de touche au lieu d’un coup d’œil, mais cela répond bien à la question « est-ce que ça tourne ».

Deux détails liés méritent d’être connus avant de vous fier à un compte à rebours : un réveil est un tour normal en rôle utilisateur, et le coût en tokens évolue avec la cadence, donc une boucle d’une minute qui n’a rien à faire coûte quand même un tour par minute — préférez le self-pacing quand vous attendez quelque chose qui évolue à son propre rythme. Et pour un travail qui doit survivre à la fermeture de votre terminal, ce n’est pas le bon outil : une planification sans supervision a sa place dans un cron job, tandis que /loop sert à la surveillance à l’intérieur d’une session dans laquelle vous êtes déjà.

À savoir également

  • loops.min_interval_seconds (30), loops.self_paced_floor_seconds (60) et loops.self_paced_ceiling_seconds (900) sont les paramètres derrière la cadence ; loops.max_ticks (100, 0 pour illimité) est le garde-fou.
  • Un /goal actif possède la session : tant qu’un goal est piloté par son judge, les réveils de boucle sont reportés et reprennent lorsque le goal se termine, se met en pause ou s’arrête sur une barrière d’attente.
  • Une boucle par session — définir un nouveau /loop remplace l’ancien ; plusieurs boucles, cela signifie plusieurs sessions (ou cron).
  • Sur le desktop et le dashboard, une session avec une boucle active est exemptée du reaper de sessions déconnectées : les réveils continuent donc de se déclencher après la fermeture de la fenêtre — tant que le processus backend lui-même tourne encore.
  • Ctrl+C sur un tour de réveil met en pause la boucle plutôt que de la détruire ; /loop resume la reprend.

Si /loop lui-même vous est inconnu, le guide de la commande loop couvre les bases sur lesquelles repose ce compte à rebours.


Sources : PR #139187 (/loop shows a live countdown to the next wakeup on CLI, TUI and Desktop, fusionnée le 15 octobre 2026) et son tableau de validation ; website/docs/user-guide/features/loops.md sur main pour les commandes, les modes de cadence, les clés de configuration et la comparaison /loop vs /goal vs cron.