Hermes corrigió 4 bugs escurridizos en un día: adjuntos, API keys, actualizaciones y búsqueda

¿Alguna vez has tenido uno de esos momentos de «algo no cuadra»? Le pides a Hermes que envíe un archivo a Telegram, responde «enviado» — pero el adjunto nunca aparece. O ejecutas varios profiles en una misma máquina, cambias de modelo y te das cuenta de que las credenciales que se están usando no parecen tuyas. O buscas en tu historial de sesiones una frase que estás seguro de haber dicho y no obtienes nada. No es cosa de tu imaginación: son cuatro bugs profundos de Hermes Agent que se corrigieron todos el 9 de agosto de 2026. En este post desglosamos cada uno: cómo se veía, por qué ocurría y cómo funciona la corrección.
Bug 1: Los adjuntos desaparecían en silencio — decía «enviado», pero no llegaba nada
El caso: Le pides a Hermes en Telegram que genere un informe en PDF. Responde «informe generado, enviando ahora». Pero tu chat solo muestra una línea extraña de texto: MEDIA:/path/to/report.pdf — sin archivo, solo la ruta literal. Y peor: a veces ni siquiera eso; registra «entrega confirmada» y no ocurre nada.
Causa raíz: El bug vivía en la ruta de entrega en cola (queued delivery). Cuando una respuesta tenía que ponerse en cola (mientras el turno anterior seguía en marcha, durante las demotions de subagente/compresión, ráfagas de fotos o el modo de cola), la respuesta del primer turno salía por una ruta lateral que se saltaba por completo el manejo de MEDIA: las respuestas no transmitidas en streaming se enviaban mediante adapter.send() crudo, con la etiqueta MEDIA: literal como texto y sin archivo; las ya transmitidas registraban «entrega confirmada» y descartaban los adjuntos en silencio. Hermes te decía que se había generado un archivo — el archivo nunca llegaba.
La corrección: Una nueva función _deliver_queued_first_response() ahora gestiona las entregas en cola de forma uniforme: separa el texto de los adjuntos usando la maquinaria existente de extract_media + _deliver_media_from_response (conservando el filtrado de seguridad de rutas) y lo enruta todo por el flujo estándar de entrega de adjuntos. También hay una guardia sensata: si el primer turno falló de verdad, entrega el texto de error normalizado pero nunca sube adjuntos — se acabó disfrazar fallos como éxitos.
Lo que debes saber: Si alguna vez recibiste una línea de texto MEDIA: o un adjunto que faltaba en Telegram, Discord o cualquier plataforma de gateway, esto era. Tras la corrección, los adjuntos llegan correctamente; antes de actualizar, puedes recurrir al explorador de archivos o a web_extract para inspeccionar los archivos generados.
Bug 2: API keys que cruzaban profiles — al cambiar de modelo se leía la key de otro
El caso: Ejecutas varios profiles en una misma máquina (por ejemplo, trabajo y personal), cada uno con sus propias API keys de proveedor de modelos. Un día cambias a tu profile personal y abres el selector de modelos — la lista de «proveedores autenticados» muestra proveedores autenticados con las keys de tu profile de trabajo. Estabas a un clic de enviar una solicitud con la key equivocada.
Causa raíz: Con profiles multiplexados, las lecturas de credenciales durante el cambio de modelo se saltaban el ámbito de secretos por profile. Había dos puntos afectados: la lista «qué proveedores están autenticados» del selector y la resolución real de keys de switch_model — esta última leía en crudo el entorno del proceso (expansión ${VAR} y fallback de key_env) y entregaba el resultado a la resolución en runtime como una API key explícita. Un profile podía, por tanto, ver — o adoptar — las API keys de otro profile.
La corrección: Un nuevo helper _scoped_key_env() enruta ambas lecturas a través de agent.secret_scope.get_secret. Con la multiplexación desactivada, el comportamiento es byte a byte idéntico a os.getenv (los usuarios de un solo profile no se ven afectados). Con ella activada, las lecturas quedan estrictamente confinadas al ámbito de secretos del profile actual. La decisión de diseño clave: fail-closed. Si se lanza un UnscopedSecretError, se produce un error en lugar de caer en las variables de entorno — las keys nunca se filtran en silencio entre profiles.
Lo que debes saber: Es el único problema type/security de los cuatro — va sobre el aislamiento de keys, así que los usuarios de varios profiles deberían actualizar en cuanto puedan. El comportamiento con un solo profile no cambia en absoluto.
Bug 3: Procesos huérfanos tras las actualizaciones — actualizaciones atascadas en el escritorio de Windows
El caso: Estás en Windows, usando la app de escritorio de Hermes. Haces clic en actualizar, pero la nueva versión no arranca — los procesos antiguos siguen reteniendo el entorno virtual, los archivos están bloqueados y la actualización no deja de fallar. El Administrador de tareas muestra un cementerio de procesos backend «sin padre» que se niegan a morir.
Causa raíz: Durante las actualizaciones del escritorio, releaseBackendLock() enviaba SIGTERM al backend principal antes de que se ejecutara forceKillProcessTree(). En Windows ese orden es fatal: si el launcher sale antes de que se ejecute taskkill /T, Windows ya no puede enumerar sus descendientes — los procesos hijo sobreviven, retienen el venv y se convierten en huérfanos.
La corrección: Una nueva función stopBackendTreesForUpdate() mata el árbol de la raíz viva primero (sin pre-señalización) y luego gestiona el teardown del pool. La clasificación de huérfanos también pasó a ser consciente del árbol: las raíces huérfanas del escáner se devuelven junto con sus descendientes (incluido el worker intérprete gestionado por uv re-ejecutado, cuyo padre vivo es la propia raíz huérfana) — taskkill /T siega entonces todo el subárbol de una sola vez. Los backends con un padre genuinamente vivo se dejan en paz.
Lo que debes saber: Esta corrección solo afecta al flujo de actualización del escritorio de Windows. Los usuarios de Linux/macOS no se ven afectados; si te has topado con «actualización atascada / procesos sobrantes» en Windows, esto debería mejorarlo drásticamente.
Bug 4: La búsqueda fallaba en silencio — las consultas cotidianas no devolvían nada
El caso: Buscas en tu historial de sesiones gateway/run.py — el archivo del que seguro hablaste — y obtienes «sin resultados». Pruebas con it's, user@host, 50%. Todo vacío. Empiezas a dudar de tu memoria. Las palabras estaban allí; la búsqueda estaba rota.
Causa raíz: La búsqueda de sesiones usa la búsqueda de texto completo FTS5 de SQLite, pero el saneador de consultas solo eliminaba seis caracteres (+{}():"^). La gramática de FTS5 rechaza muchos más — apóstrofos, barras, @, comas, signos de interrogación, signos igual, punto y coma, exclamaciones, barras verticales, tildes, almohadillas, signos de dólar, corchetes, paréntesis angulares, barras invertidas — y cualquiera de ellos que llegara crudo a MATCH lanzaba un OperationalError, que el sitio de consultas tragaba convirtiéndolo en cero resultados. No estabas buscando «nada»; la consulta estaba fallando.
La corrección: La clase de eliminación del saneador se reconstruyó (mediante re.escape, que también arregló una pérdida de barra invertida literal) para cubrir todo el conjunto de rechazos, con casos especiales inteligentes: las frases exactas entre comillas ("frase exacta") sobreviven mediante extracción de placeholders, los términos con puntos o guiones no se tocan, y % solo se elimina en consultas no-CJK — porque % es un carácter reservado para el fallback LIKE de CJK, y las consultas CJK nunca llegan a la ruta de error de FTS5 de todos modos.
Lo que debes saber: Tras la corrección, it's, gateway/run.py, user@host y 50% se parsean y coinciden correctamente; las consultas CJK no se ven afectadas en absoluto. Si alguna vez sentiste que la búsqueda de Hermes era poco fiable, probablemente este bug era el motivo.
Resumen: un día de merge, cuatro lecciones
| Bug | Síntoma | Gravedad | Afecta a |
|---|---|---|---|
| Adjuntos que desaparecen | Texto MEDIA: literal / archivos descartados en silencio |
Funcional | Todas las plataformas de gateway (Telegram, Discord, …) |
| API keys cruzadas | El cambio de modelo lee la key de otro profile | Seguridad | Usuarios con profiles multiplexados |
| Huérfanos de actualización | Los procesos antiguos bloquean el venv tras actualizar en Windows | Funcional | Escritorio de Windows |
| Fallos de búsqueda silenciosos | Las consultas con caracteres especiales devuelven vacío | Funcional | Todos |
El hilo común de los cuatro: los fallos eran silenciosos. Los adjuntos se descartaban pero se reportaban como enviados; las consultas fallaban pero se mostraban como «sin resultados»; las keys se leían mal sin ningún error. Por eso precisamente eran tan difíciles de encontrar — la primera reacción natural es dudar de ti mismo, no del software. Los cuatro están corregidos ahora; si alguno te mordió antes, merece la pena reintentar esas operaciones después de actualizar.
Nota: Estas correcciones aterrizaron en la rama
mainde Hermes Agent el 2026-08-09 y todavía no están en un tag de release (la última release sigue siendo v0.20.0). Para usarlas hoy, ejecuta desde el código fuente — o espera a la próxima release.