hermes profile update apagava as suas skills e os seus cron jobs

Seu time empacotou um agente Hermes como repositório git: quem precisa dele roda hermes profile install e recebe tudo — personalidade, skills, cron jobs, config. Você adicionou dois toques seus nesse profile: uma skill que responde perguntas de compliance, e um cron job que publica um resumo de status toda manhã. Uma release do upstream chega, você segue a documentação e roda hermes profile update. A atualização funciona, e a sua skill e o seu cron job sumiram.
Isso não foi erro do usuário. O PR #111344 corrige um bug P1 de perda de dados: atualizar uma distribution tratava skills/ e cron/ como diretórios que podia substituir por inteiro.
Duas formas de compartilhar um profile, e só uma delas machuca
Um profile do Hermes é um agente autocontido: config, credenciais, memória, skills e cron jobs próprios. Compartilhá-lo com outra pessoa tem dois caminhos, e eles respondem a perguntas diferentes:
- Uma distribution (repositório git): o autor escreve um
distribution.yaml, outras pessoas rodamhermes profile install <repo>e acompanham o upstream comhermes profile update. Escolha esta quando o agente é um produto que você vai continuar melhorando — o agente de compliance interno de um time, uma release da comunidade, o mesmo agente em cinco máquinas. - Um arquivo de export (snapshot
.tar.gz):/exporte/import, ouhermes profile export/import. Escolha esta quando você só quer que alguém tenha a sua configuração agora. Não há versionamento.
O bug só atingiu o primeiro caminho, porque só uma distribution faz atualizações que sobrescrevem. Nosso guia de profiles multi-instância cobre as fronteiras entre os dois, e a referência do comando hermes profile tem o detalhe no nível do comando.
Por que ele apagava: substituir o diretório inteiro
A lógica antiga de atualização tratava diretórios de propriedade do autor como unidades de substituição no lugar: apagar o skills/ de destino e copiar por cima o skills/ do novo payload. Do ponto de vista do autor isso está correto — garante que arquivos que o autor retirou realmente desapareçam, em vez de ficarem para sempre.
O que ela não conseguia distinguir era quais roots pertencem ao autor e quais foram adicionados pelo usuário. skills/curator-custom/ é seu, mas do ponto de vista do updater é indistinguível de skills/shipped/, então a substituição do diretório inteiro o levava junto com o resto. O mesmo valia para o cron/mine.json. E hermes profile update significa “atualizar”, não “resetar” — ninguém espera que as próprias adições desapareçam.
A nova regra: tratar um diretório de topo como um conjunto de roots e mesclar cada um
A correção trata todo diretório de topo de propriedade do autor (skills/, cron/ e qualquer diretório declarado no manifest) como um contêiner de roots em vez de uma unidade (_copy_dist_payload em hermes_cli/profile_distribution.py):
- um root que o novo payload traz substitui o seu equivalente por inteiro — arquivos que o autor retirou realmente desaparecem;
- um root que o payload não traz fica intacto, porque nunca fez parte daquela release;
- a mesma lógica vale para
updatee parainstall --force, tanto para o conjunto padrão de propriedade quanto para umdistribution_owned: [skills]explícito.
A evidência mais clara é a tabela de antes/depois do próprio PR:
| Cenário | Antes | Depois |
|---|---|---|
o seu skills/curator-custom/ |
apagado | preservado |
o seu cron/mine.json |
apagado | preservado |
| a skill publicada pelo autor atualizada para a v2 | atualizada | atualizada |
| um arquivo que o autor retirou | removido | removido |
As quatro linhas juntas são o comportamento real: proteger as suas adições e acompanhar a release do autor agora valem ao mesmo tempo, em vez de forçar uma escolha.
Três casos extremos que vale conhecer
Entradas aninhadas de allowlist ainda substituem exatamente aquele caminho. Se o manifest lista skills/compliance/, só esse caminho é substituído — os roots irmãos ficam intactos.
Um contêiner que é symlink é recusado em vez de ser escrito através dele. Se skills/ é um symlink para um diretório compartilhado, a atualização levanta um DistributionError antes da primeira escrita. Isso importa mais do que parece: escrever através do link sobrescreveria o diretório compartilhado e poderia deixar o profile atualizado pela metade, o que é bem mais difícil de diagnosticar do que uma falha dura.
.env.template continua chegando como .env.EXAMPLE. Ele é desvinculado primeiro para não poder ser escrito através de um symlink, e nunca faz sombra ao seu .env real.
O que você deve fazer
Em uma versão que contém essa correção, basta rodar a atualização de novo — não há nada para migrar:
hermes profile update
Para saber qual é a superfície de propriedade do autor de um profile, leia distribution_owned no manifest dele; caminhos aninhados ali (como skills/compliance/) limitam a substituição exatamente àquele caminho. A lista completa de flags, incluindo --force, está em:
hermes profile update --help
Se você já perdeu skills ou cron jobs adicionados por você por causa da antiga substituição de diretório inteiro, eles não voltam automaticamente — restaure-os de um backup ou de outra máquina. Nosso guia sobre onde as skills ficam cobre como reconstruí-las na mão, e cron jobs que seguem um profile explica onde os jobs agendados devem ficar.
A versão curta
O hermes profile update agora substitui apenas o que o autor publicou; skills e cron jobs que você adicionou ao profile continuam onde estão. Se você mantém um agente de time compartilhado, isso significa personalizar localmente sem se perguntar se a próxima release do upstream vai fazer uma limpeza geral.