Repository navigation
user configuration
steiner is configured through a YAML file. This document covers every field
in the Config struct, their types, defaults, and valid values.
For copy-paste provider, raw model reference, profile, and runtime selection recipes, see the Provider and model setup guide. Aliases are optional and are useful when they persist model settings.
Configuration is loaded and merged in the following precedence order (later entries win):
- Compiled defaults
-
~/.config/steiner/config.yaml— user-level config -
.steiner/config.yaml— project-level config (checked in or gitignored); applied only once the project is trusted, see Project trust - Environment variables with the
STEINER_prefix - CLI flags (
--profile,--model,--verbose,--unsafe)
--unsafe is applied as a config override that forces sandbox.enabled=false
after config files and environment variables have been merged.
Neither config file is created for you, and there is no init command; create
.steiner/config.yaml yourself. The project file is relative to the project
root: <project>/.steiner/config.yaml. With no config at all, steiner uses a
built-in default: a local provider of type openai_compat at
http://localhost:11434/v1 serving the model local/qwen3-35b-a3b. A model call
then fails unless something is listening there. See
Getting started for a worked first run.
Key environment variables:
| Variable | Maps to |
|---|---|
STEINER_MODEL |
active orchestrator model override |
STEINER_SUB_AGENTS_MAX_PARALLEL |
sub_agent.max_parallel |
STEINER_MAX_TURNS |
limits.max_turns |
STEINER_MAX_TOKENS |
limits.max_tokens |
STEINER_LOG_LEVEL |
logging.level |
STEINER_LOG_FILE |
logging.file |
STEINER_TOOL_OUTPUT_MAX_BYTES |
limits.tool_output_max_bytes |
STEINER_MAX_PARALLEL_TOOLS |
limits.max_parallel_tools |
STEINER_TUI_FPS |
tui.fps |
STEINER_TRUST_PROJECT_CONFIG |
trusts the current project's config for this run only, skipping the trust dialog (see Project trust) |
Configuration values support shell-style environment variable expansion. The expansion applies to scalar values only—keys and comments are never expanded. An undefined variable reference is a hard error that names the file, line number, YAML path, and variable name.
Recognized forms:
-
${VAR}— replaced with the value ofVAR. IfVARis undefined, loading fails. IfVARis defined but empty, it expands to nothing (empty string). -
${VAR:-default}— replaced with the value ofVARif defined and non-empty. IfVARis undefined or empty,defaultis used instead (and recursively expanded). Use${VAR:-}to allow an empty value and opt out of the error. -
$VAR— bare form, replaced with the value ofVAR. IfVARis undefined, loading fails. No defaults; bare form has no:-defaultsyntax. -
$$— escape sequence for a literal$character.
Behavior details:
- Backslash is not an escape character; write
\\to mean a backslash. - Undefined references are not silently dropped; every undefined variable in every config file is reported together in a single error listing the file, line, YAML path, and variable name.
-
${VAR:-}with an empty default is valid and does not error even ifVARis undefined. - Expanded values round-trip through YAML correctly, including those that look numeric (e.g.
PORT: "${PORT}"remains a string after expansion).
Example:
providers:
openai:
type: openai
base_url: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
api_key: ${OPENAI_API_KEY}
logging:
file: ~/.local/share/steiner/${STEINER_LOG_FILE:-steiner.log}If OPENAI_API_KEY is not set, configuration loading fails. If OPENAI_BASE_URL is not set, it defaults to the URL shown.
The first time steiner runs in a project directory it hasn't seen before, it shows a trust dialog before applying that project's .steiner/config.yaml — whether or not the project actually has one. The trust key is the symlink-resolved absolute working directory, not a discovered git or project root, so running steiner from a subdirectory of an already-trusted project prompts again.
When the project has a config file, the dialog lists every leaf setting it overrides versus the global config, one line per field. Security-relevant fields are marked with a leading ! and shown with warning styling: sandbox, permissions, paths.writable_paths/blocked_paths/project_root_only, most of mcp.servers.* (all fields except a few benign ones), lsp.servers.*.command/args/env, tools.*.exec/subcommand, and providers.*.base_url/headers/api_key/api_key_env. Secret-shaped values on the global side (api_key fields, anything under headers or env) are masked as (set); the project's own values are always shown in full and unexpanded, so a ${VAR} reference shows which environment variable would be used, not its value. If the project YAML fails to parse, the dialog still appears with a parse-error message in place of the diff.
Three choices:
- [a] Always trust — persists the decision; later changes to that project's config never re-prompt.
- [s] This session only — trusts for this run; nothing is written to disk.
-
[d] Deny and exit — the default, and what
Esc/Ctrl-Cchooses; the process exits non-zero.
"Always trust" decisions are recorded in ~/.config/steiner/trusted_projects.json. There is no revoke or list command; edit or delete that file directly to revoke trust for a project. A corrupt or unrecognized-version file is deleted automatically, and you'll see a one-time notice; a plain read/permission error is a hard error instead, and the file is left untouched.
The dialog needs a terminal: with --exec, or with no TTY, steiner can't show it. An untrusted project then makes the process exit non-zero with an error naming the fix. steiner oneshot follows the same plain-TTY rule as any other command.
For scripted or CI use, pass --trust-project-config or set STEINER_TRUST_PROJECT_CONFIG=1 (or true, case-insensitive) to trust the current project for that run only, skipping the dialog; neither persists anything.
steiner config is an exception: it loads and prints the project's config values without prompting or persisting trust, prepending a # project <root> is NOT trusted header line when the project isn't in the trust store.
--unsafe is independent of project trust in both directions: it never affects the trust dialog, and trusting a project never affects --unsafe.
| Field | Type | Default | Description |
|---|---|---|---|
models |
block | see below | Consolidated model configuration: shared model definitions and named execution profiles. |
modes |
block | see below | Execution mode configuration. |
cave_human |
bool | false |
When true, enables cave_human - combines terse output with an "avoid AI-writing tells" instruction that is applied to the system preamble, compaction prompts, and sub-agent prompts. |
advisor |
block | see below | Optional stronger-model steering config. When enabled, the advisor tool is available to the main loop and its per-run cap is enforced in handler state so the tool registry stays static for prompt-cache integrity. |
oneshot |
block | see below | Closeout settings for autonomous oneshot runs. Per-phase model assignments live in the selected model profile. |
desktop_notifications |
block | see below | Desktop notification settings for run completion and events. |
update_check |
block | see below | Passive startup version-upgrade check settings. |
mcp |
block | see below | Model Context Protocol server configuration. |
lsp |
block | see below | Language server protocol configuration for code intelligence (definitions, references, diagnostics). |
tui |
block | see below | Interactive terminal UI settings. |
Controls the optional advisor reasoning pass. The advisor is disabled by default.
When enabled, it uses a stronger model to review the live parent conversation and
return concise strategic guidance. The tool definition stays stable for the whole
session; the max_uses_per_run cap is enforced in shared handler state, persisted
for the life of the process rather than reset each turn, rather than by removing
or mutating the tool mid-conversation.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Master switch. Set to true to enable the advisor tool and prompt steering. |
max_uses_per_run |
int | 3 |
Per-session use cap enforced in shared handler state. When the cap is exhausted, the handler returns a budget-exhausted result instead of calling the advisor model. |
max_uses_per_sub_agent |
int | 1 |
Per-child use cap for the advisor tool, separate from max_uses_per_run. Applies only to code, review, and evaluate children; caps advisor calls for that child's whole lifetime, surviving follow_up resumption of the same agent ID. |
max_tokens |
*int | nil |
Optional output-token ceiling for advisor calls. When set, the value is forwarded to the provider request. |
timeout |
*Duration | 180s |
Optional HTTP timeout override applied only to advisor calls. When set, it overrides providers.<name>.timeout for the advisor model only; the main chat model and other models using the same provider are unaffected. Useful because advisor calls send a large parent-conversation prompt and frequently hit the provider's default header-read timeout. |
The model reference used for advisor calls is configured in the selected profile's advisor field (see the models block), not under advisor itself. Use a raw provider/model-id reference unless the advisor needs a configured alias for persistent ModelConfig settings. See Configuration internals for handler and prompt-cache mechanics.
advisor:
enabled: true
max_uses_per_run: 2
max_tokens: 256
timeout: 5m
providers:
local:
type: ollama
base_url: http://localhost:11434/v1
models:
profiles:
default:
default_model: local/qwen2.5-coder:14b
advisor: local/<advisor-model-id>Controls the optional closeout PR flow for the autonomous oneshot mode.
Per-phase model assignments live in the selected profile's oneshot map (see the
models block) and are sparse: omit a phase to let runtime use
that profile's default_model when the phase is resolved.
| Field | Type | Default | Description |
|---|---|---|---|
auto_pr |
bool | false |
When true, oneshot closeout may push the branch and open a PR/MR after a passing review. |
oneshot:
auto_pr: false
providers:
local:
type: ollama
base_url: http://localhost:11434/v1
models:
profiles:
default:
default_model: local/<coder-model-id>
oneshot:
plan: local/<planner-model-id>
implement: local/<coder-model-id>
review: local/<reviewer-model-id>Controls desktop notification behavior for run completion and events. All fields are optional and default to disabled (no notifications).
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Master switch. Set to true to enable desktop notifications. |
duration |
int | 0 |
Notification display duration in seconds. Set to 0 for persistent/sticky notifications that do not auto-dismiss. Set to a positive integer to auto-dismiss after that many seconds. Must be >= 0. |
desktop_notifications:
enabled: true
duration: 5Setting duration: 0 creates permanent notifications that the user must manually dismiss:
desktop_notifications:
enabled: true
duration: 0Controls the passive startup version-upgrade check. This check runs asynchronously at startup and notifies the user if a newer version is available. All fields are optional and default to enabled with a 6-hour interval.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Master switch. Set to false to disable the startup version-upgrade check. |
interval_hours |
int | 6 |
Minimum hours between consecutive update checks. Set to a non-negative integer. Must be >= 0. When set to 0, every startup checks. |
update_check:
enabled: true
interval_hours: 6To disable the check entirely:
update_check:
enabled: falseInteractive terminal UI settings. Ignored outside interactive mode.
| Field | Type | Default | Description |
|---|---|---|---|
fps |
int | 60 |
Renderer frame rate. Bubble Tea flushes the terminal on this ticker, so it bounds how long a processed keystroke waits before it becomes visible - the average wait is half a frame (~8.3ms at 60, ~4.2ms at 120). Must be between 1 and 120. |
Raising fps reduces input latency and increases CPU proportionally: renderer
flush is roughly three quarters of TUI CPU cost, so moving from 60 to 120
roughly doubles the flush work (measured at +61% process CPU during a
150 token/s stream at 220x60). Raise it if input feels sluggish and you have
CPU headroom; leave it at 60 on battery or on a constrained machine.
tui:
fps: 60A map of named provider configurations. Each entry describes how steiner
connects to a model API. The key is an arbitrary name referenced by
models.definitions.*.provider.
providers:
<name>:
type: <provider_type>
...| Field | Type | Default | Description |
|---|---|---|---|
type |
string (ProviderType) | — | The provider type. See Provider types below. |
base_url |
string |
"http://localhost:11434/v1" (for the built-in local provider) |
Base URL for the API endpoint. |
api_key |
string | — | API key value. Prefer api_key_env to avoid committing secrets. |
api_key_env |
string | — | Name of an environment variable containing the API key. Loaded at startup. |
headers |
map[string]string | — | Additional HTTP headers sent with every request to this provider. |
timeout |
duration string | "30s" |
Per-request HTTP timeout. Accepts Go duration strings: 30s, 2m, etc. |
codex |
block | see below | Codex-specific configuration (provider type codex only). |
| Type | Description |
|---|---|
openai_compat |
Generic OpenAI-compatible HTTP API. Works with any server that follows the OpenAI chat completions shape. |
ollama |
Ollama server through its OpenAI-compatible API. base_url defaults to http://localhost:11434/v1. |
lmstudio |
LM Studio's built-in OpenAI-compatible server. Typically at http://127.0.0.1:1234/v1. |
openrouter |
OpenRouter cloud gateway. Requires api_key or api_key_env. No base_url needed. |
openai |
Native OpenAI API. Requires api_key or api_key_env. No base_url needed. |
anthropic |
Native Anthropic API. Requires api_key or api_key_env. No base_url needed. |
gemini |
Not implemented by the runtime provider factory. Native Gemini is not a runtime-supported provider type. If a user has a Gemini-compatible endpoint that exposes the supported OpenAI-compatible shape, configure that endpoint with type: openai_compat instead. |
litellm |
LiteLLM gateway endpoint. Works like openai_compat but with LiteLLM-specific retry handling: when a 429 response lacks a Retry-After header, steiner parses the delay from the response body (e.g. "Try again in N seconds"). Budget-exhaustion 429s are detected and treated as non-retryable (reported as a usage limit; see Retry). Set base_url to your LiteLLM server. |
codex |
OpenAI Codex subscription via OAuth. Authenticates using your OpenAI account instead of an API key and uses the Responses wire format. Run steiner login codex before use. When login can exchange the ChatGPT ID token for an API-key style credential, Steiner sends requests to https://api.openai.com/v1/responses; otherwise it uses https://chatgpt.com/backend-api/codex/responses with the saved OAuth access token and ChatGPT-Account-ID. api_key and api_key_env are not used - authentication is managed by the OAuth token stored at ~/.config/steiner/codex_auth.json. Older token files still load, but re-running steiner login codex refreshes stored ChatGPT account metadata and the optional exchanged API credential used for direct OpenAI Responses API calls. |
opencode_go |
opencode.ai's OpenCode Go gateway. base_url defaults to https://opencode.ai/zen/go/v1. Requires api_key or api_key_env (generated in your account on the OpenCode website — no OAuth/login flow in steiner). Every request automatically carries an X-Opencode-Session header set to steiner's stable per-session ID. |
opencode_zen |
opencode.ai's OpenCode Zen gateway. base_url defaults to https://opencode.ai/zen/v1. Requires api_key or api_key_env, same as opencode_go. Claude-family models served through Zen automatically dispatch over the Anthropic-native transport (via the existing models.dev-driven transport fallback) while still carrying the X-Opencode-Session header. |
Field applicability by provider type:
| Field | openai_compat | ollama | lmstudio | openrouter | openai | anthropic | gemini¹ | litellm | codex | opencode_go | opencode_zen |
|---|---|---|---|---|---|---|---|---|---|---|---|
base_url |
required | optional | required | — | — | — | — | required | optional | optional | optional |
api_key |
optional | — | — | ✓ | ✓ | ✓ | ✓ | optional | — | ✓ | ✓ |
api_key_env |
optional | — | — | ✓ | ✓ | ✓ | ✓ | optional | — | ✓ | ✓ |
headers |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
timeout |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
codex |
— | — | — | — | — | — | — | — | ✓ | — | — |
¹ Native gemini is not a runtime-supported provider type. A user-provided endpoint may use openai_compat only when it exposes the supported OpenAI-compatible shape.
Codex-specific configuration. Applies only when type: codex.
| Field | Type | Default | Description |
|---|---|---|---|
min_request_interval |
duration string | "0" |
Minimum interval enforced between consecutive Codex requests. Defaults to 0 (disabled). When set to a positive duration (e.g., 4s), enforces a minimum gap between requests and serialises them. Affects only bursts; has no effect on interactive use where think-time already far exceeds any sensible interval. Has no effect on cache hit rate (see cache statistics internals). |
transport |
string | "http" |
Transport used for Codex requests. Valid values: http (default), websocket. http: use HTTP-only transport. websocket: use the WebSocket transport, with no HTTP fallback — failures return an error rather than silently degrading. Opt-in and experimental; see cache statistics internals for why it is not the default. |
Consolidates all model configuration: the shared model definition registry and
named execution profiles. The default profile is required and is the complete
baseline. Other profiles are partial overlays: omitted fields and map entries
inherit from models.profiles.default when selected.
| Field | Type | Default | Description |
|---|---|---|---|
definitions |
map[string]ModelConfig | empty | Shared named model definitions. Each entry binds a provider to a specific model ID and sets request-level parameters. |
discovery_enabled |
bool | true |
When true, discover available models from configured providers. When false, skip provider enumeration and network refresh; the chooser shows configured entries only. |
profiles |
map[string]ModelProfile | see below | Named model-assignment profiles. profiles.default is required and must define default_model; named profiles may override any role partially. |
Each profile supports these fields:
| Field | Type | Description |
|---|---|---|
default_model |
string | Raw provider/model-id reference used by default, or an optional configured alias. It is also the fallback for roles without a more specific assignment. Required in profiles.default. |
advisor |
string | Model reference used for advisor calls when advisor.enabled is true. |
sub_agents |
map[string]string | Per-agent-type model references, keyed by agent type. |
oneshot |
map[string]string | Per-phase model references, keyed by plan, implement, and review. Missing phases fall back to the selected profile's default_model. |
workflow_handoff |
map[string]string | Persistent handoff model references, keyed by implement, review, and build. Missing destinations use the selected profile's default_model (profile default). |
providers:
local:
type: ollama
base_url: http://localhost:11434/v1
cloud:
type: anthropic
api_key_env: ANTHROPIC_API_KEY
models:
discovery_enabled: true
profiles:
default:
default_model: local/qwen2.5-coder:14b
advisor: cloud/<advisor-model-id>
sub_agents:
code: cloud/<code-model-id>
evaluate: cloud/<evaluate-model-id>
sanity_check: local/<sanity-check-model-id>
oneshot:
plan: local/<planner-model-id>
implement: cloud/<implementer-model-id>
review: cloud/<reviewer-model-id>
workflow_handoff:
implement: cloud/<implementer-model-id>
review: cloud/<reviewer-model-id>
fast:
default_model: local/qwen2.5-coder:14b
sub_agents:
code: local/<code-model-id>workflow_handoff supports destination keys implement, review, and build. If a
destination has no entry, handoff uses the selected profile's default_model (profile default). The
interactive handoff picker can still override the pending model for one
handoff without changing configuration.
Every model selection accepts a raw provider/model-id reference. A configured alias is an optional alternate when it provides a shorter or stable name or persists ModelConfig settings:
alias | provider/model-id
This applies to profile assignments, the --model flag, STEINER_MODEL, and the
/model command. For example, openrouter/openai/gpt-4o selects model ID
openai/gpt-4o from the configured openrouter provider.
At startup, configuration is resolved in this order:
- Config files are merged, then the selected profile is resolved.
--profile <name>selects a named profile; with no flag,defaultis selected. -
STEINER_MODELoverrides the active orchestrator model. -
--modeloverridesSTEINER_MODELand the profile's active orchestrator model.
The env and CLI model overrides affect only the active orchestrator selection.
They do not replace the selected profile's role assignments or its
default_model fallback, so advisor, sub-agent, oneshot, and workflow-handoff
roles continue to resolve from the profile.
In interactive mode, /model changes only the active orchestrator selection.
/profile <name> changes future advisor, sub-agent, oneshot, and workflow-handoff
assignments and the selected default_model fallback, but preserves the current
active orchestrator model, any current /model override, conversation, and
prompt-cache identity. /profile requires a name; it does not open a picker.
An unknown or invalid profile reports an error and leaves the current selection
unchanged.
Implementation details about role resolution, cache identity, and provider transport selection are in Configuration internals.
An exact configured alias takes precedence over provider-prefix parsing. Otherwise, steiner uses the longest matching configured provider prefix, so provider and model IDs may contain additional slashes.
For recommended model tiers per sub-agent type, see Recommended model tiers in the delegation documentation.
Each key under models.definitions is an arbitrary model alias name. Each
entry binds a provider to a specific model ID and sets request-level
parameters.
models:
definitions:
<name>:
provider: <provider_name>
id: <model_id>
...For each model fact, explicit model configuration wins. Otherwise the catalog is checked before models.dev. For generic providers, models.dev uses the provider's model alias when available and then merges matching provider entries; Ollama probes the live server for context before using merged models.dev data. Codex always uses its fixed OAuth Responses transport, regardless of configured URL. Unresolved limits use the conservative fallback values.
| Field | Type | Default | Description |
|---|---|---|---|
provider |
string | "local" |
Name of the provider (key in providers) to use for this model. |
id |
string | "qwen3-35b-a3b" |
The model identifier as expected by the provider API (e.g. claude-sonnet-4-5, gpt-4o, llama3:8b). |
params |
map[string]any | — | Standard request parameters sent in the request body. Common keys: temperature, top_p, top_k. |
extra_params |
map[string]any | — | Additional provider-specific parameters merged into the request body. Use for fields not covered by params. |
prompt_suffix |
string | — | Text appended to every user message before sending to the model. Useful for model-specific control tokens (e.g. `< |
retry |
RetryConfig | see below | Retry policy for failed or rate-limited requests. |
prompts |
ModelPrompts | — | Per-model system and compaction prompt overrides. |
advanced |
AdvancedConfig | see below | Token limit and inference-level settings. |
vision |
bool or null | null (assumed capable) | When set to false, image attachments are stripped from requests to this model and a diagnostic warning is emitted. When null or unset, vision capability is assumed. Set to false for older models that reject image content. |
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Whether to retry on transient errors. |
max_attempts |
int | 3 |
Maximum number of total attempts (initial + retries). |
initial_backoff |
duration string | "250ms" |
Wait time before the first retry. |
max_backoff |
duration string | "5s" |
Upper cap on exponential backoff wait time. |
retry_after_max |
duration string | "30s" |
Maximum time to honour a Retry-After header before giving up. |
Provider usage and quota limits are never retried regardless of retry settings. These include: Codex usage_limit_reached and usage_not_included, OpenAI insufficient_quota and billing codes (credit_balance_exhausted, organization_spend_limit_exceeded, project_spend_limit_exceeded, organization_usage_limit_exceeded), and LiteLLM budget exhaustion. The run stops with reason usage_limit, and the error message names the provider-qualified model (e.g., codex/luna/high) and, when the provider reports a reset time, the reset time in local time plus relative duration. Other 429 responses (e.g., rate limits, slow-down) keep the normal retry policy.
| Field | Type | Default | Description |
|---|---|---|---|
system |
string | — | Overrides the embedded default system prompt for this model. |
system_suffix |
string | — | Text appended after all default preamble content (including cave_human instructions). Enables per-model system prompt steering without replacing the default preamble. |
compaction |
string | — | Overrides the embedded compaction (context summarisation) prompt for this model. |
| Field | Type | Default | Description |
|---|---|---|---|
limits |
AdvancedLimitsConfig | see below | Token budget settings for this model. |
reasoning_echo_back |
*bool | — | When set, controls whether reasoning tokens are echoed back in the response. Provider-dependent. |
transport |
string | "auto" |
Transport override for request formatting. Supported values: auto, openai_compat, anthropic. auto uses models.dev metadata when available and otherwise keeps the configured provider type. |
reasoning |
ReasoningConfig | see below | Reasoning effort configuration for this model. Only meaningful for providers that support configurable reasoning effort (currently OpenAI and Codex); unsupported providers ignore it. |
codex |
ModelCodexConfig | see below | Codex-only model metadata selection. use_max_context_window: true selects catalog max_context_window when positive; otherwise catalog context_window is used. |
When transport is auto (the default), request formatting uses the configured provider unless a supported model transport is available. An explicit openai_compat or anthropic override takes precedence. See Configuration internals for models.dev metadata and provider-selection mechanics.
| Field | Type | Default | Description |
|---|---|---|---|
effort |
string | — | Reasoning effort to request for this model. This is a provider/model-native string, not a Steiner-owned enum — the accepted values depend entirely on the provider and model. For OpenAI/Codex models the accepted values are model-specific (e.g. none, low, medium, high, xhigh for gpt-5.4 variants) and are discovered from the models.dev metadata cache when supported_efforts is not set. When unset, no effort is applied and no request-level reasoning field is sent at all, so the provider's own default behavior applies. |
supported_efforts |
[]string | — | The set of provider/model-native effort values this model accepts, used to validate effort and to populate the /model reasoning-effort picker in the TUI. When unset, Steiner discovers per-model efforts from the models.dev metadata cache (the same source used for context limits), falling back to a conservative built-in list (minimal, low, medium, high) only for recognized OpenAI/Codex model families (model IDs containing gpt-5, o1, o3, o4, or codex) not found in models.dev. For all other providers and model families, unset means Steiner has no known valid efforts and the /model reasoning-effort picker offers no options for that model. |
Wire shape differs by transport: Codex (OpenAI Responses API) sends the resolved effort as a reasoning: {"effort": "...", "summary": "auto"} object — summary: "auto" is always included alongside a resolved effort so the API returns reasoning summaries, which Steiner parses and replays as thinking blocks; OpenAI-compatible chat-completions providers (type: openai) send it as a flat reasoning_effort string field. Both are omitted entirely when no effort is resolved.
Example (OpenAI/Codex-style values — other providers may use a different vocabulary, or may not support configurable reasoning effort at all):
providers:
codex:
type: codex
models:
definitions:
codex-high:
provider: codex
id: <codex-model-id>
advanced:
reasoning:
effort: high
supported_efforts: [minimal, low, medium, high]
profiles:
default:
default_model: codex-highcodex-high is retained as an alias because it persists the reasoning configuration; a raw codex/<codex-model-id> reference cannot do that.
Reasoning effort can also be changed at runtime for the current session via the /model command in the interactive TUI (select a model, then a reasoning effort from supported_efforts, or "provider default" to omit the field). Runtime /model reasoning selections are session-only and never write back to the config file. Use steiner model inspect <alias> to see the resolved supported_efforts, provider_default_effort, configured_effort, and effective_effort for a model, and its facts: block for the per-fact source and confidence behind each resolved value.
| Field | Type | Default | Description |
|---|---|---|---|
use_max_context_window |
bool | false |
For Codex models, opt into the catalog's positive max_context_window; without this flag, or when max is unavailable, use catalog context_window. |
Context limit precedence is explicit numeric advanced.limits.context_window, selected Codex catalog context, existing discovery/models.dev metadata, then the built-in fallback. The flag is valid only for Codex model definitions.
| Field | Type | Default | Description |
|---|---|---|---|
context_window |
int | 32768 |
Maximum context window size in tokens. Used by the prompt assembler to budget context. |
max_output_tokens |
int | 8192 |
Maximum tokens the model may generate per response. |
Controls execution mode configuration.
| Field | Type | Default | Description |
|---|---|---|---|
default |
string | "build" |
Execution mode for interactive sessions. Allowed values: "plan" or "build". plan restricts project edits to .steiner/plans/ and .steiner/security/; build is normal workspace editing. |
modes:
default: buildControls bubblewrap sandbox behavior.
The runtime sandbox status (active, unavailable, or bypassed) is computed at startup
and surfaced only in the TUI (sidebar/badge) and startup warnings; it is not part of steiner config output and is not user-configurable.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Enable bubblewrap sandboxing. --unsafe applies a CLI override that forces this to false at load time. |
warning_on_unsupported_platform |
bool | true |
When enabled, shows a warning in the TUI when sandbox is unavailable or bypassed. |
env_passthrough |
[]string | [] |
Additional host environment variable names (beyond the built-in allowlist) passed through to sandboxed processes. Entries may end in * to match by prefix (e.g. MYAPP_*); no other wildcard forms are supported. |
env_passthrough_all |
bool | false |
When true, disables environment filtering entirely and passes the full host environment through, including credentials. |
bind_host_cache |
bool | false |
When true, binds the real ~/.cache read-write into the sandbox. By default the cache location is backed by a sandbox-private directory (.steiner/home/cache/), so sandboxed tools cannot poison host caches (go-build, pip, uv). |
host_mounts |
[]object | [] |
Additional host paths to bind-mount into the sandbox. Use mode: rw to grant writable access to paths outside the workspace (all host paths are already readable through the root bind). |
Each entry has:
| Field | Type | Description |
|---|---|---|
path |
string | Host path to mount (supports ~ expansion). |
mode |
string | Mount mode: rw for writable access (host is already read-only by default) or ro. |
sandbox:
enabled: true # default; --unsafe overrides this to false at runtime
warning_on_unsupported_platform: true # default; warns when sandbox is unavailable/bypassed
env_passthrough: [] # default; extra allowlisted env var names, "*" suffix allowed for prefix match
env_passthrough_all: false # default; true disables env filtering entirely
bind_host_cache: false # default; true binds the real ~/.cache read-write
host_mounts:
- path: ~/.kube
mode: rw
- path: /opt/tools
mode: rwOpt-in permissions for additional capabilities.
| Field | Type | Default | Description |
|---|---|---|---|
docker |
bool | false |
Gates Docker socket access inside the sandbox. false (default) masks any reachable Docker socket and unsets DOCKER_HOST, denying sandboxed tools access to the host daemon. true leaves the socket reachable. See sandboxing.md. |
permissions:
docker: falseRuntime limits for turns, tokens, and tool execution.
| Field | Type | Default | Description |
|---|---|---|---|
max_turns |
int | 50 |
Maximum agent loop turns before the run is stopped. |
max_tokens |
int | 500000 |
Maximum total tokens (input + output) consumed before the run is stopped. |
tool_timeout_default |
duration string | "30s" |
Default timeout applied to any tool not listed in tool_timeouts. |
tool_timeouts |
map[string]duration string | see below | Per-tool timeout overrides. |
tool_output_max_bytes |
int | 65536 |
Maximum bytes of output captured from a single tool call. Output is truncated to this limit. Applies to both the parent run and each child sub-agent's own tool executor. |
max_parallel_tools |
int | 4 |
Maximum number of ordinary parallel-safe tool calls (read, glob, grep, ls, fetch_url, web_search) executed concurrently within a single turn. Must be at least 1; 1 forces serial execution for these tools. Distinct from sub_agent.max_parallel, which bounds delegation-tool concurrency independently. Applies to both the parent run and each child sub-agent's own turns. |
Breaking change:
max_parallel_tools: 0previously meant unbounded concurrency; it is now rejected at startup. Set it to1for the equivalent serial behaviour, or a positive number for bounded concurrency.sub_agent.max_parallel: 0previously had no runtime effect at all (the field was dead); it is likewise now rejected — set it to1or higher.
Default tool_timeouts:
| Tool | Timeout |
|---|---|
bash |
120s |
read |
5s |
grep |
30s |
ls |
5s |
limits:
max_turns: 50
max_tokens: 500000
tool_timeout_default: 30s
tool_timeouts:
bash: 120s
read: 5s
grep: 30s
ls: 5s
tool_output_max_bytes: 65536
max_parallel_tools: 4tool_timeout_default and tool_timeouts also apply to MCP tools: every MCP call is
bounded by tool_timeout_default (the 30s default), unless the tool's full registered
name has an entry in tool_timeouts. That key is the registry name shown by
steiner tools — mcp__<server>__<tool>, or the hashed form when the name needed
sanitisation or exceeded the length limit.
Controls delegated child-agent execution. For details on what sub-agents can do and tool allowlists for each specialised agent type, see Sub-agent Delegation.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Master switch. Set to false to remove all delegation tools from the model. |
orchestration_level |
string | standard |
How strongly the system preamble steers the orchestrator to delegate. standard sends the full delegation guidance; low omits the "Your role" and "Delegation vs direct work" sections and leaves delegation to the model's judgement. Ignored when enabled is false. Switchable at runtime with /orchestration (not persisted). |
max_turns |
int | 30 |
Maximum turns allowed for each child agent run. A floor of 15 turns is enforced internally. |
max_tokens |
int | 100000 |
Maximum tokens a child agent may consume. |
max_parallel |
int | 3 |
Maximum number of delegation-tool calls (specialized sub-agent spawns, follow_up) executed concurrently within a single parent turn. Must be at least 1; 1 forces serial execution for delegation calls. Independent of limits.max_parallel_tools, which bounds ordinary tool-call concurrency — a mixed batch never lets the two compete for the same semaphore slots. |
max_follow_ups |
int | 5 |
Maximum number of follow-up resumes allowed per delegated child agent. When a child reaches this ceiling, new follow-ups are refused and must be redelegated fresh. With max_turns: 30 default, worst-case accumulated turn budget is 30 + 5×30 = 180 turns for a single child across its full resume lifetime. |
Each specialised agent type (explore, research, code, evaluate,
sanity_check, review, vision) has its own hardcoded tool allowlist; there is no
user-configurable tool allowlist field.
Per-agent-type model overrides live in the selected profile's sub_agents map
(see the models block), keyed by agent type (e.g. code,
evaluate, sanity_check, explore, vision). If an agent type has no entry, the
sub-agent uses the profile's default assignment.
sub_agent:
enabled: true
orchestration_level: standard
max_parallel: 3
max_turns: 30
max_tokens: 100000
max_follow_ups: 5
providers:
openai:
type: openai
anthropic:
type: anthropic
models:
profiles:
default:
default_model: openai/<orchestrator-model-id>
sub_agents:
code: openai/<code-model-id>
research: anthropic/<research-model-id>
vision: anthropic/<vision-model-id> # required to enable the vision toolThe vision agent type requires a vision-capable model. When the selected profile's
sub_agents.vision is empty or unset, the vision tool is not registered.
A map of externally configured tools registered alongside the built-in tools.
Each key becomes the tool name the model uses.
Tool names that collide with a built-in (read, glob, grep, ls, bash, display_file, mutate, fetch_url, workflow_handoff) are rejected at config load.
tools:
<tool_name>:
exec: <binary_path>
...| Field | Type | Default | Description |
|---|---|---|---|
exec |
string | — | Path to the executable to run when this tool is called. |
subcommand |
string | — | Subcommand or argument passed as the first positional argument to exec. |
description |
string | — | Human-readable description shown to the model in the tool schema. |
parameters |
map[string]any | — | JSON Schema fragment describing the tool's input parameters. |
timeout |
duration string | — | Per-tool timeout. Overrides limits.tool_timeout_default for this tool. |
steiner invokes exec (with subcommand as its first argument, if set) as a
subprocess for each call. The contract is fixed and not configurable per tool:
- stdin: the model's validated input arguments, as a single JSON object, written once and then closed.
-
stdout: a single JSON object on completion:
- Success:
{"ok": true, "result": <any JSON value>}.resultis what the model sees, verbatim. - Failure:
{"ok": false, "error": {"kind": "<short slug>", "message": "<human-readable>", "details": <optional any>}}.kindandmessageare model-visible;detailsis host-side only (shown in logs/TUI, not sent to the model). - Optionally, alongside
result:"model_result": <any JSON value>. When present,model_result— notresult— is what the model sees;resultis then retained host-side only (session history, TUI). Use this when your tool's full result is large, includes raw logs/diagnostics, or otherwise carries more than the model needs to act on. Omit it to keep sendingresultto the model directly (default, unchanged behavior).
- Success:
-
exit code: a nonzero exit code is always treated as a failure,
regardless of what stdout contains — even a well-formed
{"ok": true, ...}response is discarded and reported to the model as an error if the process exits nonzero. Exit0on every successful call; use theok: falseenvelope, not a nonzero exit code, to signal a specific failure. - stdout/stderr raw bytes are captured host-side (bounded by
limits.tool_output_max_bytes) and are never sent to the model on success; they surface only inside a failure'sdetailswhen stdout wasn't valid JSON or the process itself failed to run.
tools:
fmt:
exec: gofmt
subcommand: -w
description: Format Go source files in place.
timeout: 10sA minimal conforming tool, reading arguments from stdin and writing the envelope to stdout:
// stdin
{"path": "main.go"}
// stdout (success, no model_result — result goes straight to the model)
{"ok": true, "result": {"formatted": true}}
// stdout (success, with model_result — result is host-only)
{"ok": true, "result": {"formatted": true, "diff": "...200 lines..."}, "model_result": {"formatted": true}}
// stdout (failure)
{"ok": false, "error": {"kind": "invalid_input", "message": "path not found"}}Configures Model Context Protocol (MCP) servers. MCP is enabled by default.
Individual servers must still be enabled explicitly via servers.<name>.enabled. See
MCP for the TUI surfaces and approval behavior.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Master switch for the MCP client. |
servers |
map | — | Per-server configuration under mcp.servers.<name>. |
Each server entry (MCPServerConfig) supports:
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Whether this server is started. |
transport |
string | "stdio" |
Transport used to reach the server. One of stdio (starts a subprocess) or http (connects via HTTP). |
command |
string | — | Executable that starts the server. Required for stdio transport; must be empty for http. |
args |
[]string | — | Arguments passed to the command. Used only with stdio transport; must be empty for http. |
env |
map[string]string | — | Extra environment variables for the server process. Used only with stdio transport; must be empty for http. |
url |
string | — | HTTP endpoint (http or https). Required for http transport; must be empty for stdio. |
headers |
map[string]string | — | HTTP headers sent with every request. Used only with http transport; must be empty for stdio. Header names cannot collide with SDK-reserved names (case-insensitive): Content-Type, Accept, Mcp-Protocol-Version, Mcp-Session-Id, Last-Event-Id, Mcp-Method, Mcp-Name, or names with the Mcp-Param- prefix. Authorization is allowed for static bearer tokens sourced from ${VAR} (see environment variable expansion). |
approval |
string | "ask" |
Approval mode for the server's tools. One of ask (prompt per tool call), allow (run without prompting in build mode, downgraded to ask in plan mode), or deny (register no tools). |
trust_annotations |
bool | false |
When true, tools advertised with readOnlyHint: true skip approval; destructiveHint and openWorldHint tools still prompt. |
connect_timeout |
duration | "15s" |
Maximum time to wait for the MCP connection to be established. Absent or 0 falls back to 15s (mirrors crush's default, range 5-30s); negative values are rejected. |
allowed_tools |
[]string | — | Optional allowlist of MCP-native tool names for this server. Only listed names are registered; registered names like mcp__<server>__<tool> are not accepted as entries. Missing means no allowlist restriction; an explicit empty list (allowed_tools: []) registers no tools (denies all). |
blocked_tools |
[]string | — | Optional denylist of MCP-native tool names. Applied after allowed_tools: a tool that survives the allowlist is removed if its name appears here. |
sub_agents |
[]string | — | Agent types allowed to use this server's tools. Defaults to closed: missing or [] grants no MCP tools to any child. Valid values: explore, research, code, evaluate, sanity_check, review, vision. |
mcp:
enabled: true
servers:
my-stdio-server:
enabled: true
command: "npx"
args: ["-y", "@some/mcp-server"]
approval: ask
trust_annotations: false
my-http-server:
enabled: true
transport: http
url: "http://localhost:3000/mcp"
headers:
Authorization: "Bearer ${MCP_AUTH_TOKEN}"
approval: askWhen using http transport with an Authorization header, use the strict env expansion syntax (e.g. ${VAR}) to inject environment variables. See the environment variable expansion section for details.
See MCP for user-facing behavior and approval rules.
Configures optional language server connections for code intelligence (definitions, references, diagnostics). LSP is disabled by default. When enabled, servers must be configured explicitly under servers.<name>. See LSP for lifecycle, caching, and graceful degradation details.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Master switch. Set to true to enable LSP code intelligence tools. |
idle_timeout |
duration | 5m |
Server idle timeout before shutdown. |
request_timeout |
duration | 10s |
Per-request timeout; hung servers and still-indexing servers must answer within this window. |
ready_timeout |
duration | 30s |
Timeout for server readiness (initialization and first workspace load). If exceeded, requests proceed anyway with incomplete flag. |
ready_grace_period |
duration | 2s |
Grace period for servers that never send progress events; without this, silent servers look failed. |
diagnostics_window |
duration | 2s |
Time window to collect published diagnostics after file open; unconditional latency on every diagnostics query. |
max_results |
int | 200 |
Maximum results returned per navigation query (definitions, references). |
cache_dir |
string | — | Optional persistent cache directory for server state. When unset, defaults to system user cache dir. See LSP for cache layout and cleanup. |
servers |
map[string]LSPServerConfig | — | Per-server configuration under lsp.servers.<name>. |
Each server entry (LSPServerConfig) supports:
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Whether this server is started on first use. |
command |
string | — | Executable that starts the server. Required. |
args |
[]string | — | Arguments passed to the server executable. |
env |
map[string]string | — | Extra environment variables for the server process (inherit PATH, HOME, etc. from host). Consider setting GOCACHE, GOPRIVATE, or language-specific variables. |
file_extensions |
[]string | — | File extensions this server handles (e.g. .go, .ts, .py). Must include the dot. Required. |
root_markers |
[]string | — | Files or directories (e.g. go.mod, package.json) that identify workspace root. Required. |
initialization_options |
map[string]any | — | Server-specific initialization options passed during handshake. |
lsp:
enabled: true
cache_dir: ~/.cache/steiner/lsp
servers:
gopls:
enabled: true
command: gopls
file_extensions: [".go"]
root_markers: ["go.mod", ".git"]
initialization_options:
analyses:
fillstruct: true
tsserver:
enabled: true
command: typescript-language-server
args: ["--stdio"]
file_extensions: [".ts", ".tsx", ".js"]
root_markers: ["package.json", "tsconfig.json"]A language server is never installed by steiner — users must install servers separately (e.g. go install github.com/golang/tools/gopls@latest). See LSP for copy-paste examples for gopls, typescript-language-server, pyright, and rust-analyzer.
Configures extra files and a byte budget for project-level context injected into the system prompt.
| Field | Type | Default | Description |
|---|---|---|---|
max_bytes |
int | 8000 |
Byte budget for extra project context files. The prompt assembler will truncate or skip files to stay within this budget. |
max_tokens |
int | — |
Deprecated alias for max_bytes. When max_bytes is unset, converted to bytes as max_tokens * 4 at load time; max_bytes wins when both are set. When used, a deprecation warning is shown in the interactive TUI at startup and emitted as a config_warning event on the --exec stream. |
extra_files |
[]string | — | Additional files to include in project context. Paths are relative to the project root. |
ignore_files |
[]string | — | Files to exclude from extra_files. There is no automatic project-context discovery. |
project_context:
max_bytes: 8000
extra_files:
- docs/ARCHITECTURE.md
- .steiner/skills/project.md
ignore_files:
- vendor/
- generated/Constrains filesystem access for tools that read or write files.
| Field | Type | Default | Description |
|---|---|---|---|
project_root_only |
bool | true |
When true, tools are restricted to paths within the detected project root. |
writable_paths |
[]string | [] |
Additional paths outside the project root that mutation tools may write to. |
blocked_paths |
[]string | [] |
Paths that are always denied, even if they fall within the project root. |
exclude_paths |
[]string | — | Paths excluded from directory listings and glob results. |
exclude_patterns |
[]string | — | Glob patterns excluded from directory listings and glob results. |
Note: the TUI file picker shows .steiner/ contents except .steiner/tmp and .steiner/worktrees, which are always hidden to keep the picker fast. The same exclusion rules still apply to glob and grep tools.
This block also applies to each child sub-agent's own tool executor, not just the parent's.
paths:
project_root_only: true
writable_paths:
- /tmp/steiner-scratch
blocked_paths:
- .env
- secrets/
exclude_paths:
- vendor/
- node_modules/
exclude_patterns:
- "**/*.secret"
- "**/.env*"Controls diagnostic log output.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Whether file logging is active. |
level |
string | "info" |
Minimum log level. One of debug, info, warn, error. |
file |
string | "~/.local/share/steiner/steiner.log" |
Path to the log file. Tilde expansion is supported. Treat as sensitive — it may capture prompts and tool output. The session log is JSONL (one JSON object per line), appended across runs, capped in size and rotated (<file>.1, <file>.2, ...). Its first line each run is a log_started record carrying run_id, build_sha, dirty and the steiner version. |
thinking_chunk |
bool | false |
When true, reasoning/thinking tokens from the model are included in the log. |
assistant_chunk |
bool | false |
When true, streamed assistant content chunks are included in the log. Off by default because each chunk duplicates content already captured in the completed assistant_message record. |
compaction_log_file |
string | — | Separate log file for context compaction events. Useful for debugging compaction behaviour. |
logging:
enabled: true
level: debug
file: ~/.local/share/steiner/steiner.log
thinking_chunk: false
assistant_chunk: false
compaction_log_file: ~/.local/share/steiner/compaction.logStructured instrumentation capture. Disabled by default, and gated entirely
independently of logging — the diagnostics directory is never derived from
logging.file, so a week-long measurement never also captures prompts.
Records are written as JSONL, one file per stream (cache.jsonl,
provider.jsonl, tool.jsonl). Every record carries run_id, build_sha,
and dirty, so a before/after comparison can be scoped to a build rather than
to a time window. See Configuration internals
for file permissions, rotation, retention, and writer details.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Master switch. When false nothing is constructed and no directory is created. |
dir |
string |
$XDG_STATE_HOME/steiner/diagnostics, else ~/.local/state/steiner/diagnostics
|
Directory holding the per-stream files. Tilde expansion is supported. Must not resolve inside the project root — diagnostics are user state, not repo content. |
retention_days |
int | 30 |
Records older than this are dropped when the writer opens. Must be greater than zero when enabled. |
streams.cache |
bool | false |
Prompt-cache observations. |
streams.provider |
bool | false |
One record per model call, whatever the outcome. Subsumes the stream-error log, which otherwise writes next to logging.file. |
streams.tool |
bool | false |
Tool execution and delegation traces. Subsumes the delegation trace log, which otherwise writes next to logging.file. |
capture_bodies |
bool | false |
Allow full message, tool and block content instead of bounded scalar fields. Also unbounds the session log's api_request records, and makes failed mutate replaces record the attempted old_string and a bounded file region, at most 3 per call and 4 KiB each. Expensive, and captures prompts. |
diagnostics:
enabled: true
dir: ~/.local/state/steiner/diagnostics
retention_days: 30
streams:
cache: true
provider: true
tool: false
capture_bodies: falseWith capture_bodies, delegation traces are routed into the tool stream in
full. The tool stream rotates at 20 MB with 3 generations kept, so a long
collection window overwrites its oldest records; snapshot the diagnostics
directory when the window matters.
See Configuration internals for analysis modes and script checks.
Baseline context management settings.
| Field | Type | Default | Description |
|---|---|---|---|
read_annotations |
bool | true |
When true, file reads are annotated in the conversation with metadata (path, line range) to help the model track context provenance. Disable if annotations add unwanted noise for your model. |
context_management:
read_annotations: trueThis block also applies to each child sub-agent's own context manager, not just the parent's.
TUI preferences are stored separately from the main config in ~/.config/steiner/prefs.yaml. Accent, thinking, and sidebar position are updated by slash commands in the interactive TUI. Edit background fields directly in this preferences file.
| Field | Type | Default | Description |
|---|---|---|---|
accent |
string | amber |
Accent colour preset for the TUI. Valid values: amber, coral, rose, magenta, gold, violet, indigo, blue, cyan, teal, green, mint, lime, red, pink, sky, lavender, terracotta, yellow, purple, or random. random selects a different concrete preset on each startup. |
show_thinking |
bool | true |
When true, model reasoning/thinking tokens are rendered in the TUI transcript. |
sidebar_position |
string | left |
Position of the sidebar panel: left or right. |
sidebar_bg |
string | #050505 |
Sidebar seamless background. Quote YAML #RRGGBB; empty or missing uses default. |
content_bg |
string | #000000 |
Main content seamless background. Quote YAML #RRGGBB; empty or missing uses default. |
Use /accent in the TUI to open a colour picker (all 20 presets with colour swatches, listed in chromatic order), or /accent <preset> to set directly. Use /thinking to toggle thinking display. Use /sidebar to move the sidebar. Invalid non-empty background colors are rejected.
Configures web search integration.
| Field | Type | Default | Description |
|---|---|---|---|
backend |
string | — | Search backend to use. One of searxng, google, kagi, brave. |
searxng_url |
string | — | Base URL of the SearXNG instance. Required when backend: searxng. |
google_cx |
string | — | Google Custom Search Engine ID. Required when backend: google. |
google_api_key |
string | — | Google API key for Custom Search. Required when backend: google. Prefer an environment variable via STEINER_ prefix. |
kagi_api_key |
string | — | Kagi API key. Required when backend: kagi. |
brave_api_key |
string | — | Brave Search API key. Required when backend: brave. |
Per-backend required fields:
| Backend | Required fields |
|---|---|
searxng |
searxng_url |
google |
google_cx, google_api_key
|
kagi |
kagi_api_key |
brave |
brave_api_key |
search:
backend: searxng
searxng_url: http://localhost:8080providers:
local:
type: openai_compat
base_url: http://localhost:11434/v1
models:
profiles:
default:
default_model: local/qwen3:14bUse http://127.0.0.1:1234/v1 as base_url for LM Studio. For Ollama, use
type: ollama with base_url: http://localhost:11434/v1.
providers:
anthropic:
type: anthropic
api_key_env: ANTHROPIC_API_KEY
models:
profiles:
default:
default_model: anthropic/<model-id>For OpenAI, replace type: anthropic with type: openai and set
api_key_env: OPENAI_API_KEY.
providers:
local:
type: ollama
base_url: http://localhost:11434/v1
router:
type: openrouter
api_key_env: OPENROUTER_API_KEY
models:
profiles:
default:
default_model: local/qwen3:14bSwitch models at runtime with --model router/openai/gpt-4o without changing config. Raw references are enough for ordinary model selection; define an alias only when you need a stable name or persistent model settings.
providers:
local:
type: openai_compat
base_url: http://localhost:11434/v1
timeout: 60s
models:
definitions:
local:
provider: local
id: qwen3-35b-a3b
advanced:
limits:
context_window: 65536
max_output_tokens: 16384
retry:
enabled: true
max_attempts: 5
initial_backoff: 500ms
max_backoff: 10s
profiles:
default:
default_model: local
limits:
max_turns: 80
max_tokens: 800000
tool_timeout_default: 45s
tool_timeouts:
bash: 300s
read: 10s
grep: 60s
ls: 10s
tool_output_max_bytes: 131072local is retained as an alias here because the example demonstrates custom context, output, and retry settings; ordinary selection should use local/<model-id>.
The aliases in this example are intentional: each definition demonstrates persistent request, prompt, retry, limit, or other ModelConfig settings. Use raw references when those settings are not needed.
tui:
fps: 60
providers:
local:
type: ollama
base_url: http://localhost:11434/v1
timeout: 45s
cloud:
type: anthropic
api_key_env: ANTHROPIC_API_KEY
timeout: 120s
router:
type: openrouter
api_key_env: OPENROUTER_API_KEY
headers:
X-Title: steiner
models:
definitions:
local-fast:
provider: local
id: qwen3:14b
params:
temperature: 0.2
top_p: 0.95
retry:
enabled: true
max_attempts: 3
initial_backoff: 250ms
max_backoff: 5s
retry_after_max: 30s
advanced:
limits:
context_window: 32768
max_output_tokens: 8192
reasoning_echo_back: false
sonnet:
provider: cloud
id: claude-sonnet-4-5
extra_params:
metadata:
user_id: dev
advanced:
limits:
context_window: 200000
max_output_tokens: 64000
mini:
provider: router
id: openai/gpt-4o-mini
prompt_suffix: " Answer concisely."
prompts:
system: "You are a concise coding assistant."
system_suffix: "Always respond in structured JSON when possible."
profiles:
default:
default_model: local/<fast-model-id>
advisor: cloud/<advisor-model-id>
sub_agents:
code: cloud/<code-model-id>
evaluate: cloud/<evaluate-model-id>
sanity_check: router/<sanity-check-model-id>
review: cloud/<review-model-id>
oneshot:
plan: local/<planner-model-id>
implement: cloud/<implementer-model-id>
review: cloud/<reviewer-model-id>
workflow_handoff:
implement: cloud/<implementer-model-id>
review: cloud/<reviewer-model-id>
limits:
max_turns: 60
max_tokens: 600000
tool_timeout_default: 30s
tool_timeouts:
bash: 180s
read: 5s
grep: 30s
ls: 5s
tool_output_max_bytes: 65536
sandbox:
enabled: true
permissions:
docker: false
sub_agent:
enabled: true
max_turns: 25
max_tokens: 80000
advisor:
enabled: true
max_uses_per_run: 3
oneshot:
auto_pr: false
tools:
fmt:
exec: gofmt
subcommand: -w
description: Format Go source files in place.
timeout: 10s
lint:
exec: golangci-lint
subcommand: run
description: Run golangci-lint on the project.
timeout: 60s
parameters:
path:
type: string
description: Directory to lint. Defaults to ./...
project_context:
max_bytes: 8000
extra_files:
- docs/ARCHITECTURE.md
- .steiner/skills/project.md
ignore_files:
- vendor/
- testdata/
paths:
project_root_only: true
writable_paths:
- /tmp/steiner-scratch
blocked_paths:
- .env
- .env.local
exclude_paths:
- vendor/
- node_modules/
exclude_patterns:
- "**/*.secret"
sandbox:
enabled: true
host_mounts:
- path: ~/.kube
mode: rw
- path: /opt/tools
mode: rw
permissions:
docker: false
logging:
enabled: true
level: info
file: ~/.local/share/steiner/steiner.log
thinking_chunk: false
assistant_chunk: false
compaction_log_file: ~/.local/share/steiner/compaction.log
context_management:
read_annotations: true
search:
backend: searxng
searxng_url: http://localhost:8080
cave_human: falseGet started
Using Steiner
- How to work with steiner
- Interactive mode
- CLI reference
- Skills
- Sub-agent delegation
- Oneshot
- Context management
- Advisor
- Security audit
Tools and integrations
Safety
Usage and cost
Reference
Internals and maintenance