Hermes 'olvidó' 798 mensajes tras un fallo: el bug de la amnesia de sesión, explicado y corregido


Lunes por la mañana. Abres Telegram para retomar el hilo que dejaste con Hermes anoche… y empieza a hablar de un tema que debería estar cerrado desde hace tres días. «¿Se fusionó ese PR?»

No es que el modelo esté confundido, ni es cosa de tu imaginación. Es exactamente lo que le ocurrió a Teknium, el responsable principal de Hermes Agent, a principios de agosto de 2026: tras un fallo y reinicio del gateway, su DM de Telegram retomó silenciosamente un contexto de hace 2,7 días, y los 798 mensajes reales intercambiados mientras tanto parecieron desaparecer. No se perdió nada: Hermes simplemente no podía encontrarlos.

Qué ocurrió: cómo un mensaje envió una sesión al pasado

El issue oficial de seguimiento (#82616) incluye evidencia de la base de datos de producción. El incidente se desarrolló en cuatro pasos:

Cuándo Qué ocurrió
6 ago, 16:18 Un mensaje entrante dispara una ruta de recuperación que crea silenciosamente una fila de sesión nueva sin identidad
6 ago → 8 ago Los 798 mensajes reales van a parar a esta fila huérfana; el mapeo en memoria mantiene la conversación con un aspecto perfectamente normal: la bifurcación es invisible
8 ago, 17:03 El gateway falla y se reinicia; el mapeo en memoria «chat → sesión» se descarta por obsoleto
9 ago, 09:40 Llega el siguiente mensaje: Hermes resuelve el chat por session key, y la única fila que lleva la clave es la sesión obsoleta del 3 de agosto — así que retoma un contexto de hace 3 días y habla de un PR fusionado días antes

La evidencia de la base de datos muestra dos filas: la «original» del 3 de agosto (con session key completa, pero ended_at vacío y sin mensajes nuevos), y la huérfana del 6 de agosto (session_key, chat_id todo NULL — pero con los 798 mensajes colgando de ella).

En palabras llanas: Hermes «bifurcó» silenciosamente una copia sin nombre de la sesión, todos los mensajes fueron a parar a la copia, un reinicio borró la agenda en memoria, y cuando Hermes volvió a buscar la sesión solo reconoció la fila con etiqueta de nombre — así que retrocedió en el tiempo.

Causa raíz: tres fallos apilados, todos silenciosos

El análisis a fondo (con análisis completo a nivel de código en los comentarios del issue) convergió en una única cadena:

  1. Las escrituras de rotación de sesión en la base de datos fallaron, y los fallos fueron tragados en silencio. El auto-reset por inactividad de Hermes cierra la sesión antigua y crea una nueva — ambas escrituras fallaron: una se registró a nivel debug, y la otra fue un print desnudo a la consola. Nadie se dio cuenta.
  2. Sin embargo, la tabla de enrutado ya había cambiado a la nueva sesión. La sesión antigua se convirtió en un «zombie» (nunca se marcó como cerrada y siguió conservando la session key), mientras que la nueva sesión no existía en absoluto en disco.
  3. Un escritor perezoso materializó la fila fantasma. Una escritura posterior en segundo plano (p. ej., el contabilizador de uso de tokens) encontró que faltaba la fila destino y la creó sobre la marcha — pero con todos los campos de identidad (session_key, chat_id, …) vacíos. Así nació la huérfana, y ninguna ruta de código vuelve a estamparle la identidad.
  4. Tras el fallo, la resolución del reinicio no podía ver a la huérfana. Busca las sesiones por clave y por información del chat — la huérfana no coincide con ninguna de las dos, así que el único candidato es el «original» zombie. Gana, y vuelve el contexto antiguo.

Hay un giro fascinante: los logs no paraban de decir possible FTS write corruption y disk=0 (lectura desde disco de 0 filas), así que todos sospecharon primero de corrupción en la base de datos. La investigación demostró que la ruta de lectura nunca toca el índice de texto completo — el problema real era el enrutado por ID de sesión: el escritor seguía un mapa de reruteo, el lector consultaba el ID antiguo y obtenía 0 filas. La base de datos estuvo sana todo el tiempo, que es exactamente la razón por la que no se perdió ni un solo mensaje.

No se perdió nada: los 798 mensajes viven en una fila «oculta»

La parte más tranquilizadora de todo este incidente: los 798 mensajes están intactos — simplemente están en una fila sin etiqueta de identidad, invisible para el resolvedor. Para los usuarios afectados, el issue documenta un procedimiento de recuperación manual (abajo).

La corrección: prevención + tratamiento, fusionada a main el 2026-08-09

La corrección llega en dos PR — uno para que nunca vuelva a ocurrir, y otro para el daño que ya existe:

#82633 — endurecimiento del lado de escritura (prevención)

  • Identidad escrita de forma atómica: la session key, el chat ID y el origen ahora forman parte del propio INSERT de creación de la fila (ON CONFLICT rellena vía COALESCE), en lugar de un UPDATE posterior best-effort. Identidad y fila ahora viven o mueren juntas.
  • Cada refresco es una oportunidad de reparación: el gateway actualiza la información de sesión en cada turno; si falta una fila, ahora inserta la identidad completa en lugar de no hacer nada en silencio.
  • Resolución consciente de lo reciente: tras un fallo/reinicio, las sesiones candidatas se ordenan por last_activity_at, con las filas que contienen mensajes primero, y nunca se acuña un ID nuevo mientras exista una fila con clave.
  • Los errores dejan de ser silenciosos: los fallos de escritura al cerrar/crear se registran a nivel WARNING con la consecuencia de enrutado explicada; las excepciones de lectura de transcripción ya no devuelven silenciosamente una lista vacía.
  • También corrige #12857: los resets de sesión ya no pierden el ID de sesión padre (lineage).

La validación fue dura: 11 tests de regresión ejecutados contra un main sin corregir produjeron 6 fallos; tras la corrección, todos pasan. Una reproducción de extremo a extremo del incidente (zombie de 3 días + huérfana de 798 mensajes en una base de datos real) resuelve de vuelta a la conversación viva.

#82712 — hermes sessions repair-routing (tratamiento)

Un comando nuevo que «rescata» las sesiones huérfanas, diseñado en torno a un principio fail-closed:

hermes sessions repair-routing              # dry-run primero: informa de los hallazgos, no cambia nada
hermes sessions repair-routing --apply      # repara de verdad, tras la confirmación
  • Detección: escanea las filas de sesión del gateway sin clave pero con mensajes reales (las filas branch/delegate/tool no tienen clave por diseño y quedan excluidas, así que no hay falsos positivos).
  • Condicionado por la evidencia: solo actúa cuando la evidencia es inequívoca — o bien hay lineage registrado (parent_session_id apuntando a una fila con clave de la misma fuente), o bien hay exactamente un predecesor con clave que calló dentro de los 15 minutos posteriores al inicio de la huérfana. Dos predecesores candidatos, o dos huérfanas que reclaman un mismo predecesor, se rechazan con un motivo — una adopción errónea insertaría la conversación de una persona en el chat de otra, así que se niega a adivinar.
  • La reparación: estampa a la huérfana con la identidad del predecesor vía COALESCE (nunca sobrescribe un valor existente), registra el lineage y retira al predecesor con end_reason='superseded_by_repair' — deliberadamente no es el motivo de reset normal, para que el siguiente reinicio no pueda volver a desviarse.

¿Estás afectado?

Comprueba el perfil antes de entrar en pánico:

  • Solo sesiones de gateway: sesiones de Telegram, Discord, Slack, WhatsApp y otras plataformas de mensajería. Los usuarios de CLI puro son estructuralmente inmunes — la CLI no tiene nada de esta maquinaria de enrutado de sesiones.
  • Mayor riesgo: sesiones de larga duración, cualquiera que reinicie/actualice/haga caer el gateway, y conversaciones con mucho uso multimodal.
  • No es algo puntual: la misma instalación muestra 5 incidentes de sesiones huérfanas desde junio (42, 34, 5, 798 y 2 mensajes) — este era simplemente el más grande, y le ocurrió al desarrollador principal.
  • Síntoma típico: tras un reinicio del gateway, Hermes habla de temas antiguos, no recuerda los días recientes y hace referencia a cosas terminadas hace tiempo.

Actualizar y recuperar tus datos

Importante: la corrección está en main (fusionada el 2026-08-09) pero aún no está en ninguna release — la última release sigue siendo la v0.20.0 (2026-08-03). Se recomienda a los usuarios de larga trayectoria actualizar, por una de estas dos vías:

  1. Esperar a la próxima release, o
  2. Ejecutar desde main para tener cobertura inmediata (consulta la guía de instalación y la referencia del comando de actualización).

¿Ya estás afectado y quieres recuperar tu conversación?:

  • Opción preferida: actualiza a una build que contenga la corrección y ejecuta hermes sessions repair-routing (dry-run primero, luego --apply).
  • Procedimiento manual (del issue oficial, cuando la herramienta no está disponible):
    1. Detén el gateway;
    2. Haz una copia de seguridad primero: cp state.db state.db.bak;
    3. Copia session_key, chat_id, chat_type, origin_json, etc. de la fila «original» obsoleta a la fila huérfana (rellena solo los huecos, nunca sobrescribas);
    4. Establece ended_at y end_reason='superseded_by_repair' en la fila obsoleta;
    5. Reinicia el gateway y deja que la resolución encuentre la sesión real.

Haz una copia de state.db antes de cualquier paso manual y asegúrate de que el gateway esté completamente detenido. Ante la duda, espera a la release con la corrección y usa repair-routing — es más seguro que editar a mano.

Lo que nos enseña este incidente

La parte más aterradora no es el bug en sí — es el silencio. La bifurcación de sesión ocurrió en silencio en segundo plano; la continuidad en memoria mantuvo la ilusión de que todo iba bien, hasta que un reinicio lo destapó. Dos aprendizajes para cualquiera que gestione un asistente de IA de larga duración: primero, comprueba la continuidad de sesión tras reinicios y actualizaciones; segundo, los datos de Hermes rara vez desaparecen — una conversación «perdida» suele ser un problema de enrutado, no de datos.

Para profundizar en la gestión de sesiones, consulta la referencia del comando hermes sessions; para la última release mayor, mira las notas de la release v0.20.0.