Repository navigation
user provider and model setup
A provider is a service that runs models for you, together with the details Steiner needs to reach it: what kind of service it is, where it lives, and how to sign in. OpenAI's API, an OpenRouter account, and the Ollama server on your laptop are all providers. A model is one specific LLM that a provider serves, such as gpt-4o or qwen2.5-coder:14b. You set up providers once, then choose models from them by name.
For example, in .steiner/config.yaml:
# Provider: how to reach the service
providers:
openai:
type: openai
api_key_env: OPENAI_API_KEY
# Model: which model the default profile uses
models:
profiles:
default:
default_model: openai/<model-id>Set OPENAI_API_KEY in your environment and replace <model-id> with a model available to you. For all fields, see the providers block and the models block.
The key under providers is a name you choose and use in model references. It is not the provider type. The type selects the kind of service, its API protocol, authentication, and defaults. Connection details can include base_url, credentials, and optional headers or timeout.
You can declare several providers of the same type, such as two openai_compat gateways, or openai-work and openai-personal with different keys.
| Type | Connects to | base_url |
Authentication |
|---|---|---|---|
| Hosted APIs | |||
openai |
OpenAI API | Default https://api.openai.com/v1
|
Required API key |
anthropic |
Anthropic API | Default https://api.anthropic.com/v1
|
Required API key |
openrouter |
OpenRouter gateway | Default https://openrouter.ai/api/v1
|
Required API key |
opencode_go |
OpenCode Go gateway | Default https://opencode.ai/zen/go/v1
|
Required API key |
opencode_zen |
OpenCode Zen gateway | Default https://opencode.ai/zen/v1
|
Required API key |
| Subscriptions | |||
codex |
ChatGPT backend | Optional, ignored for ChatGPT sign-in | OAuth login |
claude_subscription |
Signed-in local claude CLI |
Rejected | Claude Code login |
| Self-hosted and gateways | |||
ollama |
Ollama's OpenAI-compatible API | Required | Optional API key |
lmstudio |
LM Studio's local server | Required | Optional API key |
litellm |
LiteLLM gateway | Required | Optional API key |
openai_compat |
OpenAI chat-completions compatible server | Required | Optional API key |
gemini passes validation but cannot run, so don't use it.
-
openai,openrouter,ollama,lmstudio,litellm, andopenai_compatuse the OpenAI chat-completions API by default.anthropicuses the Anthropic Messages API by default. -
opencode_goandopencode_zenselect the OpenAI chat-completions or Anthropic Messages API per model. -
codexuses the OpenAI Responses API over HTTP or an experimental WebSocket.
Model transport settings can override the default for non-subscription types; see the models block.
Hosted types openai, anthropic, openrouter, opencode_go, and opencode_zen know their addresses, so base_url is optional. A non-empty base_url overrides the default, for example to send traffic through a proxy. You can use headers for extra headers the proxy needs. For Anthropic, Steiner adds /messages for requests and /models for discovery.
For ollama, lmstudio, litellm, and openai_compat, set base_url to where you run the service. Validation fails without it. Use the API root, usually ending in /v1; Steiner adds paths such as /chat/completions.
Codex ignores base_url for ChatGPT sign-in and sends requests to the ChatGPT backend. claude_subscription rejects base_url, api_key, api_key_env, headers, and timeout because it runs the local CLI.
api_key_env names an environment variable holding the key; api_key holds the key itself and expands ${VAR}. api_key wins if both are set. Prefer api_key_env or ${VAR} to keep secrets out of files. Choose any variable name and export it before running Steiner. export lasts for the current shell only, so use your normal secret management to persist it.
Validation requires api_key or api_key_env for the hosted types, but checks only that the variable name is set, not that it has a value. If the variable is unset, requests go out without a key and the provider rejects them. Check with steiner models refresh. Keys are optional for self-hosted and gateway types. Most types send a set key as Authorization: Bearer <key>; Anthropic uses x-api-key.
Each recipe configures a provider in .steiner/config.yaml. Set a model separately, as in the introduction or Profiles.
export OPENAI_API_KEY='your-key'providers:
openai:
type: openai
api_key_env: OPENAI_API_KEYReference its models as openai/<model-id>.
export ANTHROPIC_API_KEY='your-key'providers:
anthropic:
type: anthropic
api_key_env: ANTHROPIC_API_KEYReference its models as anthropic/<model-id>.
export OPENROUTER_API_KEY='your-key'providers:
openrouter:
type: openrouter
api_key_env: OPENROUTER_API_KEYReference its models as openrouter/<provider-model-id>. OpenRouter model IDs usually contain a slash, for example openrouter/openai/gpt-4o.
Sign in at https://opencode.ai, generate an API key, and export it:
export OPENCODE_API_KEY='your-opencode-key'providers:
opencode-go:
type: opencode_go
api_key_env: OPENCODE_API_KEYOpenCode Zen uses the same shape with type: opencode_zen, for example a provider key opencode-zen. Model IDs come from OpenCode: run steiner models refresh, then pick one with /model. Reference models as opencode-go/<model-id> or opencode-zen/<model-id>.
Start a model in Ollama:
ollama run qwen2.5-coder:14bproviders:
ollama:
type: ollama
base_url: http://localhost:11434/v1Replace the model ID with one installed in your Ollama. Reference its models as ollama/<model-id>.
Start LM Studio's local server, load a model, and use the model ID the server reports:
providers:
lmstudio:
type: lmstudio
base_url: http://127.0.0.1:1234/v1Reference its models as lmstudio/<model-id>.
providers:
litellm:
type: litellm
base_url: <litellm-base-url>
api_key_env: LITELLM_API_KEYOmit api_key_env when the gateway needs no key. Reference its models as litellm/<model-id>.
Use openai_compat for any server that exposes the OpenAI-compatible chat-completions API. Omit api_key_env when the endpoint needs no key.
providers:
gateway:
type: openai_compat
base_url: <gateway-base-url>
api_key_env: GATEWAY_API_KEYA Gemini-compatible endpoint works this way only if it exposes that API shape. Reference its models as gateway/<model-id>.
These types use an account sign-in and count usage against that subscription. Codex accepts an optional base_url but ignores it for a ChatGPT sign-in. Claude subscription rejects both base_url and API keys.
Codex uses OAuth to sign in with your ChatGPT account. For a ChatGPT sign-in, requests go to the ChatGPT backend over the OpenAI Responses API; base_url is ignored. If the stored sign-in carries an OpenAI API key (API mode), requests use the configured base_url or the default https://api.openai.com/v1 instead. Codex always uses the stored sign-in, never the configured api_key or api_key_env; without a sign-in, Steiner stops and tells you to run steiner login codex.
Sign in, then check the stored login:
steiner login codex
steiner login codex statussteiner login codex opens your browser and saves the token to steiner/codex_auth.json in your user config directory (~/.config/steiner/codex_auth.json on Linux by default); the command prints the path. It waits up to --timeout (default 120s) for browser sign-in. Use --debug-url to print the authorisation URL before opening the browser, which helps if the browser does not open. The status command prints token expiry and valid or needs refresh, or tells you to sign in. Steiner refreshes the token automatically when it is close to expiry. If refresh fails or there is no login, run steiner login codex again.
Configure the provider:
providers:
codex:
type: codexOptional settings:
-
codex.transportdefaults tohttp.websocketis experimental and does not fall back to HTTP on failure. -
codex.min_request_intervalsets the minimum gap between requests and defaults to0s(disabled). -
compaction_fallback_afterdefaults to30m.
See the providers block for details.
Available model IDs depend on your account. Run steiner models refresh, then use /model to see them. Reference models as codex/<codex-model-id>.
See the usage dashboard for Codex quota information.
Steiner runs your signed-in claude CLI as a subprocess, using your Claude Code login rather than an API key. Install Claude Code with claude version 2.1.294 or later on PATH, and sign in with claude auth login --claudeai. Steiner accepts a first-party claude.ai login.
Configure the provider:
providers:
claude:
type: claude_subscriptionConfiguration rejects base_url, api_key, api_key_env, headers, and timeout. compaction_fallback_after defaults to 60m. Model discovery asks the signed-in CLI for available models, with a built-in fallback list if the CLI is missing, signed out, or returns no usable models within 20 seconds. Reference models as claude/<model-id> using a full model ID reported by the CLI.
See Claude subscription for the extra usage safety gate, privacy, and limitations.
From the project that contains .steiner/config.yaml, use steiner config to check the configuration. With discovery on, steiner models refresh prints ok or failed per provider, providing a quick connection check. Use steiner model inspect <reference> to inspect a model resolution. Run a smoke test with, for example:
steiner --model ollama/qwen2.5-coder:14b --exec "summarize this repository in one sentence"Start steiner and use /model to confirm the models you expect are listed.
-
<provider>/<model-id>is the normal reference.<provider>is the key you chose underproviders, not a display name or the provider type. - Model IDs may contain slashes, for example
openrouter/openai/gpt-4o. Steiner matches the longest configured provider key that prefixes the reference. - An exact alias from
models.definitionswins over prefix parsing.
Raw references work everywhere a model is chosen: profile roles, --model, STEINER_MODEL, /model, and steiner model inspect. A raw reference carries only the provider and model ID. Use an alias when a model needs stored settings such as reasoning effort, request parameters, retry policy, prompts, transport, or limits.
Steiner does model discovery - it will query the provider API to provide it with a list of available models. You do not strictly need to add any model configuration at all on the config file to use and reference models.
An alias under models.definitions gives a model a short or stable name, or stores settings a raw reference cannot carry, like a thinking level, or a compaction threshold.
models:
definitions:
careful:
provider: openai
id: my-model
advanced:
reasoning:
effort: high
supported_efforts: [low, medium, high]Without the reasoning setting, openai/my-model would do the same job. For every alias field, see the models block.
A profile under models.profiles assigns models to roles: default_model, advisor, compaction_fallback, sub_agents, oneshot, and workflow_handoff. Each role accepts an alias or a raw reference.
- Every declared profile must set
default_model. A profile without one fails to load. - Profiles are independent. A named profile does not inherit from
default; a role it omits falls back to its owndefault_model. - Without a
defaultprofile, no roles are assigned unless you pick a profile, and the orchestrator model must come from--model,STEINER_MODEL, or/model. - Selecting an undeclared profile, including
default, is an error. -
default_modelis mandatory and is the fallback model for everything undeclared on that profile.
For a complete configuration with several profiles, see the full example. Its cheaper profile sets no sub_agents, so every sub-agent uses its default_model, deepseek-flash. Select a profile at startup with --profile <name>.
At startup, the active orchestrator model is chosen in this order, with later entries overriding earlier ones:
- The selected profile's
default_model. -
STEINER_MODEL, when it is a valid raw reference or configured alias. -
--model.
STEINER_MODEL and --model change only the active orchestrator. Advisor, sub-agent, compaction fallback, oneshot, and workflow-handoff assignments still come from the selected profile.
During an interactive session:
-
/model <reference>switches the active orchestrator. -
/modelwith no argument, oralt+m, opens the model picker. The picker can also set reasoning effort for models that support it. -
/profile <name>changes the profile used for future role assignments. It does not change the active orchestrator. -
/profilewith no argument, oralt+p, opens the profile picker.
Switching the model or its reasoning effort in a conversation that already has messages asks for confirmation first, because the switch invalidates the prompt cache. If a run is in progress, the switch applies to the next run. See Interactive mode for the full key and slash command reference.
Steiner asks configured providers which models they offer and adds them to the model picker. Discovery is on by default. These provider types support it: openai, openai_compat, litellm, opencode_go, opencode_zen, ollama, lmstudio, openrouter, anthropic, codex, and claude_subscription. Support depends on the provider type, not on the key you gave the provider.
- At startup, cached results load first. Providers with a missing or stale cache refresh in the background, and new entries appear in the picker as each provider answers. Steiner does not refresh again during the session.
- A cached list stays fresh for seven days. Changing a provider's type or base URL invalidates its cache.
- Discovered context windows, output limits, and reasoning efforts also feed model metadata. Settings on an alias still win.
- The picker shows
<provider>/<alias>for configured aliases and<provider>/<model-id>for discovered models. An alias hides the matching raw entry. - The picker ranks models by how often you have switched to them, then aliases before raw entries, then by name. Switch counts are keyed by provider key and backend model ID, so renaming an alias keeps its count.
- Discovery never writes model definitions back to your config, and API keys are never written to the cache.
To turn discovery off:
models:
discovery_enabled: falseWith discovery off, the picker shows configured aliases only, and discovered data is not used for model metadata. Raw references and switch counts still work.
Steiner needs each model's context window, output limit, and reasoning support. For context window and output limit, it uses alias settings first, then discovered provider data, then models.dev metadata, then conservative defaults. For context window only, it also tries a live Ollama probe after discovered provider data. Reasoning efforts follow the same order without the probe. See the models block for the exact precedence.
The models.dev data is cached for seven days. When the cache is stale, Steiner refreshes it as needed and keeps using the old copy if the refresh fails.
Generic types (openai_compat, ollama, litellm) front arbitrary backends, so Steiner cannot always tell which model sits behind an ID. When it cannot, it uses conservative context and output limits. If you know the real limits, set them on an alias with advanced.limits.
| Command | What it does |
|---|---|
steiner models refresh |
Refresh discovered models for every configured provider. Prints ok or failed per provider; exits non-zero only when every provider fails. |
steiner models status |
Print fresh, stale, or missing for each provider's discovery cache, without refreshing. |
steiner model-metadata status |
Show the models.dev cache path, size, age, freshness, and model count. |
steiner model-metadata refresh |
Download fresh models.dev metadata. |
steiner model-metadata clear |
Delete the models.dev cache files. |
steiner cache refresh |
Run model-metadata refresh and models refresh together. |
steiner model inspect <reference> |
Show how an alias or raw reference resolves: provider, backend ID, transport, limits, and where each fact came from. |
When discovery is off, steiner models refresh and steiner models status report that discovery is disabled. See the CLI reference for flags.
These files live under $XDG_CACHE_HOME or $XDG_STATE_HOME when set, otherwise under the defaults shown. They can be deleted safely. A refresh rebuilds the cache directories; deleting model-popularity.json resets picker ranking history, which cannot be recovered by a refresh.
| Path | Contents |
|---|---|
~/.cache/steiner/provider-models/ |
One discovery cache file per provider. |
~/.cache/steiner/model-metadata/ |
models.dev metadata. |
~/.local/state/steiner/model-popularity.json |
Model switch counts used for picker ranking. |
providers:
local:
type: openai_compat
base_url: http://localhost:9105/v1
timeout: 240s
opencode-go:
type: opencode_go
api_key_env: OPENCODE_API_KEY
codex:
type: codex
claude:
type: claude_subscription
models:
definitions:
qwen-35b:
provider: local
id: qwen3.6-35b-a3b
prompt_suffix: "<|think_off|>"
advanced:
limits:
context_window: 65536
max_output_tokens: 16384
haiku:
provider: claude
id: claude-haiku-5-5
advanced:
reasoning:
effort: high
limits:
# Over 100k, haiku costs double, but it needs room to avoid frequent compactions
compaction_threshold_tokens: 262144
sonnet:
provider: claude
id: claude-sonnet-5-5
advanced:
reasoning:
effort: medium
opus:
provider: claude
id: claude-opus-5-5
advanced:
reasoning:
effort: high
deepseek-flash:
provider: opencode-go
id: deepseek-v4.1-flash
advanced:
reasoning:
effort: high
luna:
provider: codex
id: gpt-6-luna
advanced:
reasoning:
effort: high
sol:
provider: codex
id: gpt-6.1-sol
advanced:
reasoning:
effort: high
terra:
provider: codex
id: gpt-6.1-terra
advanced:
reasoning:
effort: high
astra:
provider: codex
id: gpt-6-astra
advanced:
reasoning:
effort: high
profiles:
default:
default_model: terra
advisor: opus
compaction_fallback: deepseek-flash
sub_agents:
code: deepseek-flash
evaluate: sonnet
explore: deepseek-flash
research: luna
review: haiku
sanity_check: haiku
vision: luna
documentor: terra
oneshot:
plan: terra
implement: terra
review: terra
workflow_handoff:
implement: terra
review: terra
cheaper:
default_model: deepseek-flash
advisor: sol
smarter:
default_model: opus
advisor: astra
compaction_fallback: sonnet
sub_agents:
code: sonnet
evaluate: sol
explore: deepseek-flash
# A raw provider/model-id reference with default settings; no alias needed
research: opencode-go/minimax-m3
review: terra
sanity_check: haiku
vision: luna
documentor: terra
local:
default_model: qwen-35bGet 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