Configuring Models
Subnaut uses two kinds of model slots:
- Main model — what the agent thinks with. Every user message, every tool-call loop, every streamed response goes through this model.
- Auxiliary models — smaller side-jobs the agent offloads. Context compression, vision (image analysis), web-page summarization, approval scoring, MCP tool routing, session-title generation, and skill search. Each has its own slot and can be overridden independently.
This page covers configuring both from the desktop app. If you prefer config files or the CLI, jump to Alternative methods at the bottom. To run models on your own machine instead of a cloud provider, see Local Models.
Nous Portal provides 300+ models under one subscription. On a fresh install, run subnaut setup --portal to log in and set Nous as your provider in one command. Inspect what's wired up with subnaut portal info.
- Portal subscribers also get 10% off token-billed providers.
model: schema — empty string vs. mappingOn a brand-new install the bundled default config has model: "" (an empty string sentinel meaning "not configured yet"). The first time you run subnaut setup or subnaut model, that key is upgraded in-place to a mapping with provider, default, base_url, and api_mode sub-keys — the shape shown throughout this page and in profiles.md / configuration.md. If you ever see an empty string in config.yaml, run subnaut model (or apply a model in the desktop app's Settings → Model) and Subnaut will write the dict form for you.
The Model settings page
In the desktop app, open Settings → Model. The page has three sections:
- The main model — the top section, showing what the agent will spin up for new sessions, plus reasoning defaults when the model supports them.
- Auxiliary models — the side-job slots.
- Mixture of Agents — named MoA presets (see Mixture of Agents).
Like other config-backed settings pages, it follows the Applies to profile selector — see Per-profile settings.
Setting the main model
In the top section:
- Provider — the provider to use. If the one you pick isn't set up yet, the page offers to add its credential right there; you can also add credentials under Settings → Providers.
- Model — the curated model list for the selected provider. These are the agentic models Subnaut recommends for that provider, not the raw
/modelsdump (which on OpenRouter includes 400+ models including TTS, image generators, and rerankers).
Pick a model, click Apply, and Subnaut writes it to ~/.subnaut/config.yaml under the model section. This applies to new sessions only — any chat you already have open keeps running whatever model it started with. To hot-swap the current chat, use the model picker in the composer (or the /model slash command).
Mid-session switches and context warnings
When you switch models inside an active session (Herm TUI model picker, subnaut CLI, or /model on Telegram/Discord), Subnaut estimates whether your next message will run preflight context compression against the new model's window. If the session is already near or above that model's compression threshold (see Context Compression), the switch reply includes a warning — the same warning_message path used for expensive-model notices. The switch still applies immediately; compression runs on the first user message after the switch, before the model answers.
Prompt caches are keyed to the model serving the request, so any mid-conversation model change — an explicit /model switch, an automatic fallback, or a credential-pool rotation onto a different account — means the next message re-reads the entire conversation at full input-token price instead of the cached (~75–90% discounted) rate. On a long session this one-time re-read can dwarf the per-token difference between the two models. Switch when you need to, but prefer doing it early in a conversation or right after starting a fresh session.
Unattended data-training tiers
Models with a -contributor suffix (e.g. muse-spark-1.2-contributor, muse-spark-1.3-contributor) are discounted because the vendor may train on your prompts and completions. Interactive model selection always shows a confirmation prompt. Non-interactive startup paths such as Kanban workers and cron agents fail closed because they cannot ask that question.
If training on the unattended workload's data is acceptable, record a persistent acknowledgement:
subnaut config set security.allow_data_training_tiers_noninteractive true
Subnaut still prints the full data-policy warning and the acknowledgement key on every unattended startup, so worker logs retain an audit trail. This setting does not approve expensive-model or provider-routing warnings, and it does not replace the interactive confirmation prompt. Revoke it with subnaut config unset security.allow_data_training_tiers_noninteractive.
Setting auxiliary models
The Auxiliary models section lists eight task slots: Vision, Compression, Skills hub, Approval, MCP, Title gen, Review, and Curator. Tasks in the table below that don't have a row there are set in config.yaml under auxiliary.<task> — see What gets written to config.yaml.
Every auxiliary task defaults to auto — meaning Subnaut tries your main model for that job too. If that route is unavailable or hits a capacity-style failure, auto follows any task-specific auxiliary.<task>.fallback_chain, then the main fallback_providers / fallback_model chain, then Subnaut' built-in auxiliary discovery chain. Override a specific task when you want a cheaper or faster model for a side-job.
Common override patterns
| Task | When to override |
|---|---|
| Title Gen | When title latency or cost matters more than matching the main model. Pin a known-good flash model, or set auxiliary.title_generation.prefer_fast_model: true to let Subnaut choose the provider's fast tier. |
| Vision | When your main model lacks vision support. Point it at google/gemini-2.5-flash or gpt-4o-mini. |
| Compression | When you're burning reasoning tokens on Opus/M2.7 just to summarize context. A fast chat model does the job at 1/50th the cost. |
| Approval | For approval_mode: smart — a fast/cheap model (haiku, flash, gpt-5-mini) decides whether to auto-approve low-risk commands. Expensive models here are waste. |
| Web Extract | When you use web_extract heavily. Same logic as compression — summarization doesn't need reasoning. |
| Skills Hub | subnaut skills search uses this. Usually fine at auto. |
| MCP | MCP tool routing. Usually fine at auto. |
| Triage Specifier | Routes the Kanban triage specifier (subnaut kanban specify) that expands a rough one-liner into a concrete spec. A cheap, capable model works well. |
| Kanban Decomposer | Routes Kanban task decomposition — splits a triage task into a graph of child tasks for specialist profiles. |
| Profile Describer | Routes profile-description generation (subnaut profile describe --auto / the Auto button in the desktop Kanban view's Orchestration settings). Short, cheap call. |
| Curator | Routes the curator skill-usage review pass. Can run for minutes on reasoning models, so a cheaper aux model is often worthwhile. |
Per-task override
Click Change on any auxiliary row, pick a provider and model, and apply. The row updates to show provider · model instead of auto · use main model.
Reset all to main
If you've over-tuned and want to start over, click Reset all to main at the top of the auxiliary section. Every slot goes back to using your main model.
What gets written to config.yaml
When you save from the desktop app, Subnaut writes to ~/.subnaut/config.yaml (or the selected profile's own config.yaml):
Main model:
model:
provider: openrouter
default: anthropic/claude-opus-4.7
base_url: '' # cleared on provider switch
api_mode: chat_completions
Auxiliary override (example — vision on gemini-flash):
auxiliary:
vision:
provider: openrouter
model: google/gemini-2.5-flash
base_url: ''
api_key: ''
timeout: 120
extra_body: {}
download_timeout: 30
Auxiliary on auto (default):
auxiliary:
compression:
provider: auto
model: ''
base_url: ''
# ... other fields unchanged
provider: auto with model: '' tells Subnaut to use the main model for that task, while still honoring fallback policy if the main route cannot serve the auxiliary call.
Optional task-specific fallback chains live under the same auxiliary task:
auxiliary:
title_generation:
provider: auto
model: ''
fallback_chain:
- provider: openrouter
model: inclusionai/ring-2.6-1t:free
When fallback_chain is absent, auto uses the top-level fallback_providers chain before the built-in auxiliary discovery chain.
Per-provider request options
Provider entries (providers.<name> in the providers: dict, or items in the legacy custom_providers list) accept knobs that shape how Subnaut talks to the endpoint:
extra_headers — a mapping of extra HTTP headers attached to every LLM request routed to that provider's base URL. They are applied last, after URL/profile defaults and user header overrides, so they survive credential swaps and client rebuilds. Useful for Cloudflare Access service tokens, proxy auth, or custom bearer schemes:
providers:
my-gateway:
api: https://llm.internal.example.com/v1
api_key: sk-...
extra_headers:
CF-Access-Client-Id: "xxxx.access"
CF-Access-Client-Secret: "yyyy"
Header values routinely carry credentials — Subnaut never logs them. extra_headers applies to OpenAI-compatible routes; the anthropic_messages and bedrock_converse API modes do not use it.
discover_models — set to false (default true) to skip querying the endpoint's /models listing and use only the models you configured on the entry. Handy for gateways whose model listing is slow, unreliable, or noisy:
providers:
my-gateway:
api: https://llm.internal.example.com/v1
discover_models: false
models:
- my-finetune-v2
- my-finetune-v1
With discovery off, the model picker (subnaut model, /model) shows the configured list instead of a live probe.
openai_native_compaction — set this capability to true only for an OpenAI-compatible endpoint that you trust with conversation content. Native compaction sends its payload to that provider's configured base_url:
providers:
trusted-proxy:
api: https://llm.internal.example.com/v1
capabilities:
openai_native_compaction: true
For a gateway that resolves a bare model alias only after receiving the
request, opt the alias into prompt-cache markers with the per-model
prompt_caching capability:
providers:
model-proxy:
api: https://gateway.example.com/v1
transport: openai_chat # or anthropic_messages
models:
fable:
context_length: 1000000
prompt_caching: true
Subnaut matches this declaration to the exact provider route and runtime model
id, without rewriting the alias or inferring support from its provider name,
host, or model family. The marker layout follows the configured transport:
openai_chat uses the OpenAI-compatible envelope layout and
anthropic_messages uses the native inner-block layout. Set
prompt_caching: false to explicitly disable cache markers for a model; when
omitted, Subnaut keeps its normal provider and model capability detection.
Older configs used a top-level custom_providers: list (with base_url instead of api). It still works and is auto-migrated to the providers: dict on subnaut update (config v12).
Nous Portal: which wire carries Claude
Nous Portal serves its anthropic/* models on two routes: OpenAI-compatible /v1/chat/completions and the native Anthropic Messages wire /v1/messages. nous.anthropic_wire picks one:
nous:
anthropic_wire: chat # default. "native" = the Anthropic Messages wire; "auto" = decide per session
chat is the default for now. The native wire is the better transport (signed thinking blocks pass through unchanged, native cache_control scopes), but on the Portal's OpenRouter-served path it currently re-writes the previous turn's prompt cache on 14–20% of consecutive calls in concurrent tool loops, which is 15–20% of a fan-out's cache-write bill; the chat route measured 0 on the same test. Set native to opt back in (for example once the portal-side fix has shipped). Only anthropic/* models are affected; everything else on Nous already uses chat/completions.
auto is for when the Portal serves the same model from more than one upstream. A session starts on chat, Subnaut reads which upstream answered the first call, and switches that session to native only when the upstream is one where native is known to be clean (the switch happens between calls, so no in-flight response and no warm cache is lost). Today no upstream is cleared, so auto behaves exactly like chat; it exists so the flip can be made from a measurement rather than a config change.
When does it take effect?
- CLI (
subnaut chat): nextsubnaut chatinvocation. - Gateway (Telegram, Discord, Slack, etc.): next new session. Existing sessions keep their model. Restart the gateway (
subnaut gateway restart) if you want to force all sessions to pick up the change. - Desktop app: next new chat. The currently-open chat keeps its model — use the composer's model picker (or
/model) to hot-swap it.
Changes never invalidate prompt caches on running sessions. That's deliberate: swapping the main model inside a session requires a cache reset (the system prompt contains model-specific content), and we reserve that for the explicit /model slash command inside chat.
Troubleshooting
"No authenticated providers" in the picker
Subnaut lists a provider only if it has a working credential. In the desktop app, check Settings → Providers — you should see one of: an API key, a successful OAuth, or a custom endpoint URL. If the provider you want isn't there, run subnaut setup to wire it up, or add its credential under Settings → Providers.
Main model didn't change in my running chat
Expected. The desktop app writes config.yaml, which new sessions read. The currently-open chat is a live agent — it keeps whatever model it was started with. Use the composer's model picker or /model <name> inside the chat to hot-swap that specific session.
Auxiliary override "didn't take effect"
Three things to check:
- Did you start a new session? Existing chats don't re-read config.
- Is
providerset to something other thanauto? If the field showsauto, the task is still using your main model. Click Change and pick a real provider. - Is the provider authenticated? If you assigned
minimaxto a task but don't have a MiniMax API key, that task falls back to the openrouter default and logs a warning inagent.log.
I picked a model but Subnaut switched providers on me
On OpenRouter (or any aggregator), bare model names resolve within the aggregator first. So claude-sonnet-4 on OpenRouter becomes anthropic/claude-sonnet-4.6, staying on your OpenRouter auth. But if you typed claude-sonnet-4 on a native Anthropic auth, it would stay as claude-sonnet-4-6. If you see an unexpected provider switch, check that your current provider is what you expect (subnaut status, or the top of Settings → Model).
Alternative methods
CLI slash command
Inside any subnaut chat session:
/model gpt-5.4 --provider openrouter # session-only
/model gpt-5.4 --provider openrouter --global # also persists to config.yaml
/model claude-opus-4.6 --once # next turn only, then auto-restores
--global does the same thing as applying a model in the desktop app's Settings → Model, plus it switches the running session in-place.
--once switches for a single turn and restores the previous model afterward — on success, error, or interrupt alike. Nothing is persisted: a gateway restart mid-turn comes back on the original model. Useful for escalating one hard question to an expensive model ("ask Opus just this once") or dropping to a cheap model for a throwaway query.
A one-turn switch breaks the provider's prompt-cache prefix twice (switching out and back). In a long session on a cached-prefix provider (Anthropic, OpenAI), the next turn re-pays full input cost — --once wins for short sessions or cheap→expensive escalation, but a quick side question inside a long expensive session can cost more than it saves.
Custom aliases
Define your own short names for models you reach for often, then use /model <alias> in a running session or subnaut chat --model <alias> at startup. There are two equivalent formats — pick whichever fits your workflow.
Canonical (top-level model_aliases:) — full control over provider + base_url:
# ~/.subnaut/config.yaml
model_aliases:
fav:
model: claude-sonnet-4.6
provider: anthropic
grok:
model: grok-4
provider: x-ai
An alias that points at its own endpoint can also carry that endpoint's
credential, with either api_key (a literal, or a "${VAR}" reference) or
key_env (the name of an environment variable). If both are set, api_key
wins:
model_aliases:
theta:
model: theta-1
provider: custom
base_url: "https://theta.example.com/v1"
key_env: THETA_API_KEY # or: api_key: "${THETA_API_KEY}"
When an alias sets neither, the key is resolved from the alias host —
OLLAMA_API_KEY for an ollama.com endpoint, DEEPSEEK_API_KEY for
api.deepseek.com, and so on. It is never inherited from whichever provider
happened to be active before the switch, so switching to an alias cannot send
one provider's secret to another provider's host.
Short string form (model.aliases.<name>: provider/model) — convenient from the shell because subnaut config set writes scalars and now also parses inline list/mapping literals, though this short alias form still can't carry a custom base_url:
subnaut config set model.aliases.fav anthropic/claude-opus-4.6
subnaut config set model.aliases.grok x-ai/grok-4
subnaut config setalso accepts inline list/mapping literals (JSON/YAML flow style). Quote them so your shell passes them through intact:subnaut config set platform_toolsets.line '["clarify", "file", "web"]'subnaut config set display.tool_progress_overrides '{"terminal": "off"}'
Both paths feed the same loader (subnaut_cli/model_switch.py). Entries declared in model_aliases: take precedence over model.aliases: entries with the same name.
Then /model fav or /model grok in chat. User aliases shadow built-in short names (sonnet, kimi, opus, etc.). See Custom model aliases for the full reference.
subnaut model subcommand
subnaut model # Interactive provider + model picker (the canonical way to switch defaults)
subnaut model walks you through picking a provider, authenticating (OAuth flows open a browser; API-key providers prompt for the key), and then choosing a specific model from that provider's curated catalog. The choice is written to model.provider and model.default in ~/.subnaut/config.yaml.
To list providers/models without launching the picker, use the desktop app's Settings → Model or the REST endpoints below. To inspect what the CLI will actually use right now: subnaut config get model --json and subnaut status.
Direct config edit
Edit ~/.subnaut/config.yaml and restart whatever reads it. See the Configuration reference for the full schema.
REST API
These are the endpoints the desktop app uses, served by the subnaut serve backend. Useful for scripting:
# List authenticated providers + curated model lists
curl -H "X-Subnaut-Session-Token: $TOKEN" http://localhost:PORT/api/model/options
# Read current main + auxiliary assignments
curl -H "X-Subnaut-Session-Token: $TOKEN" http://localhost:PORT/api/model/auxiliary
# Set the main model
curl -X POST -H "Content-Type: application/json" -H "X-Subnaut-Session-Token: $TOKEN" \
-d '{"scope":"main","provider":"openrouter","model":"anthropic/claude-opus-4.7"}' \
http://localhost:PORT/api/model/set
# Override a single auxiliary task
curl -X POST -H "Content-Type: application/json" -H "X-Subnaut-Session-Token: $TOKEN" \
-d '{"scope":"auxiliary","task":"vision","provider":"openrouter","model":"google/gemini-2.5-flash"}' \
http://localhost:PORT/api/model/set
# Assign one model to every auxiliary task
curl -X POST -H "Content-Type: application/json" -H "X-Subnaut-Session-Token: $TOKEN" \
-d '{"scope":"auxiliary","task":"","provider":"openrouter","model":"google/gemini-2.5-flash"}' \
http://localhost:PORT/api/model/set
# Reset all auxiliary tasks to auto
curl -X POST -H "Content-Type: application/json" -H "X-Subnaut-Session-Token: $TOKEN" \
-d '{"scope":"auxiliary","task":"__reset__","provider":"","model":""}' \
http://localhost:PORT/api/model/set
Requests are authenticated with the backend's session token (X-Subnaut-Session-Token) when the backend is bound to loopback; a backend bound beyond loopback requires a login session instead — see Backend authentication providers.