Dein Gateway ist um 3 Uhr nachts eingefroren? Loop-Watchdog von Hermes richtig tunen

Montagmorgen: Du öffnest deinen Laptop und stellst fest, dass die Cron-Jobs der letzten Nacht nie gelaufen sind – nicht etwa mit Fehlern, sondern ganz ohne jede Ausführungsaufzeichnung. Du verbindest dich per SSH: Der Gateway-Prozess ist eindeutig am Leben, der Port ist offen, doch jede Nachricht, die du sendest, verschwindet in der Stille, und das Log sitzt eingefroren auf seiner letzten Zeile fest. Das ist weit frustrierender als ein Absturz: Wenn ein Prozess stirbt, startet ihn systemd in Sekunden neu – aber ein Prozess, der lebt und doch blockiert ist, sieht für dein Monitoring völlig gesund aus.
Hermes Agent bringt eine eingebaute Antwort auf genau dieses Szenario mit: den Loop-Watchdog. Ein eigener OS-Thread beobachtet die Event-Loop des Gateways, und sobald er erkennt, dass die Loop eingefroren ist, beendet er den Prozess gezielt mit einem Service-Restart-Exit-Code, damit dein Supervisor ihn wieder hochzieht. Und mit PR #92317, gemergt am 22. August 2026, hat das Team die Tuning-Parameter dieses Mechanismus endlich durch den echten Config-Loader verdrahtet – vorher konntest du den ganzen Tag Werte schreiben, und nichts hätte sich geändert.
Abstürze sind leicht zu beheben, eingefrorene Loops nicht
Zwei Worte vorweg. Ein Absturz bedeutet, dass der Prozess endet: Der Port schließt sich, das Monitoring schlägt sofort an, und systemd bzw. launchd KeepAlive zieht ihn direkt wieder hoch. Ein Wedge ist etwas anderes – der Prozess bleibt am Leben, aber die asyncio-Event-Loop (das „Herz“ eines asynchronen Programms, in dem sich jede Task zum Ablauf einreiht) ist durch einen Aufruf blockiert, der nie zurückkehrt.
Hier liegt die Falle: Jeder Recovery-Pfad, der auf der Event-Loop aufbaut – Timeout-Retries, State-Rewrites, Fehler-Logging – braucht eine laufende Event-Loop, um überhaupt auszulösen. Je mehr du Recovery brauchst, desto weniger bekommst du davon. Der Prozess stirbt nicht, also klingelt kein Alarm, und ein halb totes Gateway sitzt einfach da, bis ein Mensch auftaucht.
Wie der Watchdog den Event-Loop überwacht
Hermes’ Ansatz (Quelle: gateway/shutdown_watchdog.py) umgeht die Loop komplett: Ein schlichter Daemon-Thread auf OS-Ebene hält von außen Wache, in drei Schritten:
- Probe – Alle
loop_watchdog_probe_interval_sSekunden (Standard 30) injiziert der Watchdog percall_soon_threadsafeeine Probe in die Loop. Dieser Aufruf ist thread-sicher, landet also auch dann, wenn die Loop beschäftigt ist. - Strike – Danach wartet der Watchdog bis zu
loop_watchdog_probe_timeout_sSekunden (Standard 10) darauf, dass die Probe verarbeitet wird. Gesunde Loop → Probe wird sofort behandelt, Strike-Zähler setzt zurück. Eingefrorene Loop → Probe wird nie behandelt, ein Strike. - Hard Exit – Nach
loop_watchdog_max_strikesaufeinanderfolgenden Fehlversuchen (Standard 3) schreibt der Watchdog perfaulthandlerStack-Traces aller Threads (unschätzbare Beweise für die spätere Forensik), vermerktreason=loop_liveness_watchdogim Lifecycle-Ledger und beendet den Prozess hart mit Exit-Code 75 – dem dedizierten Service-Restart-Code, der systemd/launchd anweist, das Gateway wieder hochzuziehen.
Mit den Standardwerten löst eine anhaltende Blockade der Loop von etwa 90 bis 120 Sekunden automatische Recovery aus. Verglichen mit der Entdeckung des Fehlers am nächsten Morgen ist diese Reaktionszeit wirklich brauchbar.
Die drei neuen Regler – diesmal wirklich verdrahtet
Vor #92317 hatte dieser Mechanismus einen unangenehmen Schönheitsfehler: Der Schalter gateway.loop_watchdog und seine Parameter existierten schon länger in den Config-Defaults, aber der Config-Loader hat nie einen davon gelesen – egal was du geschrieben hast, der Watchdog lief mit hartcodierten Defaults, und ausschalten konntest du ihn nicht einmal. Der PR verdrahtet die Keys in den echten Ladepfad (Top-Level gewinnt, mit Nested-Fallback) und ergänzt eine begrenzte Validierung: NaN, Infinity und überdimensionierte Werte degradieren jetzt sicher auf die Defaults, statt den Config-Load zu crashen.
Vier Einstellungen sind nun unter dem Abschnitt gateway der config.yaml justierbar:
| Key | Default | Bedeutung |
|---|---|---|
gateway.loop_watchdog |
true |
Master-Schalter; auf false setzen, um alles zu deaktivieren |
gateway.loop_watchdog_probe_interval_s |
30.0 |
Sekunden zwischen den Liveness-Probes |
gateway.loop_watchdog_probe_timeout_s |
10.0 |
Wie lange eine Probe unbearbeitet bleiben darf, bevor sie als Fehlversuch zählt |
gateway.loop_watchdog_max_strikes |
3 |
Aufeinanderfolgende Fehlversuche vor dem Hard Exit |
Tuning über die CLI
Kein Hand-Editing von YAML nötig – hermes config übernimmt das:
# Check the current value
hermes config get gateway.loop_watchdog_max_strikes
# Your machine is heavily loaded and occasionally stalls:
# give a probe more time before counting a miss
hermes config set gateway.loop_watchdog_probe_timeout_s 20
# Allow 5 consecutive misses before acting
# (~2-3 minutes of sustained block before recovery)
hermes config set gateway.loop_watchdog_max_strikes 5
# Prefer no watchdog at all?
hermes config set gateway.loop_watchdog false
# Changed your mind — back to defaults
hermes config unset gateway.loop_watchdog
Starte das Gateway nach der Config-Änderung neu. Beachte: hermes config unset entfernt den Key vollständig, sodass der eingebaute Default greift – sauberer, als ihn händisch wieder auf true zu setzen.
Wann lockern, wann die Linie halten
Lockern, wenn: du mit Remote- oder langsamen Modell-Providern sprichst, deren einzelne Requests gelegentlich länger hängen, oder deine Maschine so ausgelastet ist, dass die Event-Loop sekundenweise stockt. Gelegentliche verpasste Probes sind keine Deadlocks, und ein größeres probe_timeout_s oder max_strikes reduziert Fehlabschüsse.
Lockere nicht, um einen echten Wedge zu verstecken. Der ganze Sinn des Watchdogs ist schnelle Recovery; max_strikes von 3 auf 8 anzuheben verlängert die Recovery um das Zwei- bis Dreifache, und jede zusätzliche Minute, in der ein verkeiltes Gateway herumsteht, bedeutet mehr aufgestaute Cron-Jobs und unzugestellte Nachrichten. Das Team behebt zudem die Klasse der False Positives an der Wurzel (der eigene Heartbeat-Write des Watchdogs wird aus der Loop herausgenommen, mit einer Zwei-Zeugen-Probe – PR #90502 ist noch in Review), und genau deshalb bleibt der Default eng.
Und falls „Tasks, die mitten im Lauf stecken bleiben“ dein wiederkehrender Schmerz ist: Das ist eine andere Ebene – dafür sind die Heartbeat- und Goal-Gates auf Session-Ebene zuständig.
Eine Heartbeat-Datei für dein eigenes Monitoring
Wenn du externes Monitoring betreibst (Uptime Kuma, Prometheus oder ein Ein-Zeilen-Cron-Check): Das Gateway schreibt außerdem atomar in festem Takt eine Heartbeat-Datei unter <HERMES_HOME>/state/gateway.heartbeat neu. Sie dient zwei Zwecken: Sie lässt externe Überwachung zwischen „Prozess lebt“ und „Loop arbeitet“ unterscheiden, und sie fungiert zugleich als rollierendes Telemetrie-Snapshot vor dem Tod – nach einem unsauberen Beenden ist der letzte Heartbeat das Nächstliegende zu einem Tatortfoto.
Frische mit einem Befehl prüfen:
# Linux
stat -c %Y ~/.hermes/state/gateway.heartbeat
# macOS
stat -f %m ~/.hermes/state/gateway.heartbeat
Vergleiche den Zeitstempel mit der aktuellen Zeit. Wenn er sich seit ein oder zwei Minuten nicht bewegt hat, hat die Loop wahrscheinlich aufgehört, echte Arbeit zu verrichten – der Watchdog wird vermutlich gleich zuschlagen, und du hattest gerade eine Früherkennung.
Release-Status
Diese Tuning-Regler leben derzeit auf main (PR #92317, gemergt am 22.08.2026) und sind noch in keinem Release-Tag – das aktuelle v0.20.5 (Tag 2026.8.19) bringt weiterhin das alte feste Verhalten. Wenn du sie ausprobieren willst, warte auf das nächste Release und führe hermes update aus, oder folge der Seite GitHub releases – unsere Versionshinweise zu v0.20.5 fassen dieses Release zusammen.
Das Gateway ist das Herz jedes Hermes-Cron-Jobs, jedes Messaging-Bots und jeder Remote-Session (neu hier? Dann wirf einen Blick auf unseren Cron-Automatisierungs-Guide und die hermes-gateway-Befehlsreferenz). Gib ihm eine Watchdog-Konfiguration, die zu deiner Umgebung passt – dann weißt du wenigstens, dass sich das System um 3 Uhr nachts von selbst aufrappelt, statt darauf zu warten, dass du es um 9 entdeckst.