hermes profile update 以前会把你的技能和定时任务一起删掉


你们团队把一个 Hermes agent 打包成了 git 仓库,谁需要就 hermes profile install 一下,立刻得到一整套人格、技能、定时任务和配置。你在这个 profile 上又加了两样自己的东西:一个帮团队查合规的技能,和一个每天早上发日报的定时任务。几天后上游发布了新版本,你照着文档跑了一条 hermes profile update ——升级成功,技能和定时任务也没了。

这不是你的操作问题。PR #111344 修的就是这个 P1 级别的数据丢失:更新一个 distribution 时,skills/ 和 cron/ 曾被当成可以整目录替换的东西。

先分清两种“共享一个 profile”的方式

Hermes 的 profile 就是一份独立的 agent:自己的配置、凭据、记忆、技能与定时任务。把它分享给别人有两条路,用途并不一样:

  • distribution(git 仓库):作者准备一份 distribution.yaml,别人用 hermes profile install <repo> 安装,之后用 hermes profile update 跟随上游更新。适合“这是一个会持续演进的产品”的场景:团队内部的合规 agent、社区发布的 agent、部署到五台机器上的同一个 agent。
  • export 文件(.tar.gz 快照):用 /export 导出、/import 导入,或者走 hermes profile export / import。适合“我现在就想让你拿到我的这套配置”,没有版本概念。

会撞上这个 bug 的是第一条路,因为只有它会覆盖式更新。两者各自的边界,我们的多实例 profile 指南里讲得更细,命令层面的细节在 hermes profile 命令参考里。

为什么会删:整目录替换

distribution 的更新逻辑,过去对“归作者所有的目录”采用的是先删后拷:把目标端的 skills/ 整个删掉,再把新 payload 的 skills/ 整个拷过去。这个做法在作者的视角里完全正确——他要保证“我退休掉的文件真的消失”,而不是留下一堆陈年旧文件。

但它无法区分两件事:目录里哪些根是作者发的,哪些是用户自己加的。skills/curator-custom/ 是你加的,在作者眼里它和 skills/shipped/ 没有区别,于是在整目录替换里一起没了;cron/mine.json 同理。而 hermes profile update 的语义是“升级”,不是“重置”,用户没有任何理由预期自己的东西会消失。

现在的规则:把顶层目录当成“一堆根”,逐根合并

修复把顶层归作者所有的目录(skills/、cron/,以及 manifest 里显式声明的目录)当作一堆独立的根来处理,而不是一个整体(hermes_cli/profile_distribution.py 的 _copy_dist_payload):

  • 新 payload 里有的根,整个替换掉对应的那一个(作者退休的文件确实会消失);
  • 新 payload 里没有的根,原样保留——那不属于作者的发布内容;
  • 对 update 和 install --force 一致生效,默认的归作者目录集与显式配置 distribution_owned: [skills] 都走同一套逻辑。

对第 2 点最直观的验证来自 PR 里的实测对照:

场景 修复前 修复后
你加的 skills/curator-custom/ 被删除 保留
你加的 cron/mine.json 被删除 保留
作者发布的技能更新到 v2 更新 更新
作者退休的旧文件 消失 消失

这四条放在一起才是完整的行为:保护用户新增和跟上作者发布同时成立,不需要你在两者之间选一个。

三个边界情况,值得单独知道

嵌套白名单仍然精确替换。 如果 manifest 里写的是 skills/compliance/ 这样的嵌套路径,替换范围就严格限定在那一条路径上,不会波及兄弟根。

符号链接会被明确拒绝,而不是“写穿”。 如果把 skills/ 做成了指向共享目录的软链接,更新会在第一次写入之前就抛出 DistributionError。这一点比它听起来重要:如果这里被写穿,共享目录里的内容会被覆盖,而 profile 可能停留在“更新了一半”的状态——比直接报错难排查得多。

.env.template 仍然落成 .env.EXAMPLE。 落盘前会先解除链接再拷贝,避免通过软链接写进别处,同时保证它永远不会覆盖你真正的 .env。

你要做什么

升级到包含这个修复的版本后,直接重跑更新即可,不需要额外迁移:

hermes profile update

想先确认这套 profile 的归作者范围,看 manifest 里的 distribution_owned 字段;如果里面写的是嵌套路径(如 skills/compliance/),替换范围就限定在那条路径。命令的完整参数(包括 --force 等)可以用:

hermes profile update --help

如果你在这之前已经因为整目录替换丢过自己加的技能或定时任务,那部分内容不会自动回来——请从备份或另一台机器上恢复;技能目录相关的那篇里有手动重建技能位置的做法,而 cron 任务随 profile 走的那篇讲清了定时任务该放在哪里。

小结

一句话记住这个修复:hermes profile update 现在只覆盖作者发布的那部分,你在这个 profile 上加的技能和定时任务留在原地。对维护团队共享 agent 的人来说,这意味着可以放心在本地做定制,而不必担心每次上游更新都清一次场。