Skip to main content

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.

Fastest path: Nous Portal

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. mapping

On 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:

  1. The main model — the top section, showing what the agent will spin up for new sessions, plus reasoning defaults when the model supports them.
  2. Auxiliary models — the side-job slots.
  3. 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 /models dump (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.

Mid-session switches reset the prompt cache

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

TaskWhen to override
Title GenWhen 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.
VisionWhen your main model lacks vision support. Point it at google/gemini-2.5-flash or gpt-4o-mini.
CompressionWhen 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.
ApprovalFor 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 ExtractWhen you use web_extract heavily. Same logic as compression — summarization doesn't need reasoning.
Skills Hubsubnaut skills search uses this. Usually fine at auto.
MCPMCP tool routing. Usually fine at auto.
Triage SpecifierRoutes the Kanban triage specifier (subnaut kanban specify) that expands a rough one-liner into a concrete spec. A cheap, capable model works well.
Kanban DecomposerRoutes Kanban task decomposition — splits a triage task into a graph of child tasks for specialist profiles.
Profile DescriberRoutes profile-description generation (subnaut profile describe --auto / the Auto button in the desktop Kanban view's Orchestration settings). Short, cheap call.
CuratorRoutes 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.

Legacy format

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): next subnaut chat invocation.
  • 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:

  1. Did you start a new session? Existing chats don't re-read config.
  2. Is provider set to something other than auto? If the field shows auto, the task is still using your main model. Click Change and pick a real provider.
  3. Is the provider authenticated? If you assigned minimax to a task but don't have a MiniMax API key, that task falls back to the openrouter default and logs a warning in agent.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.

Prompt-cache cost

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 hostOLLAMA_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 set also 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.