Configuration
The three-layer configuration model — global profiles, per-project overrides, CLI flags, variable interpolation, and every file heretic-cli reads or writes
Three layers
Every run resolves configuration by merging three layers — later wins:
1. Global profile ~/.heretic/agents/<name>.yaml
2. Local override <project>/.heretic/cli/<name>.yaml (or the legacy agent.yaml)
↓
3. CLI flags --mcp, --root, --sidecar / --builder-image / --disable-sidecars
↓
${VAR} interpolation over the merged result
↓
mcp_file merge → defaults → validation
Merging is per field, not a blanket deep merge: env, extra, ssh, git and secrets merge key by key, while volumes, mcp, extra.ports and extra.capabilities are replaced wholesale. The exact table is on the Merge Rules page.
A local override must declare which profile it belongs to. A mismatch is fatal: Local config extends 'a' but loading profile 'b'.
Variable interpolation
${VAR} placeholders are expanded in every string of the merged configuration, recursively through objects and arrays, after the merge.
| Variable | Value |
|---|---|
${CWD} | the project directory (where you ran the command) |
${HOME} | your home directory |
${ANY_ENV_VAR} | any variable from the host environment |
${MY_SECRET} | any key from secrets:, because secrets are resolved first |
- An unknown variable logs
Variable "X" is undefined, resolving to empty stringand expands to""— and empty values are then stripped from the container environment unless the key was explicitly set to"". - Escape with a double dollar:
$${LITERAL}renders as${LITERAL}. - Only the braced form is recognised in configuration values. A bare
$VARis left as-is (it is recognised insidesecrets:values, which take a different code path).
Global files
~/.heretic/
├── settings.yaml github.token / github.copilot_token (script paths or raw values)
├── agents/
│ └── <name>.yaml one agent profile per file — the profile name is the filename
├── get-<name>-key.sh|.cmd per-agent secret script created by `init` (mode 0755 on Unix)
├── get-github-token-key.sh|.cmd GitHub token script
├── get-copilot-token-key.sh|.cmd Copilot token script
└── <name>-settings.json per-agent Claude Code settings
Project files
<project>/.heretic/
├── cli/
│ ├── <profile>.yaml per-profile override — PREFERRED
│ ├── agent.yaml legacy single override; applies only when its `extends:` matches
│ ├── compose.yaml RESERVED — used verbatim by runner: custom
│ └── claude-settings.json RESERVED — merged over the global settings at run time
└── temp/<session>/ generated per run (mode 0777), mounted into the container
├── .mcp.json generated MCP configuration
├── settings.json merged Claude settings → ~/.claude/settings.json
├── .claude.json onboarding seed {"hasCompletedOnboarding": true}
├── compose.yaml generated compose file (compose runner)
└── entrypoint.sh embedded entrypoint (root mode only)
Reserved filenames
Inside .heretic/cli/ every *.yaml / *.yml file is treated as a profile override except compose.yaml and claude-settings.json. Add .heretic/cli/ to .gitignore — it holds machine-specific paths and secret references.
The session directory is regenerated on every run and is where the agent's ~/.claude (or ~/.copilot) state lives, so it survives across runs of the same session. Deleting .heretic/temp/<session>/ resets that agent's history.
Inspecting a resolved configuration
heretic-cli agents show <name> # the raw profile file
heretic-cli agents show <name> --resolved # after merge, interpolation and secret resolution
heretic-cli local-validate <name> # the complete resolved object (secrets in plaintext)
Detailed topics
- Agent Profiles — every profile field
- Local Overrides — per-project configuration
- Secrets — the three secret value modes
- MCP Servers — formats, transports, mount paths
- Runners — docker, compose, custom
- Sidecars — build toolchains in sibling containers
- SSH Host Access — running tools on a remote host
- Sessions — multiple isolated instances
- Docker Images — choosing and building images