User configuration reference¶
Snodo's user-level configuration lives at ~/.snodo/config.yml. It is separate
from a project's .snodo/protocol.yml: user config holds machine-wide model
choices, credentials, cloud settings, and notification destinations; the
protocol declares project policy. Keep credentials in user config (or resolve
them from the environment), not in a protocol committed to a repository.
Snodo creates a minimal config on first use. The effective defaults below also
apply when a section or key is omitted. snodo config show redacts configured
credentials and notification destinations. The config file is saved with
owner-only (0600) permissions. Saving through Snodo rewrites YAML, so comments
in the file are not preserved.
Top-level settings¶
| Key | Default | Description |
|---|---|---|
model |
claude-sonnet-4-20250514 |
Default model for coder and other LLM roles that do not specify a role-specific model. |
default_model |
unset | Legacy fallback read after model; if set, it takes precedence over model in the model getter. Prefer model. |
providers |
Built-in provider catalog; no user overrides | Provider-specific credentials, model discovery endpoints, routing and headers. See Providers. |
engine |
max_subtask_depth: 3, max_session_age_days: 30, token_ttl_seconds: 600 |
Engine-wide limits. See Engine. |
llm |
See LLM tuning | Model roles, request budgets, retries and recon fan-out. |
cloud |
See Cloud | Cloud sync credentials and service endpoints. |
notifications |
No targets; all supported event types; silence threshold 900 seconds |
Optional background-job notifications. See Notifications. |
api_keys |
Unsupported legacy format | A non-empty api_keys section without providers is rejected. Migrate credentials to providers. |
The mcp and opencode objects emitted by older/default config generation
(mcp.port: 55441, opencode.session_token_warning: 150000, and
opencode.session_reset_on_model_change: false) are retained data, not active
Snodo user-config settings; current callers do not read them.
Providers¶
Each entry under providers is keyed by a provider name. Built-in entries are
anthropic, openai, openrouter, google, cloudflare, and deepseek;
unlisted entries can configure custom/OpenAI-compatible providers. For a
built-in provider, omitted settings inherit the catalog values; for a custom
provider, the defaults are empty strings and an empty extra_headers map.
| Provider key | Default | Description |
|---|---|---|
api_key |
"" |
Literal API key, encrypted reference @keys/<provider>.key, or empty to use another source. Prefer an environment reference for portable setups. |
api_key_env |
"" |
Name of an environment variable containing the key. Built-in provider defaults set this (for example ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, GEMINI_API_KEY, CLOUDFLARE_API_KEY, or DEEPSEEK_API_KEY). |
api_key_ref |
"" |
Named credential reference: env:VARIABLE_NAME or command:COMMAND. A command is split into arguments, has a 10-second timeout and uses trimmed stdout. |
models_endpoint |
"" |
Provider model-catalog endpoint; built-ins supply their own endpoint. |
account_id |
"" |
Provider account identifier, used by Cloudflare. |
account_id_env |
"" |
Environment-variable name for the account identifier; Cloudflare defaults to CLOUDFLARE_ACCOUNT_ID. |
base_url |
"" |
Custom API base URL for LiteLLM requests. |
litellm_provider |
"" |
LiteLLM provider route name, useful for custom-compatible providers. |
extra_headers |
{} |
Extra HTTP headers; {task_id} in a value is substituted with the current task ID (unknown when absent). |
probe_model |
"" |
Model used by provider key checks. Built-ins supply a probe model. |
catalog_provider |
"" |
Provider name used for model catalog lookup when it differs from the config entry name. |
Example:
providers:
openai:
api_key_env: OPENAI_API_KEY
local:
base_url: http://localhost:11434/v1
litellm_provider: openai
api_key: local
extra_headers:
x-task: "{task_id}"
Credential selection prefers a configured api_key, then api_key_env, then
api_key_ref. env: and command: references are resolved only when the
credential is used. Provider keys can also be moved to local encrypted files
with snodo config --encrypt-provider-keys. Before a non-mock task run creates
session or worktree state, Snodo checks that the selected model's provider has
a credential. If it does not, the run exits with status 1 and suggests the
provider environment variable or snodo config add. Providers that declare no
api_key_env (such as a local endpoint) are exempt; mock runs skip this check.
User configuration is edited in ~/.snodo/config.yml (or
$SNODO_HOME/config.yml). snodo config set/get supports only model,
engine.*, and llm.*; use snodo config add/remove for provider keys and edit
YAML directly for other providers.* settings, cloud.*, and
notifications.*.
Engine¶
| Key | Default | Description |
|---|---|---|
engine.max_subtask_depth |
3 |
Engine-level subtask depth bound; setter accepts integers 1–10. Protocol recovery is separately bounded by execution.max_recovery_depth and its mode override, described below. |
engine.max_session_age_days |
30 |
Maximum session age in days; setter accepts integers 1–365. |
engine.token_ttl_seconds |
600 |
Validation-token lifetime in seconds; setter accepts integers 60–86400. |
engine:
max_subtask_depth: 3
max_session_age_days: 30
token_ttl_seconds: 600
The protocol in .snodo/protocol.yml supplies additional execution budgets;
these are not user-config engine.* settings. execution.max_recovery_depth
(default 3) bounds recursive recovery and a mode's max_recovery_depth
overrides it when set. execution.max_total_fix_attempts (default 10) caps
fix attempts for a run. execution.max_retries (default 3) limits later
retries of failed tasks, including tasks resumed from a plan. These protocol
limits act alongside engine.max_subtask_depth, which limits engine subtask
depth; llm.num_retries instead controls LiteLLM retries for transient request
errors and does not set task recovery or task retry limits. See the
protocol reference.
LLM tuning¶
The llm section is optional and validated against a strict schema: unknown
keys are errors. Role models fall back to top-level model. A protocol
validator's own model setting takes precedence over the configured role model.
| Key | Default | Description |
|---|---|---|
llm.num_retries |
3 |
LiteLLM retry count for transient errors (0–10). |
llm.coder.model |
unset | Coder model; falls back to model. |
llm.coder.sandboxed |
false |
Run coder against a discarded copy of the task workspace. |
llm.coder.max_tokens |
16000 |
Coder completion token budget; minimum 1. |
llm.coder.max_tool_turns |
6 |
Coder tool-turn limit (1–200). |
llm.coder.timeout_seconds |
1800 |
Coder wall-clock timeout; minimum 1. |
llm.coder.silence_timeout_seconds |
600 |
Stop a subprocess coder after this many seconds without output; minimum 1. |
llm.coder.concurrency |
1 |
Maximum concurrent coders for this operator; minimum 1. |
llm.validator.model |
unset | Validator model; falls back to model. |
llm.validator.max_tokens |
1500 |
Validator completion token budget; minimum 1. |
llm.validator.max_tool_turns |
6 |
Validator read-tool turn limit (1–200). |
llm.validator_llm.model |
unset | Legacy-compatible validator role model; falls back to model. |
llm.classifier.model |
unset | Classifier model; falls back to model. |
llm.classifier.max_tokens |
500 |
Classifier completion token budget; minimum 1. |
llm.classifier.temperature |
0.0 |
Classifier temperature (0–2). |
llm.recon.num_agents |
1 |
Default number of recon agents; minimum 1. |
llm.recon.models |
[] |
Ordered model priority list for recon. Empty uses the configured default model. |
llm.wave.max_age_days |
14 |
Hard expiry age for a wave; minimum 1. |
llm.wave.max_idle_days |
5 |
Idle timeout before a wave closes; minimum 1. |
Example:
llm:
num_retries: 3
coder:
model: claude-sonnet-4-20250514
max_tokens: 16000
max_tool_turns: 6
timeout_seconds: 1800
silence_timeout_seconds: 600
concurrency: 1
validator:
model: claude-sonnet-4-20250514
max_tokens: 1500
max_tool_turns: 6
classifier:
max_tokens: 500
temperature: 0.0
recon:
num_agents: 1
models: []
wave:
max_age_days: 14
max_idle_days: 5
Older llm.wave.max_tokens and llm.wave.temperature keys are migrated to
llm.classifier with a deprecation warning. Remove them after migration.
Cloud¶
| Key | Default | Description |
|---|---|---|
cloud.api_key |
"" |
Credential for cloud admission, audit sync and liveness. |
cloud.api_url |
https://api.snodo.dev |
Cloud API base for audit ingest. |
cloud.tunnel_api_url |
https://app.snodo.dev |
Cloud app base for tunnel provisioning; independent of api_url. |
cloud.sync_enabled |
false |
Enable cloud audit sync when an API key is configured. |
cloud.liveness_interval_seconds |
60 |
Liveness push cadence/throttle in seconds; non-positive or unreadable values use 60. |
cloud.liveness_url / cloud.liveness_api_url |
Derived from api_url |
Optional equivalent override for the liveness app base. A trailing /v1 is removed. |
cloud.lease_url / cloud.lease_api_url |
Derived from api_url |
Optional equivalent override for the session-admission app base. |
cloud:
api_key: env:SNODO_CLOUD_API_KEY
api_url: https://api.snodo.dev
tunnel_api_url: https://app.snodo.dev
sync_enabled: false
liveness_interval_seconds: 60
Cloud's API key is a literal string when set here; keep it private. The URL overrides are optional and normally need not be configured.
When cloud.sync_enabled is true and an API key is configured, each run with a
session attempts audit sync automatically at the end; the hook runs in the
background and does not block run completion. Liveness heartbeats are sent only
while a run is executing. snodo cloud status reports per-session pending
counts and the last sync error. If a session is marked as refused/blocked,
snodo cloud status directs you to retry with snodo cloud sync --all --force;
the force option explicitly retries refused sessions.
Notifications¶
Notifications are per-user settings in ~/.snodo/config.yml (or
$SNODO_HOME/config.yml), not project protocol settings. They are opt-in: with
no valid targets, no delivery occurs. Delivery is detached and best-effort, so
notification failures do not fail a job. Messages identify the project by its
configured display name when available, otherwise its canonical project ID or
checkout folder, and include the runner hostname.
| Key | Default | Description |
|---|---|---|
notifications.targets |
[] |
Destinations. Supported type values are ntfy, webhook, slack, discord, and teams. |
notifications.events |
job_finished, task_halted, authorization_needed, job_silent |
Event names to deliver; set a subset to filter. |
notifications.silence_threshold_seconds |
900 |
Log-silence interval before a job_silent notification; invalid values use 900 and values are clamped to at least 1 second. |
Each target needs a supported type and an HTTP(S) url; name is an optional
display label. token is optional and sent as a bearer token. headers is an
optional extra-header map for generic webhook targets. ntfy sends plain text
and puts the project and runner in its title. Slack, Discord and Teams format
the project-named message for their respective incoming-webhook interfaces;
Slack uses its native single-asterisk bold syntax, and Discord disables
mentions. Teams sends an Adaptive Card envelope to a Workflows/Power Automate
webhook. Generic webhook receives Snodo's JSON event. URLs and credentials
are redacted by snodo config show. snodo config --notify-test sends one test
message to every valid configured target and reports per-target delivery results.
notifications:
targets:
- type: ntfy
name: phone
url: env:SNODO_NTFY_URL
token: env:SNODO_NTFY_TOKEN
- type: slack
name: slack
url: env:SNODO_SLACK_WEBHOOK_URL
- type: discord
name: discord
url: env:SNODO_DISCORD_WEBHOOK_URL
- type: teams
name: teams
url: env:SNODO_TEAMS_WORKFLOW_WEBHOOK_URL
token: env:SNODO_TEAMS_TOKEN
- type: webhook
name: generic-hook
url: env:SNODO_WEBHOOK_URL
token: env:SNODO_WEBHOOK_TOKEN
headers:
X-Source: snodo
events:
- job_finished
- task_halted
- authorization_needed
- job_silent
silence_threshold_seconds: 900
For any target url or optional token, an env:VARIABLE_NAME or
command:COMMAND reference is resolved at delivery time. In particular,
url: env:SNODO_WEBHOOK_URL reads the webhook endpoint from that environment
variable, keeping it out of the YAML. Supply only the token when the endpoint
requires bearer authentication.
The default event set is job_finished, task_halted, authorization_needed,
and job_silent. job_silent is sent once when a running job has had no log
activity for silence_threshold_seconds (default 900 seconds; invalid values
fall back to 900 and valid values are clamped to at least one second). Terminal
cancelled and unmerged jobs do not generate a job_finished notification.
Environment variables¶
These variables affect run behavior or identify the context of a run. The
SNODO_* variables below are read by Snodo; variables such as ANTHROPIC_API_KEY
are provider credentials documented under Providers.
| Variable | Use |
|---|---|
SNODO_BENCHMARK=1 |
Disables task auto-merge, even when protocol policy enables it. |
SNODO_WORKTREE_PATH |
Internal: points a run at an already-created task worktree. |
SNODO_TASK_PLAN |
Internal: supplies the plan name associated with the current task/run. |
SNODO_TASK_PLAN_WAVE |
Internal: supplies the plan wave associated with the current task. |
SNODO_PROJECT_ROOT |
Internal: overrides the project root used by Snodo; run commands set it while executing. |
SNODO_JOB_ID |
Internal: identifies the background job associated with a run. |
SNODO_PLAN_JOB |
Internal: marks plan-run job context so the plan job ID is not treated as a child task job ID. |
The internal variables are primarily set by Snodo's job and plan runners; they are not ordinary configuration knobs to set for a normal run.