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.

PurposeEnv overrideLinux/BSD defaultmacOS defaultWindows 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/sonnysame as datasame 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) and models.d/*.yaml, pack-sources.yaml, hooks.yaml, tui.yaml, mcp.yaml, mcp.allowlist.yaml, agents.yaml, plus the trusted-key directories pack-keys/*.pub and catalog-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 that worktrees/ 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.

A project cannot redirect your API endpoint
A project tier’s model 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:

VariablePurpose
SONNY_CONFIGPath to a config file (replaces the search)
SONNY_PROVIDERProvider override
SONNY_MODELModel override
SONNY_API_KEYGeneric API key when the provider-specific one is unset
SONNY_MODELS_FILEPath to the model catalog
SONNY_LOG_LEVELdebug, info, warn, error
SONNY_DEBUG1 for debug logging
SONNY_NO_UPDATE_CHECK1 to disable the background update check
SONNY_SESSION_DBSession database path
SONNY_PACKS_DIRRecipe directory override
SONNY_MCP_CONFIGmcp.yaml path
SONNY_TRACE_DIRTrace directory
SONNY_WORKDIRWorking directory override
SONNY_MESH_ADDRLegacy mesh endpoint fallback (lowest priority for that key)
SONNY_MESH_INSECURE_DEV_PROFILEDevelopment escape hatch; must name an exact loopback profile
SONNY_BINExplicit 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 .

Development-only mesh flag
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

FilePurpose
models.yaml + models.d/*.yamlModel catalog: sources, categories, routing policy
pack-sources.yamlGit-backed recipe sources
mcp.yaml + mcp.allowlist.yamlMCP server definitions and the operator allowlist that gates them
tui.yamlKeybindings, paste, clipboard and tmux behaviour
hooks.yamlLifecycle hooks
agents.yamlTrust 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.