#Model providers: connections and backends
A connection is one entry in Model providers (providers.json on the mothership): a base_url, a credential, and a wire — anthropic (the default) or openai. Model traffic reaches a connection through the provider gateway at /providers/<id>/ (docs/protocol.md §6.5), which presents the Anthropic Messages wire to the colony and translates an openai-wire connection in both directions. This page is the compatibility map between those connections and the shipped agent backends, the two switches that take tools away from a colony, and the settings that decide what a connection may spend and carry. Every field and route is in docs/protocol.md §6.5.
#Connection → backends
| Backend | anthropic wire | openai wire | Harness-level disabled_tools |
|---|---|---|---|
claude-code | yes — unrouted models go straight to Anthropic; <provider>/<model> rides the gateway's Anthropic Messages route | yes — same route; the gateway translates the openai wire | yes — the disabled_tools setting (COLONIZER_DISABLED_TOOLS) becomes the SDK session's disallowedTools |
acp | no | no — talks to the agent's own API host (Gemini: generativelanguage.googleapis.com, grok: api.x.ai) with the colony's own secret; the runner passes model ids to session/set_model and reads no model routes | no — ACP names no per-tool switch, so the module declares no disabled_tools setting |
codex | no — an anthropic-wire route is refused (CODEX_MODEL_PROVIDER): codex speaks the OpenAI wire only | yes — a <provider>/<model> whose prefix matches a route rides the gateway's OpenAI passthrough (/v1/responses) via a codex model_provider override; bare ids and openai/ with no route go straight to api.openai.com with CODEX_API_KEY | yes — native names (shell, web_search, view_image) become -c features.shell_tool=false, -c web_search="disabled" and -c features.view_image=false, and the runner passes --strict-config so a key codex stops recognising fails the turn instead of silently keeping the tool; apply_patch (how codex writes files) and MCP tools cannot be turned off |
grok-build | no — an anthropic-wire route is refused (GROK_MODEL_PROVIDER): grok speaks the OpenAI wire only | yes — same passthrough, via GROK_MODELS_BASE_URL and the colony token as the bearer key; bare ids and xai-grok/ with no route go straight to api.x.ai with XAI_API_KEY | yes — native tool ids (run_terminal_cmd, read_file, write_file, search_replace, grep, list_dir, web_fetch, Agent) go to --disallowed-tools; web_search is always off already |
hermes | yes — one config provider per gateway route, transport: anthropic_messages | yes — same route; the gateway translates | yes, toolset-granular — the names are Hermes toolsets (terminal, file, web, browser, vision, code_execution, todo, session_search, image_gen, …) added to agent.disabled_toolsets; single tools inside a toolset (only write_file, say) cannot be turned off |
opencode | yes — one @ai-sdk/anthropic provider per gateway route at <base_url>/v1 | yes — same route; the gateway translates | yes — native tool ids become permission: {"*": "allow", <id>: "deny"} entries in the generated inline config; edit covers write/edit/apply_patch (the three share one permission) |
pi | yes — the runner writes models.json from the routes, api: anthropic-messages | yes — same; the gateway presents anthropic-messages to every guest | yes — native tool names (read, bash, edit, write, grep, find, ls) go to --exclude-tools |
codex and grok-build speak only the OpenAI wire, so a route reaches them when the connection's wire is openai: the gateway forwards their Responses and Chat Completions requests to the connection verbatim and records the usage, so spend accounting and the budgets apply. A connection on the anthropic wire is refused by both runners, named in the model setting's error. A bare model id, or openai/ / xai-grok/ with no route configured, still runs against the vendor API from the colony's own secret, outside the gateway: no spend accounting through the gateway and no connection-level tool strip for those.
#model_map and wire names
A connection's optional model_map maps a canonical model name to the name sent on the wire: {"<canonical>": "<wire_name>"}. An empty wire_name sends the canonical name as-is; a non-empty one is sent verbatim, so a provider that only knows its own branding can be addressed by the canonical name every setting uses. A non-empty model_map is authoritative: the connection serves only the canonicals it lists, and boot refuses a model that is not among them.
Settings → Providers has a Model map editor for this (canonical → wire rows), in the form's Connection policy group; PUT /api/providers/{id} and providers.json take it too. A PUT that leaves a field out keeps its saved value; an explicit empty value clears it.
#Taking tools away: two levels
- Connection (
disabled_toolson the provider). A list of Claude Code tool names. The gateway strips those tools from thetools[]of every request served through that connection —WebSearchalso strips theweb_searchserver tool,WebFetchalsoweb_fetch. The strip applies per connection, so it reaches exactly the backends whose traffic rides the gateway (see the table). - Harness (
disabled_toolson the agent module). Per backend, regardless of endpoint. The setting reaches the runner asCOLONIZER_DISABLED_TOOLS, and every shipped backend butacpturns it into its own CLI's switch — the table above says which, and what the names are. Each module declares the valid names itself, as thex-known-toolslist on the setting's schema property, and the boot check validates the setting against that list — falling back to Claude Code's tool names for a module that declares none.
Boot logs what was taken away, per colony, into harness.jsonl: tool '<name>' disabled (level: connection|harness, …).
#Boot fails fast
A colony that cannot reach the model it was configured with is refused at boot, not left to flail:
backend '<agent id>' has no provider for model '<value>': <fix>
with a fix in the message, when any of these holds:
- the model's
<provider>/prefix names no configured connection; - the connection's non-empty
model_mapdoes not list the model; - the connection is unreachable at launch and the route has no Claude
fallback_model(a<provider>/<model>fallback only covers quota exhaustion). Before refusing, the boot probes the connection again rather than trusting the cached answer, so an outage that ended a moment ago does not block the launch. A connection with a Claudefallback_modelonly gets a warning in the colony log: its requests go to that Claude model instead.
Only the connections this colony's model settings name (model, subagent_model, background_model, small_model where the agent module has one, and model_low/model_high when per-task routing picks one) are checked, so an unrelated connection that is down blocks nothing.
Two more misconfigurations refuse the launch the same way, before any probe runs:
- a connection-policy row a save would have refused, in a hand-edited
providers.json— the message names the file, the provider and the row:providers.json: provider '<id>': model_map['<canonical>']: …(ordisabled_tools['<tool>']: …); - an unknown tool in the harness
disabled_toolssetting — the message names the agent module and the tools it knows:agent module '<id>' setting 'disabled_tools': unknown tool '<name>' (known: …).
#Base URLs
The gateway joins the request's own path (/v1/messages, /v1/chat/completions, /v1/responses, and /v1/models for the health probe) to base_url, ignoring any trailing /. Enter the base the provider documents as its API root:
- A base whose path ends in a version segment —
/v1,/v3,/api/v3,/api/coding/v3, any/v<digits>— is taken as that root, and the request path's leading/v1is dropped, sohttps://ark.ap-southeast.bytepluses.com/api/coding/v3posts to…/api/coding/v3/chat/completionsandhttps://api.x.ai/v1to…/v1/chat/completions. This holds for every route, on either wire. - Any other base gets the full path appended:
https://api.deepseek.com/anthropicposts to…/anthropic/v1/messages, andhttp://host:8000tohttp://host:8000/v1/chat/completions. - An
anthropic-wire base that ends in/v1is refused on save; enter it without the/v1.
Saving a new provider, or changing its base URL, wire or key, in Settings → Providers sends a one-token test request through the colony's route and shows the URL it hit and the status. An upstream 404 or 405 during a colony's turn is logged with that URL too (no userinfo or query), and names the base URL as the likely cause.
#Plans, quotas and trust
A connection carries a few more settings. pricing and quota are edited in Settings → Providers, as are trusted, model_map and disabled_tools (the form's Connection policy group); the API (PUT /api/providers/{id}) and providers.json hold them all, and GET /api/providers returns them so the form prefills.
- Which colonies may use it. A colony's gateway token opens only the connections its model settings route to, and only for the models they name: both are recorded at boot, before the token is written, and a colony with no recorded set reaches nothing. A request for any other connection or model is refused with
403, so one colony cannot spend on a provider or a model it was not configured for. pricing— dollars per million input, output, cache-read, cache-write and thinking tokens. Unset, routed requests cost$0but their tokens are still counted, so the sandbox module'sbudget_tokensstill holds a colony on a prepaid plan thatbudget_usdnever can.quota—{"url", "pointer", "limit_pointer"?}, where to read what is left in a prepaid token plan. The gateway sends aGETtourlwith the connection's own credential, sourlmust have the same scheme, host and port asbase_url, and reads the number (or numeric string) atpointer, an RFC 6901 JSON pointer. The provider health check (GET /api/providers/{id}/health) then answersquota: {remaining, error}, and the provider card shows "N left in plan". The optionallimit_pointernames the plan's total in the same answer (quota: {remaining, limit, error}), so the model switcher can draw used against limit and the percent left; without it the switcher shows the balance alone. A failed quota read never marks the connection unreachable.quotaomitted on aPUTkeeps the saved probe; an emptyurlclears it.- Moving a connection. The credential is sent wherever
base_urlpoints, so aPUTthat moves a keyed connection to a different origin — scheme, host or port — is refused unless the API key is entered again (api_key) or removed ("") with the save. A path change on the same origin is the same party and keeps the saved key, and a connection with no key stored moves freely. A savedquotaprobe left on the old origin is refused the same way: move or clear it in the same save. - Quota exhaustion. When a provider answers
429or403with a message that says the plan ran out (not a plain rate limit), the gateway records it as exhausted until the reset the message names, or for 15 minutes when it names none. With a Claudefallback_model, the request is retried on that Claude model. With a<provider>/<model>fallback on another connection of the same wire (anthropic to anthropic, openai to openai), the Mothership retries the request there itself; a cross-wire fallback is refused when you save it.COLONIZER_QUOTA_FALLBACK=0turns that failover off for every connection at once. When every connection the colonies route to is exhausted, the queue pauses, and a colony whose turn died on the plan is stopped with its worktree kept until the provider recovers. - Claude account fallback. The Claude subscription is not a connection, so it has no
fallback_modelof its own. The Claude Code module's If Claude runs out, use (account_fallback_model, a<provider>/<model>such asminimax/MiniMax-M3.1; install-wide) names one. While the account's session or weekly limit holds, every request that would go to Claude — the orchestrator, the subagents, the background, small-task and large-task roles, a per-colony or org override that names a Claude model — goes to that model through the gateway instead. A colony's model router asks the gateway (GET /account-route, with the colony token) before each such request, so the decision is made at request time from the live quota record: no saved setting changes, and at the reset the same colonies are back on Claude with nothing to switch back. Running colonies carry on, new ones start as usual, and colonies the limit parked resume on the fallback; the colony's log says so once per switch ("Claude account is out; this colony's Claude requests run on …") and once on the way back. A restricted-sensitivity task takes the fallback only when its connection meets the class's bar (markedtrusted, unless the org's sensitivity override says otherwise). Otherwise the request stays on Claude, the turn fails on the limit, and the colony parks with the reason on its card: "needs a trusted provider: Claude is out until 19:51; MiniMax is not marked trusted". A fallback that names no configured connection, or whose own plan is out, counts as no fallback. With the setting empty (the default) nothing changes: the queue pauses on the account's limit and colonies wait for the reset. - Out-of-quota card. Colonies blocked on an exhausted connection (every request since their last success answered with the quota error, and no
fallback_modelretry) show on one "Provider out of quota" card per connection — in the inbox's "Needs you" list and at the top of Settings → Providers — with the model, the reset and a countdown. Switch model moves those colonies (or, with "this org", their orgs' model settings too) to a healthy model from the picker, which shows each model's failure rate, and restarts them on it. "Every role using this provider" goes further: every model role in the install's agent settings and every org override that points at the connection moves too, after each role is checked (nothing changes if one cannot take the model), and the cockpit lists each setting as "was X → now Y". "Remember" saves the pick as the connection'sfallback_model— a Claude model, or a model on a same-wire connection — so the next exhaustion retries on it by itself. Wait until reset parks them and resumes them at the reset. Stop stops them. The API isGET /api/attentionandPOST /api/providers/{id}/quota-action(docs/protocol.md §6.5). With the notify module on, a card opening is also one notification per provider (on_quota, and the device's "Provider out of quota" switch for Web Push) naming the provider and its reset; tapping it opens the Inbox. trusted— off by default. A colony whose task names restricted paths (secrets,.envfiles, infrastructure config) may only reach a connection markedtrusted: true; any other answers403and the colony log says why. The launch resolves the orchestrator, subagent, background and small models against the task's class first (issue #704): one routed to a connection this gate would refuse is replaced with an eligible model — the orchestrator's, or the module's ownmodel— and the substitution is logged and recorded on the colony, so the cockpit shows what it is really running on instead of a colony that 403s every subtask. A model the operator named at launch is resolved the same way. When the eligible model is itself blank — the default setup, where the module names nomodel— the setting is cleared so the task inherits the harness default, recorded as "the orchestrator's model". If no eligible model exists at all the setting is left as it is and the boot warns.vetted— off by default, and implied bytrusted. Paths a repository classifiesvettedin.colonizer/sensitivity.tomlneed a connection markedvetted: trueor better; the looser classes (open,standard,custom) run on any connection unless an org's settings raise their bar. Omitted on aPUT— ornull, which counts as omitted — it keeps the saved value.vendor— the organisation that actually runs the model behind the endpoint ("anthropic", …), as the operator records it. It gates nothing on its own: an org can pin restricted work to a list of vendors, and a connection with no vendor recorded never matches such a list. Omitted on aPUT— ornull— it keeps the saved value; only an empty string clears it.- Sensitivity overrides — an org's workspace settings can set the minimum mark per class:
"sensitivity": {"standard": "vetted", "restricted": "vetted", "restricted_vendors": ["anthropic"]}in the org'sorgs.jsonentry, each class one ofany,vettedortrusted,nullinheriting the built-in default.restrictedcan be loosened tovettedbut never toany, and a vendor list, when set, must name at least one vendor, matched case-insensitively against the recordedvendor(docs/audit.md, Security-aware routing).