Quando o Hermes diz que anexou o arquivo e sua plataforma não recebe nada


O pior tipo de bug é aquele em que o agente está confiante e o usuário não vê nada. Você pede ao Hermes para enviar um relatório para a sua própria plataforma de chat — um app mobile, uma ferramenta interna, uma ponte que você mesmo escreveu — e ele responde “enviado, o arquivo está anexado”. Nada chega. Você pede de novo, o agente se desculpa e reenvia, e ainda assim nada chega, porque o assistente não tem como saber que o anexo foi descartado em algum ponto abaixo dele.

O sintoma e o erro exato

A plataforma em questão não está quebrada. Ela implementa send_document, send_image_file e send_video, e o sender dela trata media_files. Ela simplesmente está fora do conjunto de plataformas que a ferramenta send_message considera capazes de mídia. Peça a ela para entregar apenas mídia e a ferramenta recusa na hora:

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

Envie texto e um arquivo e você tem a falha mais silenciosa: o texto passa, os anexos são descartados com um aviso que só aparece no resultado da ferramenta. Do lado do usuário, a mensagem parece enviada, e é exatamente por isso que o mantenedor que reportou descreveu o desfecho como o agente repetidamente dizendo ao usuário “the file is attached” enquanto nada chegava.

Onde a decisão é tomada

tools/send_message_tool.py responde à pergunta “esta plataforma consegue receber arquivos?” em dois lugares, e só um deles diz respeito ao seu plugin:

  • Uma nota legível por humanos, usada naquela mensagem de erro: telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp, slack and qqbot.
  • Um dict que de fato habilita o caminho do plugin, bem mais curto:
_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 é uma tupla (label, discover_plugins_first, caption_capable, media_files_sentinel, pass_force_document), conforme o comentário acima dela. A lista existe porque montar arquivos nos payloads dessas quatro plataformas exige um conhecimento que o sender genérico não tem — descoberta forçada do registro de plugins, tratamento de legenda, ou um formato específico para chunks de mídia que não são o final.

Qualquer plataforma de plugin fora desse dict cai no caminho genérico apenas de texto: o texto sobrevive, os anexos são descartados, e uma chamada só de mídia falha como mostrado acima. Nada no código do próprio plugin é consultado, e é essa a parte que incomoda — a plataforma pode ser totalmente capaz e ainda assim ser invisível para a decisão.

Por que “só adicionar a plataforma” não é a correção inteira

O mantenedor que reportou fez o experimento óbvio: adicionou localmente uma tupla para a plataforma dele (("Mobile App", True, True, [], False)) e a ferramenta roteia o anexo pelo standalone_sender_fn do plugin. O arquivo chegou como um frame de mídia — kind=document, mime=text/markdown, idêntico byte a byte ao original. Ou seja, o encanamento funciona; o que não funciona é o portão.

Mas um dict fixo no código significa que toda futura plataforma de plugin precisa de uma edição no core antes de conseguir enviar um arquivo, e quem sabe que a plataforma é capaz de mídia nunca é quem edita o core. A issue propõe a saída óbvia: deixar o plugin declarar isso na entrada do seu registro — algo como media_files_supported e uma flag de legenda, lidos onde _plugin_standalone_sender resolve o sender — para que a capacidade viaje junto com o plugin em vez de morar numa allow-list do core.

Havia uma segunda camada nessa caça ao bug, e ela é um lembrete útil para quem escreve um adaptador de plugin: o mantenedor também encontrou dois erros do lado do adaptador, corrigidos na versão 1.1.1 do adaptador dele. Descritores de mídia chegam como tuplas (path, is_voice), e o sender dele as tratava como strings simples, então nenhum arquivo era capturado. Em outras palavras, “minha plataforma é capaz de mídia” precisa ser verdade de ponta a ponta — a ferramenta tem que rotear o anexo e o adaptador tem que entender o que recebe.

Se você esbarrar nisso hoje

  • Olhe o resultado da ferramenta, não o chat. Quando um agente reporta um arquivo como enviado, o motivo pelo qual o anexo desapareceu está escrito no resultado da ferramenta send_message, não na resposta do assistente. Se você está depurando uma ponte sua, essa string é a primeira coisa a ler.
  • Mantenha texto e mídia separados enquanto trabalha. Mensagens só de texto sempre funcionam numa plataforma de plugin; um envio só de mídia falha de forma ruidosa, o que na verdade é mais útil para diagnóstico.
  • Para autores de plugins: acompanhe a questão de capacidade no seu registro em vez de presumi-la, e garanta que o seu standalone_sender_fn trate o formato de tupla que recebe. Se você precisa de anexos hoje, suas opções são um patch local no dict do seu próprio checkout do Hermes, ou esperar por uma mudança no core — nenhuma das duas deveria ser algo que usuários tenham que fazer em silêncio.
  • Para todo o resto: se você usa uma plataforma de plugin e a entrega de arquivos importa, abra ou vote pela flag declarada no registro. Uma entrada por plataforma num dict do core é um fardo de manutenção que uma capacidade declarada elimina.

Status

Reportado como issue #139354 em 15 de outubro de 2026, com um caso reproduzível contra um plugin mobile e a correção verificada localmente descrita acima. No momento em que escrevo, o comportamento do core está inalterado: o dict de quatro plataformas é o que decide, e o design sugerido na issue — uma capacidade declarada pelo plugin — é uma proposta, não uma mudança já entregue.

Vale manter em perspectiva: send_message é uma ferramenta, e este é um dos seus portões. Se você está construindo uma plataforma de plugin de forma mais ampla, a entrada do hermes-mobile no ecossistema é um exemplo que funciona de um adaptador que precisou corrigir esse tipo de problema pelo lado da plataforma, e a referência do comando hermes send cobre a superfície de CLI que a ferramenta embrulha.


Fontes: issue #139354 no repositório do Hermes Agent (incluindo a verificação local de quem reportou e as correções do adaptador na versão 1.1.1); tools/send_message_tool.py na main para _PLUGIN_STANDALONE_MEDIA e a nota de plataformas de mídia usada na mensagem de erro.