O Hermes no Docker perdeu metade da sua árvore de dependências — a imagem nunca registrou uma baseline

Você abre o Hermes no seu contêiner Docker pela manhã e o servidor de API não sobe: aiohttp not installed. Os adaptadores de mensageria sumiram, os servidores MCP sumiram, agentes hospedados aparecem como degradados. Você não mudou nenhuma configuração, a imagem não se moveu — a única coisa que aconteceu foi que o contêiner instalou uma dependência opcional no dia anterior.
Isto não é uma imagem quebrada. É um mecanismo mais silencioso: a primeira geração de dependências que um contêiner constrói para si vira a baseline de toda inicialização seguinte. Quando as dependências embutidas na imagem nunca foram registradas, a primeira geração autoconstruída as jogou fora em silêncio.
Como uma “dependency generation” funciona
As dependências no Hermes não são uma instalação de mão única que fica parada. Elas são geridas por um dependency manager (PM) que produz uma geração a cada instalação ou atualização: ele registra uma seleção (quais extras devem estar presentes) mais um carimbo, e é contra essa geração que as inicializações posteriores são checadas.
O design repousa numa suposição: a baseline é registrada, então os incrementos podem construir sobre ela. Quando você instala uma dependência opcional de forma preguiçosa mais tarde, ela deveria se unir à baseline registrada. Se a baseline está vazia, essa única instalação preguiçosa vira a baseline e desloca todo o resto.
security.allow_lazy_installs tem true por padrão, o que significa que pacotes faltantes podem ser instalados sob demanda. Essa conveniência é exatamente o que amplifica o problema, porque qualquer instalação preguiçosa pode disparar “construir uma nova geração”.
Por que contêineres são o lugar perfeito para ser mordido
A imagem Docker faz algo inteiramente razoável por conta própria — ela embute oito extras direto no /opt/hermes/.venv:
all messaging otlp anthropic bedrock azure-identity matrix google-chat
Desempacotado, isso é o gateway, a telemetria OTLP, os SDKs e credenciais de Anthropic / Bedrock / Azure, o adaptador Matrix e o adaptador Google Chat. Tudo o que você precisa para de fato rodar.
O problema é que, no momento do build da imagem, esses extras nunca foram registrados como a seleção de dependências do PM. Então a sequência vai assim:
- o contêiner constrói a sua primeira geração por causa de alguma instalação preguiçosa — um plugin com as suas próprias dependências, ou você habilitando um canal sob demanda como fal, parallel-web ou exa;
- essa geração é construída só a partir do extra pedido naquele momento;
- da inicialização seguinte em diante, essa seleção incompleta é a baseline.
Esse é o cenário de abertura: servidor de API, adaptadores de mensageria e servidores MCP todos sumidos de uma vez. Desde a v0.21.6, qualquer contêiner na imagem oficial que construiu uma geração própria tem isso pela frente.
Vale dizer com clareza: o diagnóstico não passou batido. Outro contribuidor chegou independentemente à mesma conclusão ao mesmo tempo — nenhuma baseline registrada na imagem — e propôs uma correção para instalações novas. O que foi entregue faz mais duas coisas além da prevenção: conserta volumes que já estão quebrados e registra a lista de extras pedidos em vez de um inventário do que foi instalado.
Por que a lista pedida e não um inventário? Porque num build real de imagem o inventário acabou omitindo meta extras como all, cron e sms — extras que não possuem pacote próprio e portanto não podem ser enumerados, mas ainda assim precisam ficar na baseline.
Correção um: o build da imagem registra a sua baseline
O build da imagem agora escreve esses extras nos fatos de dependência do store, pelo exato mesmo caminho de registro que o build empacotado já usava. Toda instalação posterior é, portanto, uma união sobre essa baseline em vez de um começo do zero.
Correção dois: toda inicialização restaura o que sumiu
A prevenção só ajuda imagens novas. E os volumes já quebrados? Eles se curam na inicialização.
Cada inicialização agora compara os extras que a imagem registrou com a seleção que o volume de fato contém, e restaura qualquer coisa faltante pelo caminho de sincronização explícita normal. Um volume quebrado até ["fal"], ou até uma lista vazia, se recupera na sua primeira inicialização com uma imagem corrigida, sem passo manual.
Algumas bordas são tratadas com cuidado:
- se a restauração falha — offline, por exemplo — o venv da imagem ainda inicializa, e os extras faltantes ficam registrados como faltantes, então uma instalação preguiçosa antes da próxima inicialização não pode derrubá-los de novo;
- um extra registrado que a fonte não declara mais é ignorado (casado por nome PEP 685);
- um fato registrado malformado (extras ruins, carimbo ausente ou não-string) reporta
invalid recorded dependency stateem vez de silenciosamente ler como “nada registrado”; enabled-features.jsondeliberadamente não é escrito, entãoallow_lazy_installsse comporta exatamente como antes. Esta mudança toca a baseline e nada mais.
A mesma forma de falha, uma segunda vez: o app desktop derrubando a TUI
O mesmo lote carrega outro exemplo de “uma parte falha, tudo paga” — e desta vez quem paga é um servidor headless.
Uma atualização a partir do código-fonte prepara dependências instalando ui-tui, web e apps/desktop como um único npm ci união. Só o app desktop puxa addons nativos que precisam de um compilador: node-pty, electron. Então um servidor Ubuntu que nunca rodou o app desktop e tem só g++ 9 — que rejeita -std=gnu++20 — falhava a instalação de dependências inteira, e a TUI e a web UI nunca eram construídas mesmo sem ter nada a ver com o app desktop. Toda inicialização imprimia a source update could not be finished automatically (6 attempts).
Agora esse caso tenta de novo uma vez sem o app desktop, constrói a TUI e a web UI como de costume e registra o build do desktop como pendente:
Desktop app build owed: desktop dependencies failed
Ele também imprime uma saída genuinamente útil:
ⓘ If this host never runs the desktop app, hermes uninstall --gui removes it and
future updates stop building it.
Numa máquina headless essa é a resposta mais limpa — não carregue o app desktop de jeito nenhum:
hermes uninstall --gui
A dica aparece nas duas situações que precisam dela: depois daquela tentativa bem-sucedida e em qualquer atualização de Linux headless (sem DISPLAY ou WAYLAND_DISPLAY) que selecione o app desktop.
O que checar e o que fazer
Rodando no Docker:
- primeiro confirme que o sintoma bate — o servidor de API, os adaptadores de mensageria e os servidores MCP sumindo juntos, com agentes reportados como degradados;
- puxe a imagem corrigida e reinicie; um volume quebrado restaura os seus extras faltantes na primeira inicialização, e o volume não precisa ser recriado;
- se você ainda não pode mover a imagem, instalar os extras faltantes explicitamente vai restaurar aquela inicialização — mas a baseline ainda está errada, então a próxima instalação preguiçosa pode derrubá-los de novo.
Rodando num servidor headless:
- se as atualizações continuam falhando com
could not be finished automatically, cheque se as dependências nativas do app desktop são o bloqueio; - se este host nunca roda o app desktop, remova-o do checkout com
hermes uninstall --gui; - as atualizações futuras então param de construir
node-ptyeelectronpara um front end que você não usa.
Dois hábitos que se generalizam:
- problema de dependências raramente é “um pacote quebrou” e muito mais frequentemente uma geração cuja baseline foi escrita errado. Quando algo de repente perde um conjunto inteiro de capacidades, suspeite da baseline antes de reinstalar pacotes um por um;
- instalações preguiçosas são convenientes justamente porque transformam “faltando” em “o novo padrão”. Se você quer estabilidade de longo prazo, uma baseline registrada ganha de uma instalação ad-hoc toda vez.
A versão curta
Ambas as correções neste lote giram em torno de uma coisa: o estado de dependências precisa de um registro de baseline confiável. A imagem Docker embutiu oito extras sem registrá-los, então a primeira geração autoconstruída do contêiner os apagou; os módulos nativos do app desktop falharam ao construir e derrubaram junto uma TUI e uma web UI sem relação com eles. O primeiro é resolvido registrando no momento do build e restaurando na inicialização; o segundo, degradando para a parte que de fato é necessária.
Para a leitura operacional ao redor, veja rodar o Hermes num contêiner compartilhado, o problema do WAL do state.db e o que fazer quando uma atualização estoura o seu disco. Os detalhes dos comandos vivem nas páginas de referência do hermes uninstall e do hermes update, e o guia de instalação é por onde começar do zero.