Seu gateway travou às 3 da manhã? Ajustando o loop watchdog do Hermes

Segunda-feira de manhã, você abre o laptop e descobre que os jobs de cron da noite anterior nunca rodaram — não que tenham falhado com erros, simplesmente não há nenhum registro de execução. Você entra via SSH e o processo do gateway está claramente vivo, a porta está aberta, mas toda mensagem que você envia desaparece no silêncio e o log permanece congelado na última linha. Isso é muito mais frustrante do que uma queda: quando um processo morre, o systemd o reinicia em segundos, mas um processo que está vivo porém travado parece perfeitamente saudável para o seu monitoramento.
O Hermes Agent traz uma resposta nativa para exatamente esse cenário: o loop watchdog. Uma thread dedicada do sistema operacional observa o event loop do gateway e, ao detectar que o loop está congelado, encerra deliberadamente o processo com um exit code de reinício de serviço, para que o seu supervisor o traga de volta. E no PR #92317, mesclado em 22 de agosto de 2026, a equipe finalmente conectou os parâmetros de ajuste desse mecanismo ao carregador real de configuração — antes, você podia escrevê-los o dia inteiro que nada mudava.
Quedas são fáceis de resolver; travamentos não
Primeiro, duas palavras. Uma queda significa que o processo sai: a porta fecha, o monitoramento percebe na hora e o KeepAlive do systemd/launchd o puxa de volta imediatamente. Um travamento é diferente — o processo continua vivo, mas o event loop do asyncio (o “coração” de um programa assíncrono, onde toda tarefa entra na fila para executar) está bloqueado por alguma chamada que nunca retorna.
Eis a armadilha: todo caminho de recuperação construído sobre o event loop — novas tentativas por timeout, reescrita de estado, registro de erros — precisa que o event loop esteja rodando para disparar. Quanto mais você precisa de recuperação, menos você a consegue. O processo não morre, então nenhum alarme toca, e um gateway meio morto fica ali parado até que um humano apareça.
Como o watchdog vigia o event loop
A abordagem do Hermes (fonte: gateway/shutdown_watchdog.py) contorna o loop por completo: uma simples thread daemon de nível de sistema operacional mantém a vigilância de fora, em três passos:
- Probe — a cada
loop_watchdog_probe_interval_ssegundos (padrão 30), o watchdog injeta um probe no loop viacall_soon_threadsafe. Essa chamada é thread-safe, então ela chega ao destino mesmo quando o loop está ocupado. - Strike — o watchdog então espera até
loop_watchdog_probe_timeout_ssegundos (padrão 10) pelo processamento do probe. Loop saudável → probe tratado na hora, contador de strikes reinicia. Loop congelado → probe nunca tratado, um strike. - Hard exit — após
loop_watchdog_max_strikesfalhas consecutivas (padrão 3), o watchdog despeja os stack traces de todas as threads viafaulthandler(evidência inestimável para a perícia posterior), registrareason=loop_liveness_watchdogno registro do ciclo de vida e força a saída com exit code 75 — o código dedicado a reinício de serviço, que instrui o systemd/launchd a trazer o gateway de volta.
Com os padrões, cerca de 90 a 120 segundos de bloqueio contínuo do loop disparam a recuperação automática. Comparado a descobrir a falha na manhã seguinte, esse tempo de reação é genuinamente útil.
Os três novos controles — desta vez realmente ligados
Antes do #92317, esse mecanismo tinha uma falha incômoda: o interruptor gateway.loop_watchdog e seus parâmetros já existiam nos padrões de configuração há um tempo, mas o carregador de configuração nunca lia nenhum deles — não importava o que você escrevesse, o watchdog rodava com os padrões fixados no código, e você nem conseguia desligá-lo. O PR conecta as chaves ao caminho real de carregamento (prioridade do nível superior com fallback aninhado) e adiciona validação limitada: NaN, Infinity e valores exagerados agora degradam com segurança para os padrões, em vez de derrubar o carregamento da configuração.
Quatro configurações agora podem ser ajustadas na seção gateway do config.yaml:
| Chave | Padrão | Significado |
|---|---|---|
gateway.loop_watchdog |
true |
Interruptor mestre; defina false para desativar por completo |
gateway.loop_watchdog_probe_interval_s |
30.0 |
Segundos entre probes de atividade |
gateway.loop_watchdog_probe_timeout_s |
10.0 |
Quanto tempo um probe pode ficar sem processamento antes de contar como falha |
gateway.loop_watchdog_max_strikes |
3 |
Falhas consecutivas antes do hard exit |
Ajuste pela CLI
Nada de editar o YAML na mão — o hermes config resolve:
# 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
Reinicie o gateway depois de alterar a configuração. Observe que o hermes config unset remove a chave por completo, de modo que ela volta ao padrão embutido — mais limpo do que definir true manualmente de novo.
Quando afrouxar e quando manter a linha
Afrouxe quando: você conversa com providers de modelos remotos ou lentos cujas requisições individuais às vezes ficam penduradas por um bom tempo, ou sua máquina está tão carregada que o event loop para por segundos a fio. Um probe perdido de vez em quando não é deadlock, e um probe_timeout_s ou max_strikes maiores reduzem mortes falsas.
Não afrouxe para esconder um travamento real. O sentido todo do watchdog é a recuperação rápida; elevar max_strikes de 3 para 8 estica a recuperação em 2–3×, e cada minuto extra que um gateway travado fica parado significa mais jobs de cron acumulados e mensagens não entregues. A equipe também está corrigindo a classe de falsos positivos na raiz (movendo a escrita do heartbeat do próprio watchdog para fora do loop, com um probe de duas testemunhas — o PR #90502 ainda está em revisão), e é por isso que o padrão permanece apertado.
E se “tarefas que travam no meio da execução” é a sua dor recorrente, isso é outra camada: os heartbeats e goal gates em nível de sessão cuidam disso.
Um arquivo de heartbeat para seu próprio monitoramento
Se você roda monitoramento externo (Uptime Kuma, Prometheus ou uma checagem de cron de uma linha), o gateway também reescreve atomicamente um arquivo de heartbeat em <HERMES_HOME>/state/gateway.heartbeat em intervalos regulares. Ele serve a dois propósitos: permite que a supervisão externa distinga “processo vivo” de “loop funcionando”, e ainda funciona como um instantâneo contínuo de telemetria pré-morte — após uma morte suja, o último heartbeat é a coisa mais próxima de uma foto da cena do crime.
Confira a frescura com um único comando:
# Linux
stat -c %Y ~/.hermes/state/gateway.heartbeat
# macOS
stat -f %m ~/.hermes/state/gateway.heartbeat
Compare esse timestamp com a hora atual. Se ele não mudou há um minuto ou dois, o loop provavelmente parou de fazer trabalho real — o watchdog provavelmente está prestes a agir, e você acabou de ganhar um alerta antecipado.
Status de lançamento
Esses controles de ajuste vivem na main agora (PR #92317, mesclado em 2026-08-22) e ainda não estão em nenhuma tag de release — a mais recente, v0.20.5 (tag 2026.8.19), ainda traz o comportamento fixo antigo. Se quiser experimentá-los, espere o próximo release e rode hermes update, ou acompanhe a página de releases no GitHub — nossas notas de release da v0.20.5 resumem esse lançamento.
O gateway é o coração de todo job de cron do Hermes, bot de mensagens e sessão remota (novo por aqui? veja nosso guia de automação de cron e a referência do comando hermes-gateway). Dê a ele uma configuração de watchdog adequada ao seu ambiente e, pelo menos, você sabe que o sistema vai se levantar sozinho às 3 da manhã — em vez de esperar você descobrir às 9.