Agent engine config

Configure the session agent's model, gateway, prompt, reasoning effort, and sampling parameters with agent_engine_config, and assign models to the roles installed plugins declare.

The agent_engine_config object in settings.json configures the engine:

default

The one role the core loop has: stella chat, the Command Deck, and a plain stella run. default_model sets its model. This is the only model setting stella ships with.

seats

A model for a participant an installed plugin declares. Keyed as <plugin-id>/<role>. Unset by default, so a plugin runs on one model until you say otherwise. See Seats.

Every field is optional. It merges across the same user, org-managed, and project scope chain as the rest of settings.json, field by field. A project can pin the model while your user scope keeps everything else.

An unrecognized key loads normally and prints a line naming what to write instead — nothing breaks, and nothing is silently ignored either. Set default_model for the session's own model, and a seat for anything a plugin runs.

Full schema

~/.stella/settings.json
{
  "agent_engine_config": {
    // The session's model ("provider/slug", or a bare catalog slug).
    "default_model": "zai/glm-5.2",

    // A model for a role an installed plugin declared. Keyed
    // "<plugin-id>/<role>". A seat with no entry runs on the session's model.
    "seat_models": {
      "vera/verifier": "openrouter/openai/gpt-5.5"
    },

    // The model vocabulary the TUI pickers offer, and the ceiling on seat
    // assignments.
    "allowed_models": ["anthropic/claude-fable-5", "zai/glm-5.2",
                        "openrouter/openai/gpt-5.5"],

    // "on" = let stella choose rather than a pin. It does not select a
    // model (see Auto modes); it marks whether a /profile holds the dials.
    "auto_mode": "off",
    // "on" = reasoning effort chosen for you, overriding "effort" below.
    "effort_auto": "off",
    // "on" = thinking mode chosen for you, overriding "reasoning" below.
    "reasoning_auto": "off",
    // "on" = the minimal base system prompt: a bare tool advertisement, so
    // your own prompt fields carry the prose. `--minimal` for one invocation.
    "minimal_prompt": "off",

    // Seconds of provider silence that end one generation. The partner of
    // params.max_tokens below: raise the output cap without raising this and
    // long steps stop on the timeout instead of the cap.
    "model_timeout_secs": 816,

    // The agent's deep config. Set a field and it goes on the wire; leave it
    // out and the provider default applies.
    "agents": {
      "default": {
        "provider": "openrouter",          // gateway: the slug goes to THIS
        "model": "openai/gpt-5.5",         // provider verbatim
        "prompt": "You are a terse, test-first engineer.",
        "effort": "high",                   // low | medium | high | xhigh | max
        "reasoning": "on",                  // thinking mode on/off
        "params": {
          "temperature": 0.2, "top_p": 0.9, "top_k": 40,
          "frequency_penalty": 0.0, "presence_penalty": 0.0,
          "repetition_penalty": 1.0, "max_tokens": 4096, "seed": 7,
          "verbosity": "low",               // low | medium | high
          "service_tier": "priority"        // auto | default | flex | priority
        }
      }
    }
  }
}

Seats

A plugin describes a process. That process can have several participants: a planner, a reviewer, a second opinion. The plugin never asks for a model. It declares the roles it needs, and you decide which ones are worth a model of their own.

.stella/settings.json
{
  "agent_engine_config": {
    "default_model": "zai/glm-5.2",
    "seat_models": {
      "vera/verifier": "anthropic/claude-fable-5"
    }
  }
}

Or in stella.toml, where it is a section of its own:

stella.toml
[seats]
"vera/verifier" = "anthropic/claude-fable-5"
  • A seat with no entry runs on the session's model. If a plugin has five participants, installing it costs the same as a single-model session, until you add a line here. stella never picks a second model for you on its own — that would be a guess about a role it doesn't understand.
  • The key is always <plugin-id>/<role>. The plugin declares only the plain role name, and stella adds the prefix. This means one plugin can't take over a setting you made for another. A reviewer role you configured for one plugin is never inherited by a different plugin that also declares a reviewer role.
  • allowed_models sets the ceiling here too. If a seat names a model outside a non-empty list, stella refuses it, shows a notice, and falls back to the session's model.

Quote the key in TOML. vera.verifier is dotted-key syntax and would parse as a table vera containing verifier.

Model precedence

The session's model. First hit wins:

  1. The --model CLI flag (or STELLA_MODEL). This suppresses the settings below for this invocation.
  2. agents.default.model, sent verbatim to agents.default.provider when that is set
  3. default_model
  4. Auto-detection / the provider's own default

A plugin's seat. First hit wins:

  1. seat_models["<plugin-id>/<role>"]
  2. No entry: the seat runs on the session's model resolved above.

When --model suppresses a configured model, the run prints a note on stderr — engine config: model ... skipped — --model pinned ... for this invocation — so a dropped setting is never silent. Settings control the model whenever the flag is absent.

Every override here is soft. If a configured model's provider has no credential, or its adapter fails to build, stella skips it with a notice on stderr. It then falls back to the session's model, exactly as it would with no config at all. The notice names what was skipped and what runs instead:

  ! seat `vera/verifier`: no credential resolved for provider `openrouter` —
    this seat runs on the session's model.

stella config prints the resolved provider, session-default model, credential source, base URL, and dialect. These are the inputs those chains start from. stella models list --all shows which slugs each provider will accept:

stella config
stella models list --all

One gateway per role

The agent's provider field sends its model slug straight to that provider, with no prefix splitting. Seat assignments take a full provider/slug string. Together, this lets you mix billing relationships — your own key for each provider — in one run:

~/.stella/settings.json
{
  "agent_engine_config": {
    "default_model": "anthropic/claude-fable-5",   // the session — your Anthropic key
    "seat_models": {
      "vera/verifier": "openrouter/openai/gpt-5.5" // your OpenRouter key
    }
  }
}

Two keys, two bills, one run:

export ANTHROPIC_API_KEY=sk-ant-...
export OPENROUTER_API_KEY=sk-or-...
stella run --pipeline vera "add a per-provider breaker cooldown"

Without a provider field, model strings follow --model rules: provider/slug splits on the first / when the prefix names a known provider. So openrouter/openai/gpt-5.5 routes openai/gpt-5.5 through OpenRouter. A bare slug resolves through the model catalog instead.

Auto modes

Set any of these to "on" and stop thinking about it:

auto_mode

It does not select a model. What it marks is whether a /profile holds the dials or stella does.

Default off

effort_auto

Reasoning effort chosen for you, overriding agents.default.effort.

Default off

reasoning_auto

Thinking mode chosen for you, overriding agents.default.reasoning.

Default off

minimal_prompt

Run every session on the minimal base system prompt. This is a bare tool list in place of the built-in persona, so the model is steered by what you configure instead: agents.default.prompt (which adds to the minimal base rather than replacing it), workspace memories, rules, and SessionStart hook context. --minimal (or STELLA_MINIMAL=1) forces it on for one run. The flag can never turn off a mode you configured. In stella.toml this is [agents] minimal_prompt, and the interactive session's SETTINGS → AGENTS pane shows it as a GLOBAL toggle.

Default off

headless_scope_bypass

Setting this does nothing. No code reads this value, at any setting.

The key still loads and merges instead of being rejected as unknown. This is on purpose: it's part of a published benchmark setup, and removing the key would break scores that were already reported. If you have this set, delete it — nothing is waiting on you to use it.

Default off

model_timeout_secs

Seconds of silence from the provider that end a single generation. This limits the gap between stream fragments, not the total time, so a step that keeps streaming is never cut off. 0 removes this limit entirely.

Set it alongside params.max_tokens, not instead of it. The two work as one budget. If you allow 128,000 output tokens but size the timeout for 64,000, a step can hit the timeout first. That looks like a model that couldn't finish, when really the timeout just wasn't scaled to match.

Default 816

When an auto is "on", it overrides the matching per-agent setting. That's the rule.

The smallest useful config is one line:

.stella/settings.json
{
  "agent_engine_config": {
    "default_model": "zai/glm-5.2"
  }
}

Add a second model only where an installed plugin has a participant worth running on one:

.stella/settings.json
{
  "agent_engine_config": {
    "default_model": "zai/glm-5.2",
    "seat_models": { "vera/verifier": "anthropic/claude-fable-5" }
  }
}

Output token ceilings

By default, every model is asked for its own maximum output. stella reads that ceiling from the model catalog, which learns it from the provider's own /models endpoint. You never have to configure anything to get a full-length answer. A cap set below the model's ceiling decides where work stops instead of the model deciding, and a step cut off in the middle of reasoning sends no tool call and does no work at all.

So the only thing worth configuring here is asking for less than the model allows. You might want to bound cost on a long unattended run, cut latency, or match a comparator that stops lower.

--max-output-tokens

stella run "audit the auth module" --max-output-tokens 32000

This applies for one run only. It's a global flag that applies to every call in that run, also settable as STELLA_MAX_OUTPUT_TOKENS. Use it when you want one run to behave differently, or when you're debugging a configured value — it outranks both settings below for exactly that reason.

[models.output_caps]

[models.output_caps]
"anthropic/claude-sonnet-5" = 64000   # this model on this provider
"deepseek-chat" = 32000               # this model on any route

This applies for the current workspace. Keys are provider/slug or a bare slug. The provider-qualified form wins when both match, so you can cap a model only on the gateway you pay per token for. Entries merge per model across settings scopes, so a project pinning one model never drops your pins on others.

This only overrides the default; it never defines a new one. A model that's missing from the table just gets its own maximum, so a model that ships tomorrow needs no entry. Deleting an entry restores the correct default instead of leaving a stale number behind.

Precedence

Highest wins:

  1. --max-output-tokens (this invocation)
  2. [models.output_caps] (per model)
  3. agents.default.params.max_tokens under agent_engine_config
  4. the model's own ceiling, from the catalog (the default)

A value above the model's real ceiling is clamped, not sent. Asking a provider for more output than the model can write doesn't get you a longer answer. It gets a rejection on every request, which still bills the round trip and then needs a retry. Clamping gives you what an oversized number was asking for anyway.

0 is refused, not read as "unlimited." Leaving the field empty already means "the model's own maximum," so 0 has no second meaning left to carry. This is different from model_timeout_secs, where 0 does mean "no limit."

Where ceilings come from

providerfield read from its /models
Anthropicmax_tokens (output) and max_input_tokens (context)
OpenRoutertop_provider.max_completion_tokens
GeminioutputTokenLimit
OpenAI-compatiblemax_output_tokens or max_completion_tokens

stella deliberately does not read a bare max_tokens field on an OpenAI-compatible listing. On some gateways it means the output cap; on others it means the whole context window. Reading it the wrong way would set a ceiling many times larger than the model's real one.

Bedrock and Vertex don't publish a listing stella can read, so those rows carry a seeded value instead. stella models refresh (and the startup sync) replaces seeded numbers with the provider's own numbers wherever one is published.

One model's number is an estimate: grok-4. No catalog source publishes a cap for that exact slug. Its close relatives — grok-4.3 and the grok-4.20-* variants — all report 30,000, so that's what ships. The newer grok-4.5 reports 500,000, but using a different generation's number could cause a rejection on every request, instead of just leaving some headroom unused. This gets corrected automatically the first time xAI's listing publishes a real number.

Generation parameters

Every entry under params follows one rule: if you don't set a field, it's left out and the provider's own default applies. This keeps the request body identical to having no config at all, which keeps prompt caching intact. If you do set a field, it's sent on the wire.

Each provider adapter forwards only the parameters its wire format supports, and maps reasoning to that provider's own mechanism:

Z.ai (GLM)

GLM's own request-level thinking object. Also forwards top_p, top_k, frequency_penalty, presence_penalty, repetition_penalty, and seed.

Reasoning
thinking: {"type": "enabled"|"disabled"}
OpenRouter

The normalized reasoning object. Some upstream models behind OpenRouter make reasoning mandatory and reject reasoning: {enabled: false} with a hard 400 error. When that happens, stella resends the request once without the object, instead of losing the call — turning reasoning "off" is a cost preference, not a requirement for correct output.

Reasoning
reasoning object (effort / enabled)
xAI (Grok)

Sent only for Grok models that accept this parameter. grok-4 answers it with a 400 error, so this is limited by model, not just by provider. The grok-4.3 default accepts it.

Reasoning
top-level reasoning_effort
Anthropic

Models from Claude 4.6 onward, including the 5 family, take thinking: {"type": "adaptive"} plus output_config.effort, and reject budget_tokens and the sampling parameters. Models 4.5 and earlier use {"type": "enabled", "budget_tokens": N} with a budget based on effort tier, and do accept sampling — though temperature is left out whenever thinking is on, because the Messages API rejects any value but 1 alongside it.

Reasoning
extended thinking, two dialects
OpenAI

Plus text.verbosity and service_tier from params. temperature and top_p are left out for reasoning models — an API rule, not a choice stella makes.

Reasoning
reasoning.effort
Gemini / Vertex

Gemini 3 can't fully turn off thinking, so "off" maps to the lowest level the model documents — as close to "no thinking" as the model allows. verbosity, service_tier, and repetition_penalty are dropped.

Reasoning
thinkingConfig.thinkingLevel
DeepSeek, local, and settings-defined providers

There's no reasoning field that works across every Chat Completions server, and an unknown key risks a hard 400 error on a server you didn't choose to experiment with — so these providers send neither shape. Sampling parameters still go through.

Reasoning
not sent

If a provider can't accept a parameter, stella drops it silently. An unsupported setting never fails a request.

Custom prompts

agents.default.prompt replaces the built-in base instructions for the session agent. Workspace memories and rules still get added on top — they're workspace context, not part of the base persona.

A plugin's participants are the plugin's own: it ships their instructions, and agent_engine_config decides only which model each one runs on.

Config panel

Everything above is editable in the Command Deck. The editor is the full-width body of the SETTINGS tab, the home for all config. Open it with /settings or by tabbing to it:

  • Press e on the SETTINGS tab to focus the panel. It has a GLOBAL tab (auto modes, allowed_models) and the agent's own tab, in the order GLOBAL default. Navigate with ↑/↓. Press ⏎ to edit a row: enum rows cycle, and numeric or text rows take inline input. Press x to clear a field back to "provider default." Press Esc to hand the keyboard back to the tab.
  • Press ⏎ on the model row to open a type-to-filter model picker. It lists your allowed_models, or the full catalog when that list is empty. The SETTINGS tab is the one place you configure models — there are no per-agent slash commands.
  • Press s to save to your user settings (~/.stella/settings.json). Press S to save to the project (<workspace>/.stella/settings.json). Saves keep every other key in the file untouched, and apply to runs started from then on.

Degradation is always soft

Configuration can never turn a runnable session into an error. If a configured model's provider has no credential, or its adapter fails to build, stella skips it with a visible notice. It then falls back to the session's model, exactly as it would with no config at all.

The router that resolves each seat follows the same rule while a run is in progress:

  • Per-provider circuit breakers. Three transport failures in a row open a provider's breaker. Routing then skips that provider and reports the fallback visibly — it never switches silently in the middle of a session. After a cooldown, one trial call decides whether the breaker closes again. See Troubleshooting for what an open breaker looks like.
  • Soft degradation everywhere. A configured seat model whose provider has no credential falls back to the session's model with a notice.

agent_engine_config carries no credential routing of its own. An agent's provider field references provider ids, and their base_url and api_key_env stay protected by the project-scope trust boundary. A cloned repo can suggest models, but it can't redirect your keys.