When hermes update (or hermes setup) bumps _config_version and seeds new defaults, the migration only runs against the currently active HERMES_HOME. Other profiles under ~/.hermes/profiles/*/ (or custom-rooted profiles) keep their old _config_version and old on-disk shape until the user happens to activate them and re-run setup.
This is a real drift surface, not theoretical. Today after upgrading to v23 (curator defaults seeding), I ended up with three profiles in three different states:
- the active profile: v23, with
curator: and auxiliary.curator: written to disk
- a sibling profile: v22, with a legacy
curator.auxiliary block that v23 has retired
- another sibling: v22, missing
curator.backup and auxiliary.curator entirely
In all three, the runtime load_config() deep-merge papers over the gap — the curator runs — but:
- Users can't see or edit the active settings in their
config.yaml
_config_version on disk lies about the schema actually in use
- A future v23→v24 migration that depends on v23-shaped data finds v22-shaped data and either silently no-ops on conditions that already match defaults, or fires repair logic that's expensive/wrong
Proposal (cheapest first):
-
Migrate-on-activation (preferred). When hermes -p X ... activates a profile whose _config_version is below DEFAULT_CONFIG["_config_version"], run the migration silently (or with a one-line "migrating profile X to v" notice) before the agent starts.
-
Walk-all-profiles in hermes update. Iterate the configured profile roots and migrate each. Belt-and-suspenders on (1) for users who don't activate every profile.
I'd argue against persisting the deep-merge result to disk on every load — it breaks the "absent key inherits default forever" contract and would surprise users who deliberately leave keys unset to follow upstream.
Happy to put up a PR for (1) if it's wanted.
When
hermes update(orhermes setup) bumps_config_versionand seeds new defaults, the migration only runs against the currently activeHERMES_HOME. Other profiles under~/.hermes/profiles/*/(or custom-rooted profiles) keep their old_config_versionand old on-disk shape until the user happens to activate them and re-run setup.This is a real drift surface, not theoretical. Today after upgrading to v23 (curator defaults seeding), I ended up with three profiles in three different states:
curator:andauxiliary.curator:written to diskcurator.auxiliaryblock that v23 has retiredcurator.backupandauxiliary.curatorentirelyIn all three, the runtime
load_config()deep-merge papers over the gap — the curator runs — but:config.yaml_config_versionon disk lies about the schema actually in useProposal (cheapest first):
Migrate-on-activation (preferred). When
hermes -p X ...activates a profile whose_config_versionis belowDEFAULT_CONFIG["_config_version"], run the migration silently (or with a one-line "migrating profile X to v" notice) before the agent starts.Walk-all-profiles in
hermes update. Iterate the configured profile roots and migrate each. Belt-and-suspenders on (1) for users who don't activate every profile.I'd argue against persisting the deep-merge result to disk on every load — it breaks the "absent key inherits default forever" contract and would surprise users who deliberately leave keys unset to follow upstream.
Happy to put up a PR for (1) if it's wanted.