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 rodam hermes profile install <repo> e acompanham o upstream com hermes 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): /export e /import, ou hermes 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 update e para install --force, tanto para o conjunto padrão de propriedade quanto para um distribution_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.