Configuration
The single JSON config file spawn reads, every property it holds, and how each one shapes a routing decision.
spawn keeps all of its own settings in one JSON file at ~/.spawn/config.json, written on first spawn enable. JSON is the only format spawn reads or writes: there is no YAML or .toml variant. (The one TOML file in the picture, ~/.codex/config.toml, belongs to Codex, not spawn. It is just where Codex itself reads its model provider. See Connect your agents.)
spawn enable writes only the user-facing surface: routing.tiers and routing.rules. Everything else (the port, router internals, confidence floor, effort axis, auth internals) has a sensible default in code and merges in underneath, so the file only shows what you're actually meant to tune. Any key can still be added to the file, or set with spawn config set, to override its default. Run spawn config get to see the full effective config (defaults plus your overlay).
Edit the file directly, or use spawn config set, then restart the gateway (spawn stop && spawn start) to pick up changes.
Example
This is the starter file spawn enable writes:
{
"routing": {
"tiers": {
"anthropic": {
"small": "claude-haiku-4-5",
"mid": "claude-sonnet-4-6",
"large": "claude-opus-4-8"
},
"openai": {
"small": "gpt-5.4-mini",
"mid": "gpt-5.5, low",
"large": "gpt-5.5, high"
}
},
"rules": []
}
}Any other key documented below can be added to this file to override its default.
Target atoms
Every tier and rule use value is a target atom: a string that's either just a model id, or "model, effort":
"claude-sonnet-4-6"
"gpt-5.5, low"The effort suffix is one of low | medium | high | xhigh | max. An effort pinned in a target beats the automatic effort axis. This is how openai.mid and openai.large can point at the same base model (gpt-5.5) at two different reasoning efforts: the effort dial is the real lever on that ladder.
Properties
Top level
| Key | Type | Default | What it controls |
|---|---|---|---|
port | number | 8787 | Port the gateway proxy listens on. Your agent's endpoint override must point here. |
routing.tiers
A map of provider name (anthropic, openai) to its three model tiers. The router picks a tier (small, mid, large); this map decides which target atom that tier resolves to, so you control exactly what runs.
| Key | Type | What it controls |
|---|---|---|
routing.tiers.<provider>.small | string | Target for cheap, fast tasks (exploration, docs). |
routing.tiers.<provider>.mid | string | Target for the default working tier. |
routing.tiers.<provider>.large | string | Top-tier target for hard or high-risk tasks. |
Default ladder:
| Provider | small | mid | large |
|---|---|---|---|
| anthropic | claude-haiku-4-5 | claude-sonnet-4-6 | claude-opus-4-8 |
| openai | gpt-5.4-mini | gpt-5.5, low | gpt-5.5, high |
Provider names match the wire format the agent uses: anthropic for Claude Code, openai for Codex.
routing.rules
Ordered preference overrides on the classifier's dimensions. Rules are checked top-down; the first match wins, otherwise the tier ladder applies.
{
"routing": {
"rules": [
{ "when": { "task_type": "design" }, "use": "large" },
{ "when": { "complexity": "easy", "risk": "low" }, "use": "small" },
{ "when": { "risk": "high" }, "use": "claude-opus-4-8" }
]
}
}| Key | Type | What it controls |
|---|---|---|
when | object | Conditions on task_type, complexity, and/or risk. Keys AND together; an array value means "any of". |
use | string | A tier name (small | mid | large, safety bumps still apply) or an explicit target atom (final, no bumps). |
Rules replace the array wholesale when set in the config file; there's no merge-by-index with defaults (the default is an empty list, so this rarely matters).
routing.confidenceFloor
| Key | Type | Default | What it controls |
|---|---|---|---|
routing.confidenceFloor | number (0-1) | 0.55 | When the classifier's overall confidence drops below this, spawn fails safe and routes up a tier rather than risk under-powering the task. |
routing.bumpOnHighRisk
| Key | Type | Default | What it controls |
|---|---|---|---|
routing.bumpOnHighRisk | boolean | true | High risk bumps both the model tier and the effort one step up, so risky work is never silently nerfed. |
routing.effort
Maps each predicted complexity bucket to a reasoning effort level, for targets that don't pin their own effort. Easy tasks run cheap; hard tasks run at full effort.
| Key | Type | Default |
|---|---|---|
routing.effort.enabled | boolean | true |
routing.effort.byComplexity.easy | "low" | "medium" | "high" | "xhigh" | "max" | "low" |
routing.effort.byComplexity.medium | same | "medium" |
routing.effort.byComplexity.hard | same | "high" |
To route by effort alone (same model, vary effort), point every tier at one model and pin an effort on each target instead of relying on this axis.
router
Settings for how spawn runs the local classifier (spawn-router). Most installs never need to touch these.
| Key | Type | Default | What it controls |
|---|---|---|---|
router.engine | "node" | "python" | "node" | "node" runs in-process via onnxruntime-node (downloads weights on first use). "python" shells out to a spawn-router checkout for the PyTorch model. |
router.hfRepo | string | "afterbuild/spawn-router" | Hugging Face repo the node engine downloads ONNX weights from. |
router.modelRevision | string | "main" | HF revision/tag for the sha256-pinned download. |
router.repoDir | string | ~/.spawn/router | Path to the spawn-router repo checkout ("python" engine only). |
router.modelDir | string | <repoDir>/artifacts/coding-router-v6 | Checkpoint directory. |
router.pythonPath | string | the repo's .venv if present, else "python" | Python executable ("python" engine only). |
router.script | string | "onnx_predict.py" | Serving script; "predict.py" falls back to the PyTorch path. |
projects
Per-project routing overrides, keyed by absolute path prefix. The longest matching prefix wins at task kickoff.
{
"projects": {
"/home/me/work/critical-service": {
"tiers": { "anthropic": { "small": "claude-sonnet-4-6" } },
"rules": [{ "when": { "risk": "high" }, "use": "large" }],
"pin": "claude-opus-4-8"
}
}
}| Key | Type | What it controls |
|---|---|---|
tiers | object | Partial tier overrides, same shape as routing.tiers. |
rules | array | Prepended before the global rules for this project; first match still wins. |
pin | string | An explicit target atom that short-circuits the ladder entirely for this project. |
Each override needs at least one of tiers, rules, or pin.
auth
| Key | Type | Default | What it controls |
|---|---|---|---|
auth.mode | "subscription" | "api_key" | "subscription" | How upstream requests authenticate. Set by spawn enable; both are first-class. See Authentication. |
upstreams and subscriptionUpstreams
Base URLs the gateway forwards to, per provider and auth mode. Only override these if you're pointing at a different endpoint (e.g. an enterprise proxy).
| Key | Default |
|---|---|
upstreams.anthropic | https://api.anthropic.com |
upstreams.openai | https://api.openai.com |
subscriptionUpstreams.anthropic | https://api.anthropic.com |
subscriptionUpstreams.openai | https://chatgpt.com/backend-api/codex |
apiKeys and anthropicAuthHeader
Per-provider, gateway-held API keys. If a key is present for a provider, the gateway strips the client's own credentials and injects this key (api-key billing for that provider only); if absent, the client's own subscription/OAuth login rides through untouched. This means, for example, one client can ride a Claude subscription while another uses an OpenAI API key.
| Key | Type | Default | What it controls |
|---|---|---|---|
apiKeys.anthropic | string | unset | Gateway-held Anthropic API key. |
apiKeys.openai | string | unset | Gateway-held OpenAI API key. |
anthropicAuthHeader | "x-api-key" | "authorization" | "both" | "x-api-key" | Header used to inject the Anthropic key (ignored unless apiKeys.anthropic is set). |
Authentication
The auth.mode field records the choice you made at spawn enable:
"subscription"(default): the gateway forwards your agent's own Pro/Max or ChatGPT login. No API key is needed."api_key": the gateway forwards your own provider API key to the standard provider API.
Both are first-class. Switch by re-running spawn enable with --subscription or --api-keys, or edit auth.mode directly. See the gateway for the full tradeoff.
Reading and writing values
spawn config get # full effective config (defaults + your overlay)
spawn config get routing.tiers.openai.mid
spawn config set routing.tiers.openai.mid "gpt-5.5, low"
spawn routing show # tiers, rules, effort axis, with * marking overrides