hermes profile update löschte früher deine Skills und Cron-Jobs


Dein Team hat einen Hermes-Agent als Git-Repository verpackt: Wer ihn braucht, führt hermes profile install aus und bekommt alles – Persönlichkeit, Skills, Cron-Jobs, Config. Du hast diesem Profil zwei eigene Extras hinzugefügt: einen Skill, der Compliance-Fragen beantwortet, und einen Cron-Job, der jeden Morgen eine Statusübersicht postet. Ein Upstream-Release landet, du folgst der Doku und führst hermes profile update aus. Das Upgrade gelingt – und dein Skill und dein Cron-Job sind weg.

Das war kein Nutzerfehler. PR #111344 behebt einen P1-Datenverlust-Bug: Das Aktualisieren einer Distribution behandelte skills/ und cron/ als Verzeichnisse, die es komplett ersetzen durfte.

Zwei Wege, ein Profil zu teilen – und nur einer beißt zu

Ein Hermes-Profil ist ein eigenständiger Agent: eigene Config, eigene Credentials, eigenes Memory, eigene Skills und Cron-Jobs. Es gibt zwei Wege, eines weiterzugeben, und sie beantworten unterschiedliche Fragen:

  • Eine Distribution (Git-Repository): Der Autor schreibt eine distribution.yaml, andere führen hermes profile install <repo> aus und folgen dem Upstream mit hermes profile update. Nimm das, wenn der Agent ein Produkt ist, das du weiter verbessern wirst – ein internes Compliance-Agent deines Teams, ein Community-Release, derselbe Agent auf fünf Maschinen.
  • Eine Export-Datei (.tar.gz-Snapshot): /export und /import oder hermes profile export / import. Nimm das, wenn einfach jemand jetzt dein Setup haben soll. Es gibt keine Versionierung.

Der Bug traf nur den ersten Weg, denn nur eine Distribution macht überschreibende Updates. Unser Leitfaden zu Multi-Instance-Profilen beschreibt die Grenzen zwischen beiden, und die hermes profile-Befehlsreferenz liefert die Details auf Befehlsebene.

Warum gelöscht wurde: das ganze Verzeichnis ersetzen

Die alte Update-Logik behandelte autor-eigene Verzeichnisse als Einheiten zum Ersetzen an Ort und Stelle: Ziel-skills/ löschen, die skills/ aus dem neuen Payload darüberkopieren. Aus Sicht des Autors ist das richtig – es garantiert, dass Dateien, die der Autor zurückgezogen hat, tatsächlich verschwinden, statt für immer liegen zu bleiben.

Was sie nicht auseinanderhalten konnte: welche Roots dem Autor gehören und welche der Nutzer hinzugefügt hat. skills/curator-custom/ gehört dir, aber aus Sicht des Updaters ist es nicht von skills/shipped/ zu unterscheiden, also nahm die Ganz-Verzeichnis-Ersetzung es mit dem Rest mit. Dasselbe bei cron/mine.json. Und hermes profile update heißt „Upgrade“, nicht „Reset“ – niemand erwartet, dass eigene Ergänzungen verschwinden.

Die neue Regel: ein Top-Level-Verzeichnis als Menge von Roots behandeln und jeden einzeln mergen

Der Fix behandelt jedes autor-eigene Top-Level-Verzeichnis (skills/, cron/ und jedes Verzeichnis, das das Manifest deklariert) als Container von Roots statt als Einheit (_copy_dist_payload in hermes_cli/profile_distribution.py):

  • Ein Root, den der neue Payload ausliefert, ersetzt sein Gegenstück komplett – Dateien, die der Autor zurückgezogen hat, verschwinden tatsächlich.
  • Ein Root, den der Payload nicht ausliefert, bleibt unangetastet, weil er nie Teil dieses Releases war.
  • Dieselbe Logik gilt für update und für install --force, für die standardmäßig eigenen Verzeichnisse und für ein explizites distribution_owned: [skills].

Der klarste Beleg ist die Vorher/Nachher-Tabelle aus dem PR selbst:

Szenario Vorher Nachher
dein skills/curator-custom/ gelöscht erhalten
dein cron/mine.json gelöscht erhalten
der ausgelieferte Skill des Autors, aktualisiert auf v2 aktualisiert aktualisiert
eine Datei, die der Autor zurückgezogen hat weg weg

Alle vier Zeilen zusammen sind das tatsächliche Verhalten: Deine Ergänzungen schützen und dem Release des Autors folgen gilt jetzt gleichzeitig, statt dich vor eine Wahl zu stellen.

Drei Randfälle, die du kennen solltest

Verschachtelte Allowlist-Einträge ersetzen weiterhin genau diesen Pfad. Wenn das Manifest skills/compliance/ auflistet, wird nur dieser Pfad ersetzt – benachbarte Roots bleiben unangetastet.

Ein symlinktes Container-Verzeichnis wird verweigert statt beschrieben. Ist skills/ ein Symlink auf ein geteiltes Verzeichnis, wirft das Update vor dem ersten Schreibvorgang einen DistributionError. Das ist wichtiger, als es klingt: Das Schreiben durch den Link würde das geteilte Verzeichnis überschreiben und könnte das Profil halb aktualisiert zurücklassen – deutlich schwerer zu diagnostizieren als ein harter Fehler.

.env.template landet weiterhin als .env.EXAMPLE. Es wird zuerst entfernt, damit es nicht durch einen Symlink geschrieben werden kann, und es überdeckt nie deine echte .env.

Was du jetzt tun solltest

Auf einer Version mit diesem Fix führst du einfach das Update erneut aus – es gibt nichts zu migrieren:

hermes profile update

Um zu wissen, welcher Teil eines Profils dem Autor gehört, lies distribution_owned in seinem Manifest; verschachtelte Pfade darin (wie skills/compliance/) begrenzen die Ersetzung auf genau diesen Pfad. Die vollständige Flag-Liste, inklusive --force, findest du in:

hermes profile update --help

Wenn du durch die alte Ganz-Verzeichnis-Ersetzung bereits nutzerseitig hinzugefügte Skills oder Cron-Jobs verloren hast, kommen sie nicht automatisch zurück – stelle sie aus einem Backup oder von einer anderen Maschine wieder her. Unser Leitfaden dazu, wo Skills liegen, zeigt, wie du sie von Hand neu aufbaust, und Cron-Jobs, die einem Profil folgen erklärt, wohin die geplanten Jobs gehören.

Die Kurzfassung

hermes profile update ersetzt jetzt nur noch, was der Autor ausgeliefert hat; Skills und Cron-Jobs, die du dem Profil hinzugefügt hast, bleiben, wo sie sind. Wenn du einen geteilten Team-Agent pflegst, heißt das: Du kannst lokal anpassen, ohne dich zu fragen, ob das nächste Upstream-Release aufräumt.