Cuando Hermes dice que ha adjuntado el archivo y tu plataforma no recibe nada

El peor tipo de bug es aquel en el que el agente está seguro de lo que dice y el usuario no ve nada. Le pides a Hermes que envíe un informe a tu propia plataforma de chat — una app móvil, una herramienta interna, un bridge que escribiste tú — y responde «enviado, el archivo va adjunto». No llega nada. Vuelves a pedirlo, el agente se disculpa y lo reenvía, y sigue sin llegar nada, porque el asistente no tiene forma de saber que el adjunto se cayó en algún punto por debajo.
El síntoma y el error exacto
La plataforma en cuestión no está rota. Implementa send_document, send_image_file y send_video, y su sender maneja media_files. Simplemente está fuera del conjunto de plataformas que la tool send_message considera capaces de enviar multimedia. Pídele que entregue solo multimedia y la tool lo rechaza de plano:
send_message MEDIA delivery is currently only supported for telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp, slack and qqbot; target mobile had only media attachments
Envía texto y un archivo y obtendrás el fallo más silencioso: el texto pasa, los adjuntos se descartan con un warning que solo aparece en el tool result. Desde el lado del usuario el mensaje parece enviado, y precisamente por eso el maintainer que reportó el problema describió el resultado como su agente diciéndole una y otra vez a un usuario que «el archivo va adjunto» mientras no llegaba nada.
Dónde se toma la decisión
tools/send_message_tool.py responde a la pregunta «¿puede esta plataforma recibir archivos?» en dos sitios, y solo uno de ellos tiene que ver con tu plugin:
- Una nota legible por humanos, usada en ese mensaje de error:
telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp, slack and qqbot. - Un dict, que es el que realmente habilita la ruta de plugins, y que es mucho más corto:
_PLUGIN_STANDALONE_MEDIA = {
"discord": ("Discord", False, True, [], False),
"feishu": ("Feishu", True, False, None, False),
"slack": ("Slack", True, True, [], False),
"whatsapp": ("WhatsApp", True, True, None, True),
}
Cada valor es una tupla (label, discover_plugins_first, caption_capable, media_files_sentinel, pass_force_document), según el comentario que tiene justo encima. La lista existe porque construir archivos dentro de los payloads de esas cuatro plataformas requiere un conocimiento que el sender genérico no tiene — descubrimiento forzado del registro de plugins, manejo de captions, o una forma concreta para los chunks de multimedia no finales.
Cualquier plataforma plugin que no esté en ese dict cae a la ruta genérica de solo texto: el texto sobrevive, los adjuntos se descartan y una llamada solo de multimedia falla tal como se muestra arriba. No se consulta nada del código del propio plugin, y eso es lo que escuece — la plataforma puede ser totalmente capaz y aun así resultar invisible para la decisión.
Por qué «solo añade la plataforma» no es el fix completo
El maintainer que reportó el problema hizo el experimento obvio: añadir localmente una tupla para su plataforma (("Mobile App", True, True, [], False)) y la tool enruta el adjunto a través del standalone_sender_fn del plugin. El archivo llegó como frame de multimedia — kind=document, mime=text/markdown, idéntico byte a byte al original. Así que la fontanería funciona; lo que no funciona es la puerta.
Pero un dict hardcodeado significa que toda futura plataforma plugin necesita una edición en el core antes de poder enviar un archivo, y la persona que sabe que la plataforma es capaz de enviar multimedia nunca es la persona que edita el core. La issue propone la salida obvia: deja que el plugin lo declare en su entrada de registro — algo como media_files_supported y un flag de captions, leído allí donde _plugin_standalone_sender resuelve el sender — para que la capacidad viaje con el plugin en lugar de vivir en una allow-list del core.
Hubo una segunda capa en esa caza de bugs, y es un recordatorio útil para cualquiera que escriba un adaptador de plugin: el maintainer también encontró dos errores del lado del adaptador, corregidos en la versión 1.1.1 de su adaptador. Los descriptores de multimedia llegan como tuplas (path, is_voice), y su sender las había tratado como strings simples, así que no se recogía ningún archivo. En otras palabras, «mi plataforma es capaz de enviar multimedia» tiene que ser cierto de extremo a extremo — la tool tiene que enrutar el adjunto y el adaptador tiene que entender lo que recibe.
Si te topas con esto hoy
- Mira el tool result, no el chat. Cuando un agente informa de que un archivo se ha enviado, el motivo por el que desapareció un adjunto está escrito en el tool result de
send_message, no en la respuesta del asistente. Si estás depurando un bridge propio, esa cadena es lo primero que hay que leer. - Mantén texto y multimedia separados mientras trabajas. Los mensajes de solo texto siempre funcionan en una plataforma plugin; un envío solo de multimedia falla de forma ruidosa, lo que en realidad es más útil para diagnosticar.
- Para autores de plugins: registra la cuestión de la capacidad en tu registro en lugar de darla por supuesta, y asegúrate de que tu
standalone_sender_fnmaneja la forma de tupla que recibe. Si necesitas adjuntos hoy, tus opciones son un parche local al dict en tu propio checkout de Hermes, o esperar a un cambio en el core — ninguna de las dos es algo que los usuarios deban tener que hacer en silencio. - Para el resto: si usas una plataforma plugin y la entrega de archivos te importa, abre una issue o vota por el flag declarado en el registro. Una entrada por plataforma en un dict del core es una carga de mantenimiento que una capacidad declarada elimina.
Estado
Reportado como issue #139354 el 15 de octubre de 2026, con un caso reproducible contra un plugin móvil y el fix verificado en local que se describe arriba. A fecha de esta escritura el comportamiento del core sigue igual: el dict de cuatro plataformas es lo que decide, y el diseño sugerido en la issue — una capacidad declarada por el plugin — es una propuesta, no un cambio ya publicado.
Vale la pena mantener la perspectiva: send_message es una tool, y esta es una de sus puertas. Si estás construyendo una plataforma plugin de forma más amplia, la entrada de hermes-mobile en el ecosistema es un ejemplo real de un adaptador que tuvo que arreglar este tipo de problema desde el lado de la plataforma, y la referencia del comando hermes send cubre la superficie de CLI que la tool envuelve.
Fuentes: issue #139354 en el repositorio de Hermes Agent (incluyendo la verificación local de quien reportó el problema y los arreglos del adaptador en la versión 1.1.1); tools/send_message_tool.py en main para _PLUGIN_STANDALONE_MEDIA y la nota de plataformas multimedia usada en el mensaje de error.