Seu plugin parou de carregar? Os caminhos de import antigos morreram em 2026-09-14

Você escreveu um plugin pequeno, ele funcionou por um mês e hoje simplesmente parou. Depois de um hermes update e de um restart do gateway, o bot fica quieto, hermes plugins list diz que seu plugin não está carregado, e o log contém um único ImportError. Você não tocou em uma linha de código.
Isso não é um sistema de plugins instável. É uma limpeza programada: a janela de compatibilidade fechou. Veja por que a camada existia, o que exatamente foi removido e como trazer seu plugin de volta.
1. A decomposição de setembro moveu caminhos internos
Em setembro de 2026 o Hermes dividiu seus módulos grandes em arquivos irmãos <stem>_<topic> (PR #102117). Os usuários quase não notam. Plugins que importam de caminhos internos notam na hora: os nomes em que eles confiavam não estão mais definidos onde costumavam estar.
O projeto sempre foi explícito sobre isso: caminhos de import internos nunca fizeram parte do contrato de plugins. O Native plugin compatibility contract documentado protege comportamento — plugin.yaml, register(ctx), payloads de hook, interfaces de provider — não a localização de um módulo Python.
Mas a decomposição teria quebrado plugins existentes da noite para o dia, então uma camada de compatibilidade temporária foi enviada junto, com data de expiração publicada: 2026-09-14. E ela fez bem mais do que o nome sugere:
- 328 blocos
PLUGIN-COMPATanexados aos módulos afetados; - 1.148 nomes públicos movidos reexpostos através de tabelas de ponteiros
__getattr__preguiçosas (lazy, para evitar ciclos de import); - 592 nomes de terceiros ou da stdlib restaurados, porque o módulo antigo costumava expô-los por acidente;
- 290 definições públicas restauradas ao pé da letra que tinham sido deletadas como não usadas;
- 41 helpers privados restaurados e 17 imports restaurados, puramente para dar suporte às definições acima;
- 3 módulos inteiros deletados transformados em stubs para reexportar do seu substituto;
- 34 nomes que não puderam ser restaurados de forma alguma (variáveis de laço vazadas e coisas do tipo), listados apenas por completude.
Os autores de plugins tinham um verificador dedicado: hermes plugins compat <caminho-do-seu-plugin> imprimia cada file:line, caminho antigo para caminho novo, e saía com 1 enquanto restasse algo. Em runtime, a primeira resolução através de um shim levantava um HermesPluginCompatWarning (um FutureWarning), uma vez por nome por processo. Usuários que não podiam esperar por um autor tinham uma saída de emergência: plugins.allow_deprecated_imports: true.
2. O que foi removido
O PR #126164 deletou a camada em 2026-09-28: 360 arquivos, +38 / −27.749 linhas. Foram embora com ela:
- todos os 328 blocos
PLUGIN-COMPAT, mais os três módulos stub de reexportação; - as ferramentas:
COMPAT_MANIFEST.md,compat_manifest.json,scripts/check_compat_pointers.pye seu passo de lint no CI; - todas as superfícies de report: o aviso em banner na CLI,
hermes plugins compat, a seção dohermes doctor, o aviso pós-update, o diálogo único do Desktop e o skip pré-import do loader; - a própria saída de emergência —
plugins.allow_deprecated_importsnão existe mais, e defini-la não faz nada.
Três stubs inertes foram mantidos de propósito. O raciocínio é prático: um update que já estava rodando código pré-remoção pode importar esses módulos de forma lazy depois da troca de checkout, e deletá-los de vez quebraria o updater. Então hermes_cli/plugin_compat.py mantém três funções silenciosas (compat_report retorna um dict vazio, removal_in_effect retorna true, summary_lines retorna uma lista vazia) puramente para sobreviver a essa ordem.
3. O que acontece com o seu plugin
Versão curta: um plugin que ainda importa um caminho antigo não carrega. Ele falha com um ImportError, e hermes plugins list mostra o motivo. Ele não está “desabilitado” — ele nunca iniciou, e é por isso que o bot simplesmente fica mudo.
Duas formas de corrigir, em ordem de preferência:
- Use
ctxe as ABCs documentadas. A maioria das necessidades do tipo “preciso importar uma função interna” já é coberta por métodos dePluginContextou por uma classe base abstrata de provider. Código escrito assim sobrevive a um remexer de arquivos. - Se você precisa importar, importe do módulo que define o nome hoje. O mapa completo de módulo antigo para nova localização ainda existe — é o manifesto do último commit que trouxe a camada: COMPAT_MANIFEST.md (commit 5912ed81ed9). Atualize o import, reinicie o gateway, confira o status de carregamento.
Uma fronteira que vale conhecer: nomes privados nunca foram restaurados. Se seu plugin importava algo como _foo, ou fazia monkeypatch em uma costura interna, isso nunca foi coberto pela camada — mesmo que você nunca tenha visto um aviso, já pode estar quebrado. Esses casos têm de migrar para o equivalente público ou para o novo módulo.
4. Encontrando a linha exata que quebrou
hermes plugins list # status de carregamento e motivo, por plugin
hermes plugins show <name> # detalhes de um plugin (incluindo emits / listens)
hermes plugins doctor <path> # valida o próprio diretório do plugin
O teste mais direto ignora o Hermes e importa o plugin no mesmo ambiente Python:
python -c "import your_plugin"
O ModuleNotFoundError: No module named 'agent.tools' (ou qualquer nome que apareça) é a linha a mudar. Reinicie o gateway depois — plugins carregam no início do processo.
5. O que essa mudança não tocou
Se seu plugin só usa superfícies documentadas, a remoção não é problema para você:
plugin.yamleregister(ctx): métodos documentados dePluginContextnão são removidos nem renomeados; parâmetros novos são opcionais, têm valores padrão e devem ser keyword-only; campos de retorno existentes não são removidos nem silenciosamente alterados de tipo.- Payloads de hook: dados novos chegam apenas como campos de keyword, nunca mudando o significado ou a posição de um campo existente. Um callback legado recebe os campos que declara; um callback com
**kwargsrecebe o payload completo — por isso plugins novos devem sempre declarar**kwargs. - Manifestos: campos desconhecidos em
plugin.yamlsão ignorados, então um Hermes mais antigo consegue carregar um plugin com metadados de uma release mais nova. - Interfaces de provider: métodos novos têm implementação padrão, e contexto novo de callback só é encaminhado quando a inspeção de assinatura mostra que o provider o aceita.
- A política de deprecação continua com quatro condições duras: uma substituição mais instruções de migração, um aviso no máximo uma vez por processo nomeando a release de remoção mais próxima, suporte ao comportamento antigo por pelo menos mais duas releases minor, e cobertura de compatibilidade baseada em comportamento para os dois caminhos ao longo dessa janela.
Versão curta: o contrato de plugins é comportamento, não caminhos de arquivo. Escreva seguindo a documentação e o próximo refactor não vai te tocar.
Se você está começando agora, comece pelo nosso guia do catálogo de plugins; para rodar um backend de memória, o texto sobre a migração dos providers de memória conta a outra metade da história de setembro. Detalhes comando por comando estão na referência de hermes plugins, e para religar plugins em runtime veja o guia de rewire ao vivo.