« La connexion semble vivante, mais il n'y a personne » : comment les heartbeats de Hermes réparent les silent disconnects


Vous fermez votre ordinateur portable, partez déjeuner, puis revenez. Hermes est toujours là, à l’écran — historique de session, curseur, tout semble normal. Vous tapez un message et appuyez sur Entrée… rien ne se passe. Pas d’erreur, pas de spinner, pas de timeout : juste le silence. Votre message a disparu dans un trou noir.

Cette situation de « l’interface est vivante, la connexion est morte » porte un nom : le silent disconnect. Ce n’est pas un caprice propre à Hermes — c’est la maladie de tous les programmes en réseau : le réseau n’est plus là, mais aucune des deux extrémités ne le sait, alors l’une attend et l’autre attend aussi. Un ensemble de changements récemment fusionnés dans Hermes permet au TUI et au Desktop de détecter ces connexions zombies et de les reconstruire tout seuls. Voici ce qu’ils corrigent concrètement, et pourquoi cela compte pour quiconque fait tourner Hermes à distance.

Pourquoi les connexions simulent leur propre mort

D’abord, les bases : votre interface Hermes (le TUI en terminal ou l’application Desktop) dialogue avec le véritable « cerveau » — le processus gateway qui tourne sur un serveur ou en arrière-plan sur votre machine — via une seule connexion WebSocket. Chaque message que vous envoyez et chaque token qu’il renvoie transitent par ce tuyau.

Le problème vient du réseau qui se trouve en dessous. Disons que votre Mac dort pendant dix minutes, que le Wi-Fi coupe entre-temps, et qu’il se réveille avec une nouvelle adresse IP. Ou que vous passiez du Wi-Fi du bureau au hotspot de votre téléphone. Ou encore que votre VPN se reconnecte. Dans tous ces cas, l’ancienne connexion TCP n’existe plus — mais votre machine ne le sait pas. Le terme technique est half-open connection : une extrémité du tuyau a disparu, et l’autre continue de croire que tout va bien.

Pourquoi aucune des deux extrémités ne s’en aperçoit-elle ? TCP dispose bien d’un mécanisme keepalive, mais par défaut il sonde le câble beaucoup trop rarement pour avoir le moindre effet, et les navigateurs n’exposent même pas le ping/pong aux développeurs WebSocket. Le client continue donc d’écrire sur une connexion morte — des écritures qui n’échouent pas bruyamment : elles partent juste dans le vide. C’est exactement votre message qui entre dans le trou noir.

La solution : un heartbeat en trois couches

PR #93792, fusionné le 24 août (il intègre trois contributions du développeur communautaire @100yenadmin — #89958, #90012, #89984), s’attaque précisément à ce problème : chaque connexion client envoie désormais un ping au gateway à intervalle régulier, déclare la connexion morte quand aucune réponse n’arrive, et la reconstruit automatiquement. Le mécanisme fonctionne en trois couches, chacune résolvant un problème.

Couche 1 : le serveur apprend à répondre aux pings

Côté serveur (tui_gateway/ws.py), trois petites choses. D’abord, le handshake de démarrage (gateway.ready) annonce désormais heartbeat: true, signalant aux clients « je prends en charge les heartbeats ». Ensuite, il ajoute une méthode gateway.ping à laquelle il est répondu directement dans la boucle de lecture — pas de file d’attente, pas de planification : on répond dès réception. Enfin, il horodate chaque connexion avec last_inbound_at (le moment de la dernière arrivée de données), ce qui facilitera le diagnostic plus tard.

Couche 2 : le client envoie des pings à intervalle régulier et déclare la mort en cas de timeout

Le client TUI (ui-tui/src/gatewayClient.ts) et le JsonRpcGatewayClient partagé du Desktop utilisent les mêmes paramètres : envoyer un gateway.ping toutes les 15 secondes ; si aucune donnée n’arrive du gateway au bout de 45 secondes (pas seulement les accusés de heartbeat — toute donnée compte), déclarer la connexion morte et la démonter pour se reconnecter.

Pourquoi 45 secondes et pas 15 ? Les allers-retours réseau ont de la latence, et un heartbeat perdu ne signifie pas que la connexion est coupée. Si vous n’avez rien reçu du tout en 45 secondes (soit trois cycles de heartbeat), ce n’est presque certainement plus de la gigue — la connexion est réellement morte.

Couche 3 : exponential backoff et rejet des données obsolètes

Une fois la connexion déclarée morte, le client ne martèle pas le serveur de tentatives de reconnexion. Il utilise l’exponential backoff : nouvelle tentative après 1 seconde, puis 2, puis 4, 8, 16… plafonnée à 30 secondes, et il continue jusqu’à ce que la connexion aboutisse. Chaque tentative publie un événement gateway.reconnecting (numéro de la tentative + délai en millisecondes), ce qui rend le dépannage facile.

Le Desktop ajoute un mécanisme plus fin : l’invalidation par socket generation. Quand une connexion est reconstruite, le client remet au nouveau socket un jeton de generation tout frais ; les trames tardives qui arrivent encore sur l’ancien socket — par exemple les derniers messages émis par le gateway juste avant la coupure — sont écartées, car elles portent l’ancien jeton. La nouvelle connexion démarre proprement : le trafic résiduel d’un socket mort ne peut pas polluer la nouvelle session, et aucun travail n’est rejoué deux fois.

Les gateways plus anciens ne sont pas affectés

Tout le heartbeat est capability-gated : les clients ne se mettent à envoyer des heartbeats qu’après avoir reçu heartbeat: true dans le handshake gateway.ready. Si vous restez connecté à un gateway plus ancien — par exemple un serveur distant qui n’a pas encore été mis à jour —, le client se comporte exactement comme avant. Cette correction peut donc voyager avec les nouveaux clients sans casser les anciens serveurs.

Quels scénarios sont couverts

  • Veille/réveil : les cycles de veille de macOS et Windows reconstruisent la pile réseau — c’est la source la plus courante de connexions zombies. Auparavant, le Desktop ne lançait qu’une sonde unique au réveil (c’était le #93694, fusionné plus tôt le même jour, qui empêchait les mises à jour de gateway distant de laisser l’application coincée sur un socket mort). Désormais, un heartbeat continu tourne en dessous.
  • Changements de réseau : Wi-Fi vers hotspot, débranchement/rebranchement Ethernet, roaming au bureau.
  • Reconnexions VPN : quand le tunnel se reconstruit, tous les paquets TCP de l’ancienne connexion sont perdus.
  • Redémarrages silencieux du serveur : le processus gateway redémarre sans que le client reçoive jamais un avis de fermeture.

Le serveur a aussi activé le keepalive TCP sur les sockets WebSocket (détection des pairs morts), en deuxième ligne de défense.

Quand vous en bénéficierez réellement

Pour être honnête sur l’état des lieux : ces changements ne vivent que sur la main branch — ils n’ont encore été livrés dans aucune release taguée (la dernière, v0.20.5, a été taguée le 19 août, avant que ce lot n’atterrisse). Donc si vous lancez hermes update aujourd’hui, vous ne verrez pas encore de logs de heartbeat. Mettez à jour quand la prochaine version arrivera — notre install and upgrade guide vous montre comment, et vous pouvez parcourir les v0.20.5 release notes pour voir ce que la version précédente a changé.

Comment confirmer que le heartbeat fonctionne après la mise à jour ? Surveillez les lignes [lifecycle] dans le TUI : en fonctionnement normal, elles restent silencieuses ; quand le réseau hoquette, vous verrez websocket silent drop detected; forcing reconnect et scheduling gateway reconnect in Xms. Ce n’est plus une mauvaise nouvelle — c’est le client qui se sauve lui-même.

Et si votre inquiétude concerne l’autre type de blocage — la boucle de raisonnement de l’agent qui se fige (pas un problème de connexion, mais l’agent lui-même qui cesse de travailler) —, c’est un mécanisme distinct : le watchdog de boucle côté gateway, que nous avons couvert dans the loop-watchdog tuning guide. Lisez les deux ensemble et vous couvrez la fiabilité sur la couche transport comme sur la couche d’exécution.

En résumé

Les silent disconnects sont le tueur invisible des outils distants : pas d’erreur, pas d’indice, juste votre regard perplexe. La correction de Hermes est d’une simplicité rafraîchissante dans son esprit — envoyer un ping à intervalle régulier, se reconnecter quand personne ne répond, et patienter poliment en attendant — mais c’est justement cette simplicité qui fait son efficacité. Elle transforme « la connexion a simulé sa propre mort » de légende urbaine en événement détectable et récupérable. La prochaine fois que votre réseau hoquette, arrêtez de fixer le trou noir : le client s’affaire déjà à trouver le chemin du retour.