feat(config): profile config inheritance from default (#20270)#41741
feat(config): profile config inheritance from default (#20270)#41741teknium1 wants to merge 2 commits into
Conversation
New profiles created with `hermes profile create` get a skeleton config.yaml with `inherit: true` and store only overrides; all other values are read from the default profile's config.yaml. - hermes_constants: add get_default_config_path() - profiles.py: INHERITANCE/FULL skeleton templates + inherits param on create_profile (fresh profiles only; cloning paths untouched) - docs: profiles.md inheritance section + configuration.md precedence Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
Salvage follow-up to make profile inheritance actually take effect in real sessions, not just `hermes config show`: - Single resolver: resolve_inherited_raw_config() / _apply_inheritance() are the source of truth; load_config(), the CLI's load_cli_config(), and the gateway env bridges all resolve through it. Previously only load_config() inherited, so `hermes -p work chat` and the gateway saw only the skeleton and silently dropped inherited values. - Cache integrity: fold the parent config's (mtime_ns, size) into the load_config cache key so editing the default config.yaml invalidates inheriting profiles' caches. - save_config writes overrides-only against the parent's FULLY-RESOLVED config (DEFAULT_CONFIG + default profile), keeping inherit: true. - main.py: replace the invalid Click-style "--inherits/--no-inherit" argparse flag (which never bound a dest) with a real --no-inherit store_false; wire inherits through cmd_profile. - _should_inherit never inherits from itself (active == default profile). - AUTHOR_MAP: joe@maples.dev -> frap129. - Tests: 13 inheritance tests (all three loaders, cache-bust, standalone, save round-trip, skeleton seeding, clone-no-seed).
🔎 Lint report:
|
|
Closing — profile isolation is intentional design, not a gap. Profiles are meant to be independent islands; we don't want a live config link that lets the default profile silently change a named profile's behavior. The existing Thanks @frap129 for the implementation in #20964 — the work was sound, the design direction just isn't one we're taking. |
AGENTS.md was almost entirely how-to/mechanics with the want/don't-want
guidance implicit and scattered. Adds a single authoritative intent layer
near the top, calibrated against what actually merges and what actually
gets rejected.
- 'What Hermes Is': framing + the two properties that drive design
(prompt-cache integrity, narrow-waist core).
- 'Contribution Rubric': dual-purpose intent doc — (1) for humans/own work:
what gets merged vs rejected; (2) for the triage sweeper: when a PR is safe
to close on the three allowed reasons AND when NOT to close one. Taste-based
'won't implement / out of scope' closes stay human-only by design.
- 'What we want' calibrated against the last ~55 merges: fix real bugs well,
expand reach at the edges (platforms/channels/providers/models/desktop —
large features land routinely), refactor god-files into clean modules,
keep the CORE narrow. 'Expansive at the edges, conservative at the waist.'
- 'What we don't want': speculative hooks, .env-for-non-secrets, needless
core tools, lazy-read escape hatches, feature-destroying fixes, ungated
telemetry, change-detector tests, core-touching plugins.
- 'Before you call it a bug — verify the premise (and when NOT to close)':
distilled from real closes (#41741 intentional-design-not-a-gap, #41610
wrong-premise, #42327 fix-never-executes, #42393 deliberate-omission,
#41999 overreach). Doubles as sweeper guidance to avoid wrongly closing
legitimate PRs.
- 'The Footprint Ladder' (core-tool decision): extend > CLI+skill > gated tool
> plugin > MCP server in the catalog > new core tool (last resort).
Trim: 'Adding New Tools' intro points at the ladder. Detailed mechanics stay
where readers need them.
#42641) AGENTS.md was almost entirely how-to/mechanics with the want/don't-want guidance implicit and scattered. Adds a single authoritative intent layer near the top, calibrated against what actually merges and what actually gets rejected. - 'What Hermes Is': framing + the two properties that drive design (prompt-cache integrity, narrow-waist core). - 'Contribution Rubric': dual-purpose intent doc — (1) for humans/own work: what gets merged vs rejected; (2) for the triage sweeper: when a PR is safe to close on the three allowed reasons AND when NOT to close one. Taste-based 'won't implement / out of scope' closes stay human-only by design. - 'What we want' calibrated against the last ~55 merges: fix real bugs well, expand reach at the edges (platforms/channels/providers/models/desktop — large features land routinely), refactor god-files into clean modules, keep the CORE narrow. 'Expansive at the edges, conservative at the waist.' - 'What we don't want': speculative hooks, .env-for-non-secrets, needless core tools, lazy-read escape hatches, feature-destroying fixes, ungated telemetry, change-detector tests, core-touching plugins. - 'Before you call it a bug — verify the premise (and when NOT to close)': distilled from real closes (#41741 intentional-design-not-a-gap, #41610 wrong-premise, #42327 fix-never-executes, #42393 deliberate-omission, #41999 overreach). Doubles as sweeper guidance to avoid wrongly closing legitimate PRs. - 'The Footprint Ladder' (core-tool decision): extend > CLI+skill > gated tool > plugin > MCP server in the catalog > new core tool (last resort). Trim: 'Adding New Tools' intro points at the ladder. Detailed mechanics stay where readers need them.
NousResearch#42641) AGENTS.md was almost entirely how-to/mechanics with the want/don't-want guidance implicit and scattered. Adds a single authoritative intent layer near the top, calibrated against what actually merges and what actually gets rejected. - 'What Hermes Is': framing + the two properties that drive design (prompt-cache integrity, narrow-waist core). - 'Contribution Rubric': dual-purpose intent doc — (1) for humans/own work: what gets merged vs rejected; (2) for the triage sweeper: when a PR is safe to close on the three allowed reasons AND when NOT to close one. Taste-based 'won't implement / out of scope' closes stay human-only by design. - 'What we want' calibrated against the last ~55 merges: fix real bugs well, expand reach at the edges (platforms/channels/providers/models/desktop — large features land routinely), refactor god-files into clean modules, keep the CORE narrow. 'Expansive at the edges, conservative at the waist.' - 'What we don't want': speculative hooks, .env-for-non-secrets, needless core tools, lazy-read escape hatches, feature-destroying fixes, ungated telemetry, change-detector tests, core-touching plugins. - 'Before you call it a bug — verify the premise (and when NOT to close)': distilled from real closes (NousResearch#41741 intentional-design-not-a-gap, NousResearch#41610 wrong-premise, NousResearch#42327 fix-never-executes, NousResearch#42393 deliberate-omission, NousResearch#41999 overreach). Doubles as sweeper guidance to avoid wrongly closing legitimate PRs. - 'The Footprint Ladder' (core-tool decision): extend > CLI+skill > gated tool > plugin > MCP server in the catalog > new core tool (last resort). Trim: 'Adding New Tools' intro points at the ladder. Detailed mechanics stay where readers need them.
#42641) AGENTS.md was almost entirely how-to/mechanics with the want/don't-want guidance implicit and scattered. Adds a single authoritative intent layer near the top, calibrated against what actually merges and what actually gets rejected. - 'What Hermes Is': framing + the two properties that drive design (prompt-cache integrity, narrow-waist core). - 'Contribution Rubric': dual-purpose intent doc — (1) for humans/own work: what gets merged vs rejected; (2) for the triage sweeper: when a PR is safe to close on the three allowed reasons AND when NOT to close one. Taste-based 'won't implement / out of scope' closes stay human-only by design. - 'What we want' calibrated against the last ~55 merges: fix real bugs well, expand reach at the edges (platforms/channels/providers/models/desktop — large features land routinely), refactor god-files into clean modules, keep the CORE narrow. 'Expansive at the edges, conservative at the waist.' - 'What we don't want': speculative hooks, .env-for-non-secrets, needless core tools, lazy-read escape hatches, feature-destroying fixes, ungated telemetry, change-detector tests, core-touching plugins. - 'Before you call it a bug — verify the premise (and when NOT to close)': distilled from real closes (#41741 intentional-design-not-a-gap, #41610 wrong-premise, #42327 fix-never-executes, #42393 deliberate-omission, #41999 overreach). Doubles as sweeper guidance to avoid wrongly closing legitimate PRs. - 'The Footprint Ladder' (core-tool decision): extend > CLI+skill > gated tool > plugin > MCP server in the catalog > new core tool (last resort). Trim: 'Adding New Tools' intro points at the ladder. Detailed mechanics stay where readers need them.
Summary
Profiles can now inherit their config from the default profile and store only the values they override — instead of every profile being a standalone full copy. A profile opts in with
inherit: truein itsconfig.yaml; at load time the default profile's config is deep-merged underneath the profile's overrides.Salvaged from @frap129's #20964 (closes #20270), with follow-up fixes so inheritance actually takes effect in real agent sessions — not just under
hermes config show.Changes
hermes_constants.py—get_default_config_path().hermes_cli/config.py—_should_inherit(),resolve_inherited_raw_config()/_apply_inheritance()(single source of truth),_compute_overrides_only();load_config()resolves inheritance and folds the parent config's(mtime_ns, size)into its cache key;save_config()writes overrides-only against the parent's fully-resolved config and keepsinherit: true;hermes config showgains a Profile/inheritance section.cli.py—load_cli_config()resolves through the shared resolver (CLI sessions now see inherited values).gateway/run.py— both config→env bridges resolve through the shared resolver (gateway sees inherited values).hermes_cli/profiles.py—INHERITANCE_SKELETON_YAML/FULL_CONFIG_SKELETON_YAML; fresh profiles seed a skeleton (inheritsparam); cloning paths untouched.hermes_cli/main.py— real--no-inheritflag (replaces an invalid Click-style--inherits/--no-inheritthat never bound a dest).scripts/release.py— AUTHOR_MAP:joe@maples.dev→frap129.Root cause of the follow-up fixes
The original PR only taught
load_config()to inherit. Hermes has three config readers (load_config, the CLI'sload_cli_config, and the gateway's raw env bridges); the other two read the profile'sconfig.yamldirectly, so aninherit: trueprofile would resolve correctly inhermes config showbut silently drop inherited values in actual CLI/gateway sessions. The fix routes all three through one resolver. Also fixed: a cache key that ignored the parent config (stale on default edits) and an argparse flag that could never be triggered.Validation
load_config/load_cli_config/ gateway resolverinherit: false)save_configon inheriting profileinherit: true--no-inheritflagtests/hermes_cli/test_config.py+test_profiles.py(231) + config/gateway env-bridge suites (99) green; 13 new inheritance testsInfographic
Original PR #20964 by @frap129 — cherry-picked authorship preserved in git log.