Introduction
config.yaml is more than just a settings file. It is a declarative description of what your Hermes install should be: which model, which terminal backend, which toolsets, which compression strategy. Treat it that way and you unlock reproducibility: the same file plus a fresh .env plus SOUL.md recreates your exact agent on any machine. Treat it casually and it drifts into a junk drawer of opaque toggles.
Key Concepts
- Declarative: The file describes the desired state; Hermes makes the runtime match.
- Reproducibility: Same
config.yaml+ sameSOUL.md+ fresh.envrecreates the same agent. - Source of truth: When in doubt about what is set, the file is authoritative.
- Version control friendly: Plain YAML, line-by-line diffs, no binary state.
Real World Context
A developer sets up Hermes carefully over six weeks: picks a provider, tunes compression, enables specific toolsets, configures memory limits. They commit config.yaml and SOUL.md to a private dotfiles repo. Eight months later they move to a new laptop. They clone dotfiles, copy in a fresh .env with their keys, run hermes. The agent boots in their exact configured state with no manual reconfiguration. The eight-month time gap was bridged by the declarative file.
Deep Dive
A realistic config.yaml is a small structured document:
yaml# ~/.hermes/config.yaml model: anthropic/claude-sonnet-4-20250514 terminal: backend: docker docker_forward_env: - GITHUB_TOKEN - NPM_TOKEN display: busy_input_mode: interrupt show_status_bar: true indicator: kaomoji memory: max_tokens: 200000 compress_strategy: focus providers: openrouter: api_key: ${OPENROUTER_API_KEY} fallback: anthropic/claude-opus-4 ollama: base_url: http://localhost:11434 toolsets: enabled: - filesystem - terminal - web
Notice what the file has and does not have:
- It has: model defaults, runtime behavior, toolset enablement, provider routing, references to
.envsecrets. - It does not have: session IDs, cached state, current conversation history, anything that changes between sessions.
That is the declarative test: does this value describe what the agent should be, or what the agent is doing right now? Only the first kind belongs in config.yaml.
Why declarative wins
Imperative configuration ("run command X to set value Y") makes the current state opaque. You have to remember every command you ran. Declarative configuration is auditable: reading the file tells you exactly what you have set. hermes config show and hermes config edit both work because the file is the source of truth.
This is also why hermes config set KEY VAL is implemented as "edit the file and reload" rather than as a parallel state store: there is no state outside the file.
Things that should not be in config.yaml
- Secrets: They belong in
.env(covered in the previous lesson). - Volatile session state: Current session ID, last response, partial outputs. Hermes manages these internally.
- Per-invocation overrides: If you want a different model for one call, pass
--modelon the CLI instead of changing the config.
Common Pitfalls
- Treating
config.yamlas a scratchpad: Editing it during a session for a one-off change, then forgetting to revert. Use CLI flags for one-offs. - Burying secrets in nested keys: Even three levels deep, a raw key is a leak risk. Use
${VAR}substitution instead.
Best Practices
- Version-control
config.yamlin your dotfiles repo: Treat it like.bashrcor.tmux.conf. - Keep the file small and commented: A 50-line config you fully understand beats a 500-line config you do not. Comment the lines that surprised you.
Summary
config.yamlis a declarative description of your Hermes install.- The file is the single source of truth; CLI commands edit it and reload.
- Pairing version-controlled
config.yamlandSOUL.mdwith a fresh.envmakes the install reproducible. - Keep volatile state, secrets, and one-off overrides out of the file.
Code Examples
# A realistic, commented config.yaml: declarative, reproducible, shareable
model: anthropic/claude-sonnet-4-20250514 # default model and provider
terminal:
backend: docker # all shell tool calls run in a sandbox
display:
busy_input_mode: interrupt # pressing Enter while busy stops the agent
indicator: kaomoji # `(>_<)` style busy spinner
memory:
max_tokens: 200000 # raise once /compress lands frequently
providers:
openrouter:
api_key: ${OPENROUTER_API_KEY} # value comes from ~/.hermes/.env