Everything lives in one file, config.yaml, seeded on first run from the tracked template config.default.yaml. A clone keeps it beside the install, and pipx/uv installs keep it in ~/.saturn. /config <dotted.key> <value> sets most of it live. Edits persist by default, and --session keeps one for this session only. Trust keys are the exception (see below).
::tiers
Each tier binds one chat model, used for the agent's calls and the background work (compaction, the memory review, /init), plus an embedder. Swapping hardware is a one-line active_tier change.
active_tier: 4b
tiers:
4b:
model: "qwen3.5:4b"
embedder: qwen3-embedding:8b
9b:
model: "qwen3.5:9b"
embedder: qwen3-embedding:8b
27b:
model: "qwen3.8:27b"
embedder: qwen3-embedding:8b
35b:
model: "qwen3.6:35b"
embedder: qwen3-embedding:8b| tier | model | context window | sized for |
|---|---|---|---|
4b | qwen3.5:4b | 32k | an 8 GB machine; the install default |
9b | qwen3.5:9b | 64k | a 16 GB Mac |
27b | qwen3.8:27b | 64k | a 32 GB Mac |
35b | qwen3.6:35b | 128k | 36 GB or more of unified memory |
The ladder is a recommendation, not a gate. Any Ollama model with native tool-calling binds with /models use <id>. capabilities: declares each model's context window, and an unknown model falls back to an 8,192-token window. /config runtime.num_ctx <size|auto> overrides every window at once. Ollama is the only backend.
::/models
The model page reads your machine (on Apple silicon: the chip, its GPU cores, unified memory, and memory bandwidth) and prices two ladders against it, the four chat tiers and three qwen3-embedding sizes: weights, the memory each needs at its window, an estimated decode speed, and whether it's pulled. The recommendation is the largest tier that fits and decodes at 10 tok/s or better. A tier that fits but would crawl reads fits · slow and is never the default. Below the ladders come every other model you've pulled, chat models and embedders in separate sections, each with its size, parameter count, and whether it fits. The rows are numbered: pick a chat model to run it on the active tier, or an embedder to switch to it. A chat model that can't call tools reads no tool calling and can't be picked. The first launch runs this page.
/models the page, then pick a row (Enter takes the recommendation)
/models list the page only
/models use <id> run any Ollama tool-calling model on this tier
/models embedder <id> switch the embedding model (re-embeds the corpus)A pick whose model isn't pulled asks first (y/N, default no) and switches only after the pull succeeds. Every switch persists to config.yaml; --session applies it live only.
::runtime
| key | default | meaning |
|---|---|---|
max_iterations | 16 | agent passes per turn; from this pass on no tool call runs, and the model answers from what it has |
think | auto | fast never thinks, auto thinks before it acts (and after an error or a steer), deep thinks on every pass; set it with /think. The old off / adaptive / on still read, and anything else runs as auto with a warning at startup |
think_budget | 1024 | the most tokens one thought may spend before it's cut |
auto_approve | read_only | tools at or below this tier run without asking (trust key) |
num_ctx | null (auto) | Ollama context window; null uses each model's declared window |
llm_timeout | 120 | read timeout per model call, so a wedged daemon can't hang a turn |
keep_alive | 30m | how long Ollama keeps the model loaded between requests |
prime | true | re-send the stable prompt prefix between turns so the next turn prefills only what's new |
auto_compact / compact_threshold | true / 0.85 | fold older turns into a summary once the window fills |
citations | true | the numbered Sources list under an answer |
airgap | false | block and record every network exit (trust key) |
quarantine | gate | off / warn / gate for untrusted tool output (trust key) |
receipt | true | the trust segment on the per-answer receipt |
grant_scope | task | how long a gate a grant lives: task / session / persist (trust key) |
::other sections
memory.context_cap / stale_days / review_llm
4000 / 90 / true: the memory block's budget, when an unmatched fact is flagged stale, and whether the review asks the model for proposals
memory.auto_learn
true: keep a fact you state in your own words without asking; false makes every remember ask. Headless runs never auto-learn
web.max_results
results per web_search (default 5); the backend is fixed and keyless
rag.chunk_size / chunk_overlap / k
1000 / 150 / 6; chunking changes force a re-embed
mcp.servers / connect_timeout / call_timeout
see mcp servers
toolkits.<name>
true for every toolkit: false unbinds that group of tools. /tools on|off edits these lines; see toolkits
shell.timeout / shell.env_scrub
60 s per command; secret-shaped variable names stripped from children (trust key)
notify.menubar
false: start the menu bar icon with each launch (macOS)
paths.*
database, documents, workspace (a fallback only), cache, memory, db_sqlite, sessions, snapshots, permissions, exports
::what persists
- –Ordinary keys set with
/configpersist toconfig.yamlby default;--sessionkeeps the change for this session. A save rewrites the key's existing line in place, comments kept. A setting added to Saturn after yourconfig.yamlwas written gets its line appended to the end of its section, and nothing else in the file changes. A key Saturn doesn't know is never written. - –Trust keys (
runtime.auto_approve,runtime.airgap,runtime.quarantine,runtime.grant_scope, andshell.env_scrub) and the/policytoggles are session-only unless you pass--save. A loosened posture is never written to disk silently. - –
/policy risk --saveoverrides, the shell prefix allowlist, and the Shortcuts allowlist persist indatabase/permissions.json./policy allow <prefix>always persists; a gateagrant followsgrant_scope. - –The live
config.yamlis user data that git doesn't track, so saved settings never dirty the repo or break/update.
note
Environment variables use the SATURN_ prefix (SATURN_HOME, SATURN_DEBUG, …), standing instructions live in SATURN.md, and a tier binds one model:. A config.yaml with an old roles: block is refused with the exact line to write instead.