¿Tu gateway se congeló a las 3 AM? Ajustando el loop watchdog de Hermes


Lunes por la mañana, abres el portátil y descubres que los trabajos cron de anoche nunca se ejecutaron — no es que fallaran con errores, es que no hay ni rastro de ejecución. Entras por SSH y el proceso del gateway está claramente vivo, el puerto abierto, y sin embargo cada mensaje que envías se pierde en el silencio mientras el log permanece congelado en su última línea. Es mucho más frustrante que un crash: cuando un proceso muere, systemd lo reinicia en segundos, pero un proceso que sigue vivo pero bloqueado parece perfectamente sano ante tu monitoreo.

Hermes Agent trae una respuesta integrada exactamente para este escenario: el loop watchdog. Un hilo dedicado a nivel de sistema operativo vigila el event loop del gateway y, cuando detecta que el bucle está congelado, mata deliberadamente el proceso con un exit code de reinicio de servicio para que tu supervisor lo reviva. Y en el PR #92317, fusionado el 22 de agosto de 2026, el equipo por fin conectó los parámetros de ajuste de este mecanismo con el cargador de configuración real — antes podías escribirlos todo el día y nada cambiaba.

Los fallos son fáciles de arreglar; los bloqueos no

Dos palabras primero. Un crash significa que el proceso termina: el puerto se cierra, el monitoreo lo nota al instante y systemd/launchd con KeepAlive lo levanta de nuevo al momento. Un bloqueo (wedge) es distinto — el proceso sigue vivo, pero el event loop de asyncio (el «corazón» de un programa asíncrono, donde cada tarea hace cola para ejecutarse) está bloqueado por alguna llamada que nunca retorna.

Aquí está la trampa: todas las vías de recuperación construidas sobre el event loop — reintentos con timeout, reescrituras de estado, registro de errores — necesitan que el event loop esté en marcha para poder dispararse. Cuanto más necesitas recuperación, menos puedes tenerla. El proceso no muere, así que no suena ninguna alarma, y un gateway medio muerto se queda ahí sentado hasta que aparece un humano.

Cómo vigila el watchdog el bucle de eventos

El enfoque de Hermes (fuente: gateway/shutdown_watchdog.py) sortea el bucle por completo: un simple hilo daemon a nivel de sistema operativo vigila desde fuera, en tres pasos:

  1. Sonda (probe) — cada loop_watchdog_probe_interval_s segundos (por defecto 30), el watchdog inyecta una sonda en el bucle mediante call_soon_threadsafe. Esa llamada es thread-safe, así que llega aunque el bucle esté ocupado.
  2. Marca (strike) — el watchdog espera luego hasta loop_watchdog_probe_timeout_s segundos (por defecto 10) a que la sonda se procese. Bucle sano → la sonda se maneja al instante y el contador de marcas se reinicia. Bucle congelado → la sonda nunca se procesa: una marca.
  3. Salida forzada — tras loop_watchdog_max_strikes fallos consecutivos (por defecto 3), el watchdog vuelca los stack traces de todos los hilos mediante faulthandler (una evidencia invaluable para el análisis forense posterior), registra reason=loop_liveness_watchdog en el registro del ciclo de vida y fuerza la salida con exit code 75 — el código dedicado a reiniciar el servicio, que le indica a systemd/launchd que vuelva a levantar el gateway.

Con los valores por defecto, unos 90–120 segundos de bloqueo sostenido del bucle disparan la recuperación automática. Comparado con descubrir el fallo a la mañana siguiente, ese tiempo de reacción es genuinamente útil.

Las tres nuevas perillas: esta vez conectadas de verdad

Antes del #92317, este mecanismo tenía un defecto incómodo: el interruptor gateway.loop_watchdog y sus parámetros llevaban un tiempo existiendo en los valores por defecto de la configuración, pero el cargador de configuración nunca leía ninguno de ellos — escribieras lo que escribieras, el watchdog se ejecutaba con valores fijos (hardcoded) y ni siquiera podías apagarlo. El PR conecta estas claves con la ruta real de carga (gana la de nivel superior, con respaldo anidado) y añade validación acotada: NaN, Infinity y valores desmesurados ahora degradan con seguridad a los valores por defecto en lugar de romper la carga de la configuración.

Cuatro ajustes son ahora configurables en la sección gateway de config.yaml:

Clave Valor por defecto Significado
gateway.loop_watchdog true Interruptor maestro; pon false para desactivarlo por completo
gateway.loop_watchdog_probe_interval_s 30.0 Segundos entre sondas de actividad
gateway.loop_watchdog_probe_timeout_s 10.0 Cuánto tiempo puede una sonda estar sin procesarse antes de contar como fallo
gateway.loop_watchdog_max_strikes 3 Fallos consecutivos antes de la salida forzada

Ajuste desde la CLI

No hace falta editar el YAML a mano — hermes config lo gestiona:

# 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

Reinicia el gateway después de cambiar la configuración. Ten en cuenta que hermes config unset elimina la clave por completo para que vuelva a caer en el valor por defecto integrado — más limpio que volver a poner true a mano.

Cuándo aflojar y cuándo mantener la línea

Afloja cuando: hables con proveedores de modelos remotos o lentos cuyas peticiones individuales a veces se cuelgan un buen rato, o tu máquina esté tan cargada que el event loop se detenga durante segundos seguidos. Una sonda fallida de vez en cuando no es un deadlock, y un probe_timeout_s o max_strikes más grandes reducen los matados en falso.

No aflojes para esconder un bloqueo real. Todo el sentido del watchdog es recuperarse rápido; subir max_strikes de 3 a 8 alarga la recuperación 2–3 veces, y cada minuto extra que un gateway bloqueado permanece ahí significa más trabajos cron acumulados y mensajes sin entregar. El equipo también está corrigiendo de raíz la clase de falsos positivos (moviendo la escritura del propio heartbeat del watchdog fuera del bucle, con una sonda de dos testigos — el PR #90502 sigue en revisión), que es por lo que el valor por defecto se mantiene estricto.

Y si «las tareas se quedan a medias» es tu dolor recurrente, eso es otra capa: lo cubren el heartbeat y los goal gates a nivel de sesión.

Un archivo de heartbeat para tu propio monitoreo

Si ejecutas monitoreo externo (Uptime Kuma, Prometheus o un cron de una línea), el gateway también reescribe atómicamente un archivo de heartbeat en <HERMES_HOME>/state/gateway.heartbeat con una cadencia determinada. Sirve a dos propósitos: permite a la supervisión externa distinguir entre «el proceso está vivo» y «el bucle funciona», y además funciona como una instantánea de telemetría continua previa a la muerte — tras una muerte sucia, el último heartbeat es lo más parecido a una foto de la escena del crimen.

Comprueba la frescura con un solo comando:

# Linux
stat -c %Y ~/.hermes/state/gateway.heartbeat
# macOS
stat -f %m ~/.hermes/state/gateway.heartbeat

Compara esa marca de tiempo con la hora actual. Si no se ha movido en un minuto o dos, es probable que el bucle haya dejado de hacer trabajo real — el watchdog está a punto de actuar, y acabas de recibir una alerta temprana.

Estado de publicación

Estos ajustes viven ahora en main (PR #92317, fusionado el 2026-08-22) y todavía no están en ninguna release — la última v0.20.5 (tag 2026.8.19) sigue incluyendo el comportamiento fijo antiguo. Si quieres probarlos, espera a la próxima release y ejecuta hermes update, o sigue la página de GitHub releases — nuestras notas de la release v0.20.5 resumen esa versión.

El gateway es el corazón de cada trabajo cron de Hermes, bot de mensajería y sesión remota (¿nuevo aquí? mira nuestra guía de automatización con cron y la referencia del comando hermes-gateway). Dale una configuración de watchdog que encaje con tu entorno, y al menos sabrás que el sistema se levantará solo a las 3 AM — en lugar de esperar a que lo descubras a las 9.