Layering
Hyprpilot reads layered config. Each source overrides the one before it for the fields it sets, so you only write what you want to change.
The layers
- Compiled defaults — every knob has a working default, baked into the binary from
src/config/defaults.toml. - Global config —
~/.config/hyprpilot/config.{toml,json,yaml,yml}, or an explicit--config <path>. - Config-layer overlays —
~/.config/hyprpilot/profiles/<name>.{ext}, picked with--config-profile <name>(repeatable) orHYPRPILOT_CONFIG_PROFILE=<name>[,<name>…]. Overlays fold in the order given, each on top of the one before; flags on the command line replace the env list rather than extending it. patchesand--with-config— profile overlays applied at resolve time. See Patches and Ad-hoc Overlays.
Any layer can be any supported format — a YAML global config composes with a TOML config-layer profile. See Formats for extension discovery.
Merge rules
- Scalar fields overwrite — a later layer's value wins for the fields it sets.
- The keyed
agents/profileslists merge byid— a later layer's entry with a matchingidreplaces the earlier entry wholesale, and new ids append. There is no field-level merge inside a single entry. - The
patcheslist is additive — each layer's patches append to the earlier layers' list instead of replacing it, so the seeded default patch and your global patches survive a config-layer profile that adds more. See Patches → Additive across layers for how to neutralize an inherited patch.
Overlays follow the same rules among themselves, so with --config-profile personal --config-profile work a profiles entry both files define comes from work, and work's patches fold after personal's.
~ and ${VAR} / ${env:VAR} in path-valued fields expand at consume time; relative paths resolve against the current directory.
Config-layer profile ≠ session profile
The word "profile" lives in two parallel namespaces — keep them apart:
| Concept | Addressed via | Purpose |
|---|---|---|
| Config-layer profile | --config-profile / HYPRPILOT_CONFIG_PROFILE | Layer a different config file overlay (e.g. work vs personal). |
| Session profile | profiles in config, picked via the positional [PROFILE] | Which agent + model + cwd + system prompt + MCPs a launch uses. See Profiles. |
A config-layer profile can itself define or override session profiles — that is the point: HYPRPILOT_CONFIG_PROFILE=work can swap your whole profiles registry. Two accounts can therefore keep identical session ids (claude/opus in both personal.yaml and work.yaml), and the overlay decides which one a launch gets:
hyprpilot --config-profile work claude/opus # the work account's claude/opus
hyprpilot --config-profile personal claude/opus # the personal oneThe selection follows the launch into the Agent Harness: the injected hyprpilot mcp harness entry carries --config and every --config-profile explicitly, so its list_profiles / spawn read the same layers even when the vendor filters the environment it hands MCP servers, and the sessions it spawns pass the selection on again.
Validation
After the layers merge, the whole config is validated in one pass:
- Unknown fields reject at parse time — every section is a closed shape, so a typo like
modle: …fails with an error naming the field. - Closed sets are enums —
provider,--log-levelvalues, and config formats reject unknown values at parse, not deep in a launch. - Cross-field references are checked —
profiles[].agentmust reference a realagents[].id, andprofile.defaultmust name a realprofiles[].id. - The
profileslist must be non-empty — a fresh install with no profile refuses to launch rather than guessing.
Validation failures abort startup with a readable field-path error, so a broken config never reaches the vendor CLI.
Where things live
| Path | What |
|---|---|
~/.config/hyprpilot/config.{toml,json,yaml,yml} | Global config. |
~/.config/hyprpilot/profiles/*.{ext} | Named config-layer overlays. |
~/.config/hyprpilot/skills/<path>/SKILL.md | Skill bundles (default catalogue root). |
~/.config/hyprpilot/mcps/*.json | MCP catalogue files (your convention). |