hermes update ya no sabotea tu trabajo: gateways que drenan, backends que sobreviven y bajas precisas en Windows


¿Te ha pasado alguna vez? Una tarea de larga duración va por la mitad, escribes hermes update casi sin pensar y al segundo siguiente el gateway muere de golpe: la conversación se corta a mitad de turno y todo lo que esperabas desaparece. En Windows era peor: el actualizador llegó a matar todos los hermes.exe de la máquina, derribando instalaciones y proyectos que no tenían nada que ver. Durante las últimas semanas el equipo ha estado reconstruyendo el flujo de actualización de cabo a rabo (campaña #91277), y el último lote acaba de llegar. Las actualizaciones ahora son suaves: los turnos en curso drenan antes de salir, las conexiones remotas se mantienen y las instalaciones basadas en contenedores quedan protegidas de plano.

Actualizar era una lotería

El antiguo pipeline de actualización era, en esencia, un camino de fuerza bruta: hermes update necesitaba desbloquear los archivos del venv, así que hacía tree-kill de cualquier gateway en ejecución, estuviera o no atascado a mitad de turno. Identificar «qué procesos son míos» dependía de un escaneo por patrones de argv — impreciso y propenso a daños colaterales.

En Windows el dilema era aún más crudo: el gateway tenía que morir para que la actualización pudiera avanzar, y el antiguo mecanismo de pausa te obligaba a elegir entre «que el gateway sobreviva al cierre de la app» y «que la actualización pueda proceder». Una actualización que aterrizaba a mitad de turno destruía ese turno sin más.

Mejora 1: los gateways drenan en lugar de morir (#95695)

hermes update ahora pide a un gateway en ejecución que drene y salga a través de su socket de control, usando el nuevo verbo pause-for-update: rechazar turnos nuevos, terminar el activo, entregar su respuesta final, liberar todos los manejadores de archivos del venv y detenerse — exactamente el mismo camino de drenado que ya usan SIGUSR1 y los reinicios de servicios.

Si el socket no se puede alcanzar (un gateway step-1 sin ese verbo, o directamente sin socket), el cliente recibe None y recurre al camino legacy byte por byte. Un ACK correcto trae pausing / already_stopping / pid / drain_timeout, y el actualizador amplía su espera hasta el presupuesto de drenado declarado por el propio gateway (más 10 s de gracia para el teardown) en lugar de matar a la fuerza un gateway atascado a mitad de turno cuando se agota un default local demasiado corto.

Para la mayoría de usuarios esto significa: el turno que está en marcha cuando actualizas llega a terminar.

Mejora 2: los backends de serve remoto sobreviven a la actualización (#95576)

Si ejecutas hermes serve --host <ip> para alimentar un Desktop remoto, el antiguo pipeline ni siquiera sabía que el proceso existía: no aparecía en el inventario de runtime, era invisible para --status y, una vez eliminado, nunca se relanzaba — dejando al cliente remoto tirado contra un endpoint muerto.

Los backends de serve/dashboard ahora se registran por sí mismos en el spawn ledger con su host/puerto/profile realmente vinculados (una identidad auto-registrada a propósito, no otro escaneo por patrones de argv). Tras una actualización siguen corriendo en su endpoint registrado, así que las conexiones remotas no se interrumpen. Esto también cierra una asimetría de larga data: los backends de serve podían morir con hermes dashboard --stop pero estaban ocultos para --status — ahora --status los lista.

Mejora 3: el actualizador de Windows solo mata el hermes.exe de esta instalación (#95086)

Esta era la más aterradora. El antiguo actualizador de Windows ejecutaba taskkill /IM hermes.exe — una matanza por nombre de imagen que derribaba todos los procesos llamados hermes.exe de la máquina, incluidas instalaciones ajenas y trabajo en curso (el issue #91964 fue un caso real del shim del actualizador matando instalaciones ajenas a mitad de ejecución).

force_kill_other_hermes() ahora solo termina los procesos cuya ruta completa del ejecutable coincide con el directorio Scripts del venv de esta instalación (snapshot Toolhelp32 + QueryFullProcessImageNameW, con predicado de ruta insensible a mayúsculas). Varias instalaciones de Hermes en una misma máquina ya no interfieren entre sí — una actualización solo toca lo suyo.

Mejora 4: los backends SSH y los HOMEs ajenos ya no son daño colateral (#95641)

La limpieza de backends obsoletos recibió la misma disciplina: la barrida ya no mata los backends propiedad de SSH (la propiedad SSH se preserva durante las actualizaciones) y nunca revive backends desde un HERMES_HOME ajeno (#94030). Antes, una actualización podía matar tus sesiones SSH remotas y procesos en directorios sin relación; ahora esos límites se hacen cumplir.

Mejora 5: las instalaciones gestionadas por imagen rechazan las actualizaciones en el sitio (#95722)

Las instalaciones de Docker, Nix y apt deben actualizarse haciendo pull de una imagen nueva y reconstruyendo el contenedor, no ejecutando hermes update dentro de un contenedor y mutando una imagen inmutable. Antes, las tres superficies de actualización (hermes update, hermes update --check y el botón Update del escritorio) tenían cada una sus propias heurísticas de Docker/Nix/apt — y un checkout montado por bind dentro de un contenedor parecía una instalación por git, engañando a las comprobaciones. Además, los rechazos no dejaban registro.

El build de la imagen ahora hornea /etc/hermes/image-provenance.json dentro de la imagen (fuera tanto del checkout montable por bind como del volumen HERMES_HOME), y los tres puntos de entrada pasan por una única puerta de admisión compartida:

  • Marker presente (aunque esté malformado) → gestión por imagen; las actualizaciones en el sitio se rechazan, el CLI sale con el código 2 (refused-by-contract) e imprime not updatable in place (<code>); use: <command>;
  • Marker ausente → se aplican las heurísticas existentes como antes;
  • hermes update --plan ahora informa honestamente updatable_in_place=False, incluso cuando un checkout montado por bind engañaría a las antiguas heurísticas de git.

Cómo debería ser tu flujo de actualización

Las instalaciones estándar (el script curl) funcionan como antes:

hermes update --check    # revisa el plan primero — sin sorpresas
hermes update            # los gateways drenan, los backends de serve sobreviven

Usuarios de Docker, recuerden: no ejecuten hermes update dentro de un contenedor. Hagan pull de la imagen nueva y reconstruyan:

docker pull <your-hermes-image>:latest
docker compose up -d --build   # o como lo orquestes tú

¿Varias instalaciones en Windows? Ya puedes relajarte — una actualización solo toca lo suyo. Antes de actualizar, hermes status te muestra los backends de serve/dashboard para que sepas qué está en marcha.

Para terminar

Esta ronda no añade funciones nuevas, pero convierte «actualizar» de algo que podía estallar en cualquier momento en algo que puedes ejecutar con seguridad a media jornada: los turnos terminan, las conexiones remotas se mantienen, las demás instalaciones quedan intactas y las instalaciones en contenedores no pueden romperse a sí mismas. Combinado con la guía del watchdog de bucle del gateway y los contenedores Docker compartidos que cubrimos antes, las operaciones del día a día ganan en fiabilidad de forma notable. Todos estos cambios están ya en main, todavía sin etiqueta de release — para probarlos hoy mismo, ejecuta hermes update hasta el último dev build; mientras tanto, las notas de la release v0.20.5 resumen la oleada anterior de mejoras operativas.