Sonny reads exactly four kinds of configuration: the hierarchical
.sonny.yaml file, the project .sonny/ directory, environment
variables (SONNY_*, SONNY_*), and command-line flags. This page tells
you where each lives, which one wins, and how to check the answer.
Where files live
Sonny layers the XDG basedir spec on top of Go’s standard library directory
resolvers, so $XDG_*_HOME always wins when set — on every OS. Every path ends
in /sonny so the base directories can be shared with other tools.
| Purpose | Env override | Linux/BSD default | macOS default | Windows default |
|---|---|---|---|---|
| Config | $XDG_CONFIG_HOME | ~/.config/sonny | ~/Library/Application Support/sonny | %AppData%\sonny |
| Data | $XDG_DATA_HOME | ~/.local/share/sonny | ~/Library/Application Support/sonny | %AppData%\sonny |
| Cache | $XDG_CACHE_HOME | ~/.cache/sonny | ~/Library/Caches/sonny | %LocalAppData%\sonny |
| State | $XDG_STATE_HOME | ~/.local/state/sonny | same as data | same as data |
| System data | $XDG_DATA_DIRS | /usr/local/share/sonny, /usr/share/sonny | /Library/Application Support/sonny | %ProgramData%\sonny |
The four directories hold different things, and the split matters when you are backing up or cleaning up:
- Config —
models.yaml(the model catalog) andmodels.d/*.yaml,pack-sources.yaml,hooks.yaml,tui.yaml,mcp.yaml,mcp.allowlist.yaml,agents.yaml, plus the trusted-key directoriespack-keys/*.pubandcatalog-keys/. - Data —
packs/(installed recipes),traces/(conversation traces, on by default),sessions.db,control.db,memory/<id>.yaml. - Cache —
pack-repos/<name>/(cloned git recipe sources),memindex/,worktrees/. Safe to delete, except thatworktrees/can hold a delegated child’s work until it is integrated. - State —
fitness.yaml,active-tabs.json,agent-availability.yaml,worker-spawn-audit.jsonl.
To move everything to another disk, set XDG_CONFIG_HOME, XDG_DATA_HOME,
XDG_CACHE_HOME and XDG_STATE_HOME before launching.
The precedence chain
For every key Sonny knows about, the effective value is the highest-priority source that set it:
code default / bound flag's default (lowest)
< .sonny.yaml
< SONNY_* environment variable
< an explicitly-set command-line flag (highest)
The one thing to internalise: the environment beats the config file. That is
the opposite of a typical .env-first CLI, and it is deliberate — Sonny loads
the file into the config layer, then consults the environment, and only an
explicit flag outranks that.
# .sonny.yaml
log_level: info
export SONNY_LOG_LEVEL=debug # wins over the file
sonny config show --log-level warn # wins over the environment
Use sonny config show to print the effective provider, model, debug and
log_level — the cheapest way to confirm which source actually won.
Nested keys are file- and flag-only
mesh.* and trace.* are dotted paths into a sub-map. The automatic
environment lookup only substitutes - for _; it never turns . into _, so
SONNY_MESH_ENDPOINT is not consulted for mesh.endpoint. Nested keys come
from .sonny.yaml or from a flag bound to that key (--mesh-addr,
--mesh-peer, --trace-server). The exception is a small set of keys bound by
hand with an explicit name, which do sit in the normal environment tier.
.sonny.yaml
The hierarchical file, found by walking from the current directory up to $HOME.
Write a project’s version with sonny init.
provider: anthropic
model: claude-sonnet-4-5-20250929
log_level: info
permission_mode: acceptEdits
workdir: /srv/work/my-service
models:
coding:
provider: anthropic
model: claude-sonnet-4-5-20250929
effort: high
session:
persist: true
max_age: 30d
mesh:
endpoint: 127.0.0.1:50052
peer_addr: 0.0.0.0:50053
control_db: ~/.local/share/sonny/control.db
workdir re-roots the search, so a project file in <workdir>/.sonny.yaml is
found from anywhere. --config <file> replaces the whole search with one file.
Operator-only keys
The trace.upload.*, trace.server.*, mesh.*, variants.* and
agent_messaging.* blocks are operator-only: a project’s .sonny.yaml
cannot set them unless that directory is a trusted workspace. A cloned
repository therefore cannot route your telemetry or enrol you in a mesh just by
being cloned. Put those keys in your $HOME file.
The project .sonny/ directory
Alongside .sonny.yaml, Sonny walks the same path collecting .sonny/
directories:
.sonny/
├── pack.yaml # project recipe (the entry point)
├── packs/ # other project packs
├── models.yaml # project model catalog
├── mcp.yaml # project MCP servers
├── config.yaml # project command surface
└── hooks.yaml # lifecycle hooks (working directory only, never walked)
The merge is outer-first: the directory nearest the filesystem root is
lowest priority, the one nearest your working directory is highest. A .sonny/
directory is only read if it is owned by you or by root; an untrusted one — left
behind by another user on a shared machine — is skipped silently.
sources: list is stripped with a warning unless you set
SONNY_TRUST_PROJECT_MODELS=1. An ancestor directory you do not control cannot
quietly point your API calls at its own endpoint.Environment variables
SONNY_* and SONNY_* are the same prefix: where the canonical environment
variable is defined with single underscores, the double-underscore spelling is
accepted too, and the single-underscore form is canonical in docs and in the TUI.
The ones you will actually use:
| Variable | Purpose |
|---|---|
SONNY_CONFIG | Path to a config file (replaces the search) |
SONNY_PROVIDER | Provider override |
SONNY_MODEL | Model override |
SONNY_API_KEY | Generic API key when the provider-specific one is unset |
SONNY_MODELS_FILE | Path to the model catalog |
SONNY_LOG_LEVEL | debug, info, warn, error |
SONNY_DEBUG | 1 for debug logging |
SONNY_NO_UPDATE_CHECK | 1 to disable the background update check |
SONNY_SESSION_DB | Session database path |
SONNY_PACKS_DIR | Recipe directory override |
SONNY_MCP_CONFIG | mcp.yaml path |
SONNY_TRACE_DIR | Trace directory |
SONNY_WORKDIR | Working directory override |
SONNY_MESH_ADDR | Legacy mesh endpoint fallback (lowest priority for that key) |
SONNY_MESH_INSECURE_DEV_PROFILE | Development escape hatch; must name an exact loopback profile |
SONNY_BIN | Explicit path to the Sonny binary for delegating child agents |
Provider credentials are their own namespace — ANTHROPIC_API_KEY,
OPENAI_API_KEY, GEMINI_API_KEY, BEDROCK_*, VERTEX_*, AZURE_OPENAI_* and
so on, falling back to SONNY_API_KEY. See
Provider credentials
.
SONNY_MESH_INSECURE_DEV_PROFILE turns off mesh transport security. It is
refused unless it names an exact loopback development profile, it is never
appropriate on a machine that hosts real sessions, and setting it in a
project’s .sonny.yaml has no effect.Other configuration files
| File | Purpose |
|---|---|
models.yaml + models.d/*.yaml | Model catalog: sources, categories, routing policy |
pack-sources.yaml | Git-backed recipe sources |
mcp.yaml + mcp.allowlist.yaml | MCP server definitions and the operator allowlist that gates them |
tui.yaml | Keybindings, paste, clipboard and tmux behaviour |
hooks.yaml | Lifecycle hooks |
agents.yaml | Trust for resolved agent-CLI executables and directories |
.gitignore (project) | Files sonny context skips |
MCP configuration is documented in Configuring MCP servers and model routing in Choosing a model for each task .
Checking what Sonny actually loaded
sonny config show # effective provider, model, debug, log_level
sonny models doctor # catalog reachability and auth diagnostics
sonny doctor # full environment diagnostics, no LLM calls
sonny recipe list # every pack discovered, with its location
sonny mcp list # configured servers vs the operator allowlist
sonny mesh status # mesh endpoint and reachability
See also
Sources: docs/config/paths.md, docs/config/precedence.md,
docs/config/environment.md, docs/config/project-dotfiles.md.