Model Slots
Bind capability tiers (local / small / medium / large) to specific models and providers, including per-slot API keys and endpoints for mixing Anthropic with local shims or hosted OpenAI.
Model slots let you assign specific models to four fixed capability tiers
— local, small, medium, and large (cheapest → most capable). They're fixed labels; bind any model to each one and skills and subagents pick the right tier automatically. The local tier is unset by default — configure it to point at a local OpenAI-compatible shim (Ollama, LM Studio, MLX, vLLM).
This means you can mix providers per tier: Anthropic on one tier, a local OpenAI-compatible shim on another, and hosted OpenAI on a third — all in the same process.
Default bindings
An unconfigured install uses these defaults:
| Tier | Default model ID | Identity alias (fixed) |
|---|---|---|
local | (unset — configure via AFK_MODEL_LOCAL) | — |
small | claude-haiku-4-5-20251001 | haiku |
medium | claude-sonnet-5 | sonnet, sonnet_1m |
large | claude-opus-5 | opus, opus_1m |
Note: These default model IDs reflect the latest available models as of this writing. As new versions ship, these defaults are updated in the codebase (src/agent/session/model-slots.ts). You can override any slot via environment variables (AFK_MODEL_SMALL, AFK_MODEL_MEDIUM, AFK_MODEL_LARGE) or afk.config.json — see Configuring slots for details.
If you never touch slots, behavior is identical to before this feature existed.
Configuring slots in afk.config.json
Each slot accepts a bare model ID string, or an object with optional per-slot
provider credentials. The full object shape (ModelSlotConfigEntry, source:
src/cli/config/types.ts:246) is:
| Field | Type | Description |
|---|---|---|
id | string | Concrete model ID this tier resolves to (e.g. claude-sonnet-5, gpt-4o-mini). Required when using the object form. |
name | string | Optional user-defined alias. When set, you can select this tier by name (case-insensitive) anywhere a model is named — AFK_MODEL, --model, /model, or the agent/skill tools' model parameter. Custom names take priority over neutral tier names (small/medium/large) and identity aliases. |
provider | "anthropic" | "openai" | "chatgpt-oauth" | Explicit provider override. Inferred from the model ID when omitted. Use when a bare shim ID (no gpt-/org/model signal) must route to a specific provider. "chatgpt-oauth" (or the "chatgpt" shorthand) forces the ChatGPT-subscription OAuth credential (~/.codex/auth.json) for this tier only — see Per-slot ChatGPT-subscription OAuth. |
baseUrl | string | Per-slot endpoint. For Anthropic-routed tiers, the Messages API base; for OpenAI-routed tiers, the Chat Completions base. Wins over global AFK_OPENAI_BASE_URL / AFK_LOCAL_BASE_URL for this tier only. |
apiKey | string | Per-slot API key. Wins over global credentials for this tier only. |
{
"models": {
// Bare ID — provider inferred, global credentials used.
"small": "claude-haiku-4-5-20251001",
// Object — local OpenAI-compatible shim with its own endpoint.
"medium": {
"id": "mlx-community/Qwen3-32B-4bit",
"name": "local",
"provider": "openai",
"baseUrl": "http://localhost:8080/v1",
"apiKey": "local"
},
// Object — hosted OpenAI with its own key.
"large": { "id": "gpt-4.1", "provider": "openai", "apiKey": "sk-…" }
}
}provider is anthropic, openai, or chatgpt-oauth (inferred from the ID
when omitted; chatgpt-oauth must be set explicitly — see
Models & Providers).
For an Anthropic-routed tier, baseUrl is the Messages API base; for an
OpenAI-routed tier it is the Chat Completions base. A per-slot value wins over
the corresponding global (AFK_OPENAI_BASE_URL, ANTHROPIC_API_KEY, etc.) for
that tier only.
The full field reference for afk.config.json is generated under Reference.
Configuring slots via environment variables
Per-tier env vars override the afk.config.json models block. A
file-provided name or provider is preserved when env overrides only the ID
or credentials.
| Variable | Binds |
|---|---|
AFK_MODEL_SMALL | small tier model ID |
AFK_MODEL_MEDIUM | medium tier model ID |
AFK_MODEL_LARGE | large tier model ID |
AFK_MODEL_SMALL_BASE_URL | small tier endpoint base URL |
AFK_MODEL_MEDIUM_BASE_URL | medium tier endpoint base URL |
AFK_MODEL_LARGE_BASE_URL | large tier endpoint base URL |
AFK_MODEL_SMALL_API_KEY | small tier API key |
AFK_MODEL_MEDIUM_API_KEY | medium tier API key |
AFK_MODEL_LARGE_API_KEY | large tier API key |
Example — run a local shim on the small tier from the command line:
AFK_MODEL_SMALL='mlx-community/Qwen3-32B-4bit' \
AFK_MODEL_SMALL_BASE_URL='http://localhost:8080/v1' \
AFK_MODEL_SMALL_API_KEY='local' \
afk iNote: provider is config-only. When a bare ID bound via env needs an
explicit provider override, set provider in the afk.config.json slot
object; env-bound IDs fall back to ID-inference.
Selecting a tier
Anywhere a model is named — AFK_MODEL, --model, the REPL's /model
command, and the agent/compose/skill tools' model parameter — you can
pass:
- A tier name:
local,small,medium,large - A custom name you set in the binding's
namefield (case-insensitive) - An identity alias:
haiku,sonnet/sonnet_1m,opus/opus_1m,fable - The
autosentinel (passthrough — picks the session model) - A raw concrete model ID
Resolution order (first match wins):
- Custom name (user-assigned
nameon a binding) → tier - Neutral name (
local|small|medium|large) → tier - Identity alias (
haiku/sonnet/opus/fable/*_1m) → its own fixed model ID — never a tier - Raw model ID or
autosentinel (passthrough)
Identity aliases are decoupled from capability tiers. sonnet always names
Claude Sonnet, opus always Claude Opus, and so on, regardless of how you've
rebound the small/medium/large tiers. Rebinding medium to a non-Claude
model changes the medium tier — and the session default, since AFK_MODEL
defaults to the medium tier, not the sonnet identity — but it does not
change what sonnet means. Only the neutral tier names and your custom names
follow tier bindings; identity aliases never do.
How routing works
Assign a non-Anthropic model to a tier (e.g. small → gpt-4o-mini) and that tier routes to the OpenAI-compatible provider automatically.
Each tier's credentials are applied automatically — an Anthropic key can't leak into an OpenAI-routed tier.
What you can mix in the same process
- All bound tiers on Anthropic —
small/medium/largeby default;localis unset until you configure it (unchanged default behavior). - Each tier on a different provider, endpoint, and key — e.g. Anthropic on large, a local MLX shim on small, hosted OpenAI on medium.
- A ChatGPT-subscription model (
provider: "chatgpt-oauth"), a separately-keyed OpenAI model, and an Anthropic model, all coexisting in the same session — see Per-slot ChatGPT-subscription OAuth. - Per-tier credentials via
afk.config.jsonorAFK_MODEL_*env vars.