Skip to main content

Providers, Models, and Routing

go-code can talk to ten different LLM backends — OpenAI, Anthropic, DeepSeek, Groq, xAI, Kimi, Qwen, Together AI, OpenRouter, and Google Gemini — through a single unified interface. Every provider, every model, and every pricing rate is described in a JSON catalog (catalog/models.json) that the server loads at startup. This page explains how that catalog works, how go-code decides which provider to use for a given run, and how it tracks and caps spending.


The model catalog

catalog/models.json is the source of truth for everything provider-related. It has two top-level fields:

  • catalog_version — a string tag for the catalog itself.
  • providers — a map from provider key (e.g. "openai") to a ProviderEntry.

What a ProviderEntry contains

Each provider entry describes how to reach the API and what models are available:

type ProviderEntry struct {
DisplayName string `json:"display_name"`
BaseURL string `json:"base_url"`
APIKeyEnv string `json:"api_key_env"`
Protocol string `json:"protocol"`
Quirks []string `json:"quirks,omitempty"`
Models map[string]Model `json:"models"`
Aliases map[string]string `json:"aliases,omitempty"`
}

Both base_url and api_key_env are required — the catalog loader will reject an entry missing either field. The protocol field is descriptive metadata in the catalog; it is not consumed by routing. The actual wire client is selected by provider name — only the anthropic provider uses the native Anthropic client; all others use the OpenAI-compatible client. Observed values in the catalog are openai_compat, anthropic, and openai (used by the gemini provider).

What a Model entry contains

Each model entry describes capabilities, pricing, and quirks:

type Model struct {
DisplayName string `json:"display_name"`
ContextWindow int `json:"context_window"` // required, must be > 0
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
Modalities []string `json:"modalities"`
ToolCalling bool `json:"tool_calling"`
ParallelToolCalls bool `json:"parallel_tool_calls,omitempty"`
Streaming bool `json:"streaming"`
ReasoningMode bool `json:"reasoning_mode,omitempty"`
Quirks []string `json:"quirks,omitempty"`
SpeedTier string `json:"speed_tier,omitempty"`
CostTier string `json:"cost_tier,omitempty"`
Pricing *ModelPricing `json:"pricing,omitempty"`
API string `json:"api,omitempty"`
}

The api field controls which HTTP endpoint is used: "responses" routes the call to POST /v1/responses (the OpenAI Responses API); any other value (or empty) uses POST /v1/chat/completions. Several OpenAI Codex models use the Responses API endpoint.


Supported providers

go-code ships with ten providers pre-wired in the catalog. The table shows the provider key you use in API calls, the required API key environment variable, and the wire protocol.

The anthropic provider uses a native Anthropic client (internal/provider/anthropic). All other providers use an OpenAI-compatible client (internal/provider/openai), even when the underlying API is not from OpenAI.

Provider keyDisplay nameAPI key env varProtocol
openaiOpenAIOPENAI_API_KEYopenai_compat
anthropicAnthropicANTHROPIC_API_KEYanthropic
deepseekDeepSeekDEEPSEEK_API_KEYopenai_compat
groqGroqGROQ_API_KEYopenai_compat
xaixAI (Grok)XAI_API_KEYopenai_compat
kimiKimi (Moonshot)MOONSHOT_API_KEYopenai_compat
qwenQwen (DashScope)DASHSCOPE_API_KEYopenai_compat
togetherTogether AITOGETHER_API_KEYopenai_compat
openrouterOpenRouterOPENROUTER_API_KEYopenai_compat
geminiGoogle GeminiGOOGLE_API_KEYopenai

A provider is considered "configured" when its API key environment variable is set. You can check which providers are configured at runtime:

curl http://localhost:8080/v1/providers

Response shape:

{
"providers": [
{
"name": "openai",
"configured": true,
"api_key_env": "OPENAI_API_KEY",
"base_url": "https://api.openai.com/v1",
"model_count": 8
}
]
}

You can also set a provider's API key at runtime without restarting the server:

curl -X PUT http://localhost:8080/v1/providers/anthropic/key \
-H "Content-Type: application/json" \
-d '{"key": "sk-ant-..."}'

This endpoint requires the admin scope when authentication is enabled.


Model catalog by provider

Several OpenAI Codex model IDs in the catalog (e.g. gpt-5.1-codex, gpt-5.2-codex, gpt-5.3-codex) may not be available on all accounts. Verify availability against your OpenAI account before selecting these models in production.

Model IDContextMax outputTool callingEndpoint
gpt-4.1-mini1,000,00016,384yes (parallel)chat/completions
gpt-4.11,000,00032,768yes (parallel)chat/completions
gpt-5.1-codex128,00032,768yesresponses
gpt-5.1-codex-mini128,00032,768yesresponses
gpt-5.1-codex-max128,00032,768yesresponses
gpt-5.2-codex128,00032,768yesresponses
gpt-5.3-codex128,00032,768yesresponses
computer-use-preview128,0008,192yesresponses

Aliases: gpt4-minigpt-4.1-mini, gpt4gpt-4.1, codexgpt-5.1-codex-mini, codex-minigpt-5.1-codex-mini

You can browse all catalog models at runtime:

curl http://localhost:8080/v1/models

Routing: how go-code picks a provider

Startup default

When harnessd starts, resolveDefaultProvider works through four paths in order:

  1. Fake provider — if HARNESS_PROVIDER=fake, use the deterministic fake provider (no API key needed). Useful for smoke tests and CI.
  2. Catalog match — if the default model (set via HARNESS_MODEL, config file, or the built-in default "gpt-4.1-mini") resolves to a configured provider in the catalog, use that provider's client.
  3. Legacy OpenAI — if OPENAI_API_KEY is set, bootstrap an OpenAI client directly.
  4. Error — no provider configured; the server refuses to start.

The built-in default model is "gpt-4.1-mini". You can change the server-wide default via the HARNESS_MODEL environment variable or the model key in your TOML config file.

Per-run resolution

Each POST /v1/runs call resolves a provider independently, letting different runs use different providers without restarting the server:

  1. Explicit provider — if provider_name is set in the RunRequest, that provider is used directly from the registry.
  2. Model lookupProviderRegistry.GetClientForModel(model) searches all providers in order: direct model ID match, then alias match, then (for OpenRouter) live discovery, then the / heuristic for OpenRouter's dynamic model namespace.
  3. Fallback — if lookup fails and allow_fallback is true, the run falls back to the server's default provider.

Alias resolution

Aliases let you use short names like "codex" instead of "gpt-5.1-codex-mini". Each provider in the catalog can define an aliases map. The resolver follows chains up to 8 hops to prevent cycles.

OpenRouter dynamic discovery

OpenRouter can serve thousands of models not listed in the static catalog. When OPENROUTER_API_KEY is set, the harness fetches https://openrouter.ai/api/v1/models with a 5-minute TTL cache. Live results are merged additively with the static catalog — static metadata wins on conflicts.

As a convenience, any model ID containing / is automatically routed to the openrouter provider if the key is configured. This means you can pass "openai/gpt-4.1" directly in model without setting provider_name, and go-code will route it to OpenRouter.


Per-run model and provider selection

Control routing at the run level with these RunRequest fields:

{
"prompt": "Refactor the auth module to use JWTs",
"model": "claude-sonnet",
"provider_name": "anthropic",
"allow_fallback": true,
"fallback_providers": ["openai", "openrouter"],
"reasoning_effort": "high",
"max_cost_usd": 0.50
}
FieldTypeWhat it does
modelstringModel ID or alias. The resolver maps this to a provider.
provider_namestringSkip auto-resolution and use this provider directly (e.g. "anthropic", "openrouter").
allow_fallbackboolIf the primary provider fails with a transient error (429, 5xx), try the next provider.
fallback_providers[]stringOrdered list of provider keys to try when allow_fallback is true.
reasoning_effortstringFor reasoning models: "low", "medium", or "high". Empty uses the provider's default.
max_cost_usdfloat64Per-run spending ceiling in USD. Zero means unlimited.

provider_name bypasses model lookup entirely — the specified model ID is sent to the chosen provider as-is. Use this when you want to override the catalog's routing decision or when a model is available through multiple providers and you prefer a specific one.


Pricing and cost accounting

How pricing is resolved

go-code uses a two-layer pricing system:

  1. External pricing file — if HARNESS_PRICING_CATALOG_PATH is set, a FileResolver reads rates from that JSON file (the repository ships catalog/pricing.json with version "2026-04-28").
  2. Inline catalog pricing — when HARNESS_PRICING_CATALOG_PATH is not set (the default), rates are taken from the pricing block embedded in each model entry in catalog/models.json.

Pricing is computed per turn: (tokens / 1,000,000) * rate_usd. Cache-read tokens are billed at the lower cache_read_per_1m_tokens_usd rate when the model supports prompt caching.

Selected rates (inline catalog, USD per 1M tokens)

These rates come from the embedded pricing in catalog/models.json and are used by default. The separate catalog/pricing.json file shows different rates for some models (for example, deepseek-chat shows different input/output prices). Do not treat these numbers as authoritative billing figures — verify actual charges against your provider's invoices.

ProviderModelInputOutputCache read
openaigpt-4.1-mini$0.40$1.60
openaigpt-4.1$2.00$8.00
anthropicclaude-opus-4-6$15.00$75.00
anthropicclaude-sonnet-4-6$3.00$15.00
anthropicclaude-haiku-4-5-20251001$0.80$4.00
deepseekdeepseek-chat$0.28$0.42$0.014
deepseekdeepseek-v4-flash$0.14$0.28
deepseekdeepseek-v4-pro$0.435$0.87
groqllama-3.3-70b-versatile$0.59$0.79
groqqwen-qwq-32b$0.20$0.20
xaigrok-3-mini$0.30$0.50
xaigrok-4-1-fast-reasoning$5.00$25.00
geminigemini-2.0-flash$0.10$0.40
geminigemini-2.5-flash$0.15$0.60
togethermeta-llama/Llama-4-Maverick-17B-128E$0.27$0.35

Cost ceiling and the run.cost_limit_reached event

Set max_cost_usd in a RunRequest to cap spending for that run. When cumulative cost crosses the ceiling, the harness emits a run.cost_limit_reached event after the current turn finishes, then stops the agent loop (no further turns run) and marks the run completed (not failed) — it is checked at the turn boundary, so it is never aborted mid-turn. You can also set a server-wide default ceiling with HARNESS_MAX_COST_PER_RUN_USD (0 = unlimited).

After a run completes, GET /v1/runs/{id}/summary returns total_cost_usd and cost_status so you can see exactly what was spent.


Provider quirks

Some providers need special handling that the client applies automatically when the catalog entry carries the relevant quirk tag.

QuirkProvidersWhat it does
reasoning_content_passbackDeepSeek, xAI, OpenRouter/DeepSeekReplays prior assistant reasoning back to the API on multi-turn tool use calls.
speed_optimizedGroqInformational only; flags LPU-based inference.
no_parallel_tool_callsGeminiInformational tag indicating Gemini does not support parallel tool calls. The actual disabling is hardcoded by provider name in the client factory, not read from this quirk tag.

Key-free smoke testing

To run the server without any provider API key — useful in CI or local development — use the fake provider:

HARNESS_PROVIDER=fake \
HARNESS_FAKE_TURNS=path/to/turns.json \
HARNESS_AUTH_DISABLED=true \
go run ./cmd/harnessd

With the server running, you can verify catalog routes work:

# List all providers
curl http://localhost:8080/v1/providers

# List all models
curl http://localhost:8080/v1/models

# Start a run against the fake provider
curl -X POST http://localhost:8080/v1/runs \
-H "Content-Type: application/json" \
-d '{"prompt": "hello"}'

The fake provider returns deterministic responses from the turns file and never calls a real LLM.


Configuration reference

The server-wide default model and related paths flow through the standard configuration cascade. The most common environment variables for providers are:

VariableDefaultPurpose
HARNESS_MODEL"gpt-4.1-mini"Server-wide default model
HARNESS_MODEL_CATALOG_PATHauto-detectedPath to catalog/models.json
HARNESS_PRICING_CATALOG_PATH(none)Path to external pricing JSON; when unset, inline catalog pricing is used
HARNESS_PROVIDER(none)Set to "fake" for key-free deterministic mode
HARNESS_OPENROUTER_REFERER"https://github.com/dennisonbertram/go-agent-harness"HTTP Referer header sent to OpenRouter
HARNESS_OPENROUTER_TITLE"go-agent-harness"X-Title header sent to OpenRouter

Next steps

  • See the HTTP routes reference for the full RunRequest schema and response shapes.
  • The configuration guide covers the full six-layer cascade for setting a default model in TOML config files.
  • The events reference lists every event emitted during a run, including provider.resolved, usage.delta, and run.cost_limit_reached.