hermes profile update borraba tus skills y tus tareas cron

Tu equipo empaquetó un agente de Hermes como repositorio git: quien lo necesite ejecuta hermes profile install y se lo lleva entero — personalidad, skills, tareas cron, configuración. Tú añadiste dos detalles propios a ese profile: una skill que responde preguntas de cumplimiento y una tarea cron que publica un resumen de estado cada mañana. Llega una versión upstream, sigues la documentación y ejecutas hermes profile update. La actualización termina bien, y tu skill y tu tarea cron han desaparecido.
No fue un error tuyo. El PR #111344 corrige un fallo P1 de pérdida de datos: al actualizar una distribución, skills/ y cron/ se trataban como directorios que se podían reemplazar de golpe.
Dos formas de compartir un profile, y solo una te hace daño
Un profile de Hermes es un agente autocontenido: su propia configuración, credenciales, memoria, skills y tareas cron. Compartirlo con otra persona tiene dos caminos, y responden a preguntas distintas:
- Una distribución (repositorio git): el autor escribe un
distribution.yaml, los demás ejecutanhermes profile install <repo>y siguen el upstream conhermes profile update. Elige esto cuando el agente sea un producto que vas a seguir mejorando — el agente interno de cumplimiento de un equipo, un lanzamiento comunitario, el mismo agente en cinco máquinas. - Un archivo de exportación (instantánea
.tar.gz):/exporty/import, ohermes profile export/import. Elige esto cuando solo quieras que alguien tenga tu configuración ahora mismo. No hay versionado.
El fallo solo afectaba al primer camino, porque solo una distribución hace actualizaciones que sobrescriben. Nuestra guía de profiles multi-instancia cubre las fronteras entre ambos caminos, y la referencia del comando hermes profile tiene el detalle a nivel de comando.
Por qué borraba: reemplazar el directorio entero
La lógica antigua de actualización trataba los directorios del autor como unidades de reemplazo in situ: borrar el skills/ de destino y copiar encima el skills/ del payload nuevo. Desde el punto de vista del autor eso es correcto: garantiza que los archivos que el autor retiró desaparezcan de verdad en lugar de quedarse por ahí para siempre.
Lo que no sabía distinguir era qué roots pertenecen al autor y cuáles añadió el usuario. skills/curator-custom/ es tuyo, pero desde la perspectiva del updater es indistinguible de skills/shipped/, así que un reemplazo de directorio completo se lo llevaba junto con el resto. Lo mismo con cron/mine.json. Y hermes profile update significa «actualizar», no «resetear»: nadie espera que sus propios añadidos se esfumen.
La nueva regla: tratar cada directorio de primer nivel como un conjunto de roots y fusionarlos uno a uno
El arreglo trata cada directorio de primer nivel propiedad del autor (skills/, cron/ y cualquier directorio que declare el manifiesto) como un contenedor de roots en lugar de una unidad (_copy_dist_payload en hermes_cli/profile_distribution.py):
- un root que el payload nuevo sí incluye reemplaza a su equivalente por completo — los archivos que el autor retiró desaparecen;
- un root que el payload no incluye se deja intacto, porque nunca formó parte de esa versión;
- la misma lógica se aplica a
updatey ainstall --force, tanto para el conjunto por defecto como para undistribution_owned: [skills]explícito.
La prueba más clara es la tabla de antes y después del propio PR:
| Escenario | Antes | Después |
|---|---|---|
tu skills/curator-custom/ |
eliminado | preservado |
tu cron/mine.json |
eliminado | preservado |
| la skill que publica el autor, actualizada a v2 | actualizada | actualizada |
| un archivo que el autor retiró | desaparece | desaparece |
Las cuatro filas juntas son el comportamiento real: proteger tus añadidos y seguir la versión del autor ahora se cumplen a la vez, en lugar de obligarte a elegir.
Tres casos límite que conviene conocer
Las entradas anidadas de la allowlist siguen reemplazando exactamente esa ruta. Si el manifiesto lista skills/compliance/, solo se reemplaza esa ruta: los roots hermanos no se tocan.
Un contenedor que es un enlace simbólico se rechaza en vez de escribirse a través de él. Si skills/ es un symlink a un directorio compartido, la actualización lanza un DistributionError antes de la primera escritura. Eso importa más de lo que suena: escribir a través del enlace sobrescribiría el directorio compartido y podría dejar el profile a medio actualizar, algo mucho más difícil de diagnosticar que un fallo duro.
.env.template sigue aterrizando como .env.EXAMPLE. Se desenlaza primero para que no se pueda escribir a través de un symlink, y nunca hace sombra a tu .env real.
Qué deberías hacer
En una versión que incluya este arreglo, basta con ejecutar la actualización otra vez — no hay nada que migrar:
hermes profile update
Para saber qué superficie pertenece al autor en un profile dado, lee distribution_owned en su manifiesto; las rutas anidadas que aparezcan ahí (como skills/compliance/) limitan el reemplazo a esa ruta exacta. La lista completa de flags, incluido --force, está en:
hermes profile update --help
Si ya has perdido skills o tareas cron que habías añadido por culpa del antiguo reemplazo de directorio completo, no vuelven solas: restáuralas desde una copia de seguridad o desde otra máquina. Nuestra guía sobre dónde viven los skills cubre cómo reconstruirlos a mano, y tareas cron que siguen a un profile explica dónde deben estar los trabajos programados.
La versión corta
hermes profile update ahora reemplaza solo lo que publicó el autor; las skills y las tareas cron que añadiste al profile se quedan donde están. Si mantienes un agente compartido de equipo, eso significa que puedes personalizarlo en local sin preguntarte si la próxima versión upstream va a hacer limpieza.