Skip to content

Providers

The providers config map declares which model providers the agent supports, and it is a gate, not a hint. What an agent advertises as selectable is exactly:

  • every provider listed in providers,
  • every provider named by runtime.model or a runtime.fallbacks[] entry — routing through a provider you did not mean to support is not possible, so those count as declared,
  • ollama and lmstudio when zero-config discovery finds them running.

Declaring a provider widens selection to that provider’s whole advertised catalog (up to maxAdvertisedModels, default 100), not just the models you route to. A Pi built-in that nobody declared and no route uses is not offered at all: advertising all 41 would let an operator pick a provider the agent holds no credential for, and the failure would surface only when the turn ran.

Coverage: config. Configure the map in mono-agent.config.json under providers.

Three keys inside providers are reserved for Pi runtime configuration; every other key is a provider id:

KeyPurpose
piAuthPathPath to the Pi auth store (default ~/.pi/agent/auth.json). OAuth/account credentials and API keys live here.
piNativePi transport and retry tuning: transport (auto/sse/websocket/websocket-cached), piMaxRetries (0–8, default 2), maxRetryDelayMs (default 60000).
localLegacy compatibility projection for providers.local[]. New configs prefer the provider-map shape below.

Each non-reserved key is a provider id, and its value is a provider definition:

{
"providers": {
"ollama": {
"type": "ollama",
"baseUrl": "http://localhost:11434",
"models": [{ "name": "gemma4:31b", "capabilities": { "context_window": 32768 } }]
},
"my-gateway": {
"type": "openai_compat",
"baseUrl": "https://gateway.example.com/v1",
"apiKeyEnv": "GATEWAY_API_KEY"
}
}
}

Each definition accepts: enabled, type (ollama/lmstudio/openai_compat for a self-hosted endpoint — a baseUrl requires one of these unless the id is ollama or lmstudio), baseUrl, trustPublicUrl, apiKey / apiKeyEnv (give the secret’s variable name in .env, never an inline value), models[] (with name, optional alias/displayName, enabled, capabilities like context_window, and pricing), and maxAdvertisedModels.

In JSON config, providers are always the map above (or the legacy providers.local[]). providers.entries[] is the resolved shape — what resolveConfiguredProviders() returns and what a programmatic embedder constructing a MonoAgentConfig in code may set directly. It is not accepted from mono-agent.config.json: entries is not a reserved key there, so it would be read as a provider whose id is entries. Duplicate provider ids are rejected.

ollama and lmstudio are checked against their localhost endpoints without any configuration. A route that names either id is admitted even when providers does not list it, and the running agent probes the endpoint live:

Provider idEndpoint
ollamahttp://localhost:11434
lmstudiohttp://localhost:1234

Probes run concurrently, fail independently, and a live probe lists the endpoint’s models into the operator-facing catalog (bounded by maxAdvertisedModels, default 100). A provider entry that declares enabled: false is not probed.

A declared provider’s catalog becomes selectable — every model it advertises, up to maxAdvertisedModels (default 100 per provider), or exactly the ids you list in that provider’s models allowlist. That applies to:

  • runtime.model and runtime.fallbacks[] (<provider>:<model> refs)
  • per-trigger model overrides (cron jobs, webhook endpoints and request bodies)
  • the per-conversation model selector surfaced by Telegram/Slack/web runtimeControls
  • agent-host memory.llm.model when you run BuJo capture through a declared provider

A route that names a provider in none of providers, Pi’s built-in catalog, nor the two autodiscoverable ids fails closed at startup with a repair message naming the endpoint it needs — for example Provider "private-provider" used by runtime.model is not available; add "providers": { "private-provider": { "type": "openai_compat", "baseUrl": "https://..." } } to mono-agent.config.json. A bare {} entry is deliberately not enough: Pi has no built-in catalog for an unknown id, so an entry with no baseUrl is rejected at load rather than validating and then failing at turn time.

  • Pi-level auth (OAuth/account providers such as Anthropic, GitHub Copilot, OpenAI Codex, and ChatGPT on openai; API-key providers such as OpenCode-Go and openai) resolves through providers.piAuthPath. Set it up with mono-agent auth login <provider> [--pi-auth-path ...]; for openai choose --auth-method oauth|api-key when headless.
  • Provider-level keys resolve through the definition’s apiKeyEnv: keep the value in .env and reference only its variable name in config, so the committed file carries no secret.

Every current app-owned running agent advertises provider auth by default. The web console’s Agent settings dialog lists only providers used by its effective primary/fallback routes, agent-host memory LLM, and enabled static cron/webhook overrides. Each row distinguishes credential detection (present, expired, missing, or keyless not applicable) from proof by a successful live model request. A completed login is therefore detected but remains unverified until a real turn succeeds. Recent process-local provider-auth and provider-unavailable failures are warnings alongside that state; they are not durable history.

Static inspection is intentionally conservative. A stored OAuth access token past expires remains usable-looking when it has a non-empty refresh token, because Pi can refresh it during a request. An expired access token without a refresh path is expired, while empty material, an unsupported credential type, an invalid expiry, an unsafe/unreadable store, or ambient file existence alone needs action. Environment API-key presence may establish only present/not_verified; it never establishes live health.

The headless login flow is provider-owned by the bundled Pi version (0.99.2):

  • GitHub Copilot and OpenAI Codex use native device authorization: open the displayed URL on any browser, enter the displayed code, and leave the dialog open while the agent host polls.
  • Anthropic has no device-code flow in this Pi release. Open its authorization URL and, when the browser cannot reach the callback on the remote host, paste the complete final localhost redirect URL into the dialog. Pi validates the callback state before exchange.
  • openai offers Sign in with ChatGPT or OpenAI API key. A successful selection replaces the previous stored openai method, leaving other providers unchanged; cancellation leaves it intact. The ChatGPT callback listens on the agent host at 127.0.0.1:1455. In a remote browser, or when that port is busy, paste the entire final redirect URL from the browser’s address bar into Agent settings. A code alone is not accepted, and a bad paste ends the attempt: restart sign-in. The authorization URL carries an installation identifier to OpenAI; it is not stored in console history.
  • OpenCode-Go and other Pi API-key providers render their provider-owned prompts in the dialog. Secret prompts are masked and cleared as they are submitted.

There is no --device-auth CLI flag. Browser login uses Pi’s real device-code or manual-code interaction contract; CLI syntax remains mono-agent auth login. The agent host alone reads and writes providers.piAuthPath, using the same owner-only, locked, no-clobber promotion path as auth login --api-key-stdin. The web service only proxies short-lived, no-store session projections and never persists submitted values. This feature does not inspect or modify Codex CLI credentials such as ~/.codex/auth.json.

ChatGPT sign-in creates mono-agent-installation-id.json next to the effective Pi auth file on first use, with owner-only permissions. Agents using the same auth directory share its stable UUID; it is not a credential. To rotate it, stop sign-ins, remove the file from that private directory, then explicitly sign in again. Pi token refresh does not require the installation ID. A stored credential (of either method) takes precedence over OPENAI_API_KEY; selecting an environment key while a stored entry exists cannot switch methods. Use secure-store replacement instead, or explicitly remove the old entry first.

A valid repeated login replaces the current login with a fresh session after provider/method validation; invalid requests do not cancel a usable prompt. Cancellation fences late prompts, events, results, and pre-promotion credential writes. A credential promotion that has already changed filesystem state must finish or recover under the existing atomic transaction. The replacement waits up to two seconds for that safe drain and otherwise fails with replacement_timeout without starting a competing write. Provider checks are not cancelled implicitly.

The agent routes are keyless when its operator endpoint has no API key and otherwise retain the endpoint’s normal bearer requirement. No additional auth configuration is required to make the surface available.

The explicit Run check action uses this agent’s effective piAuthPath, environment, local-provider definition, and Pi implementation. It creates one isolated request for the selected provider/model with a four-token output cap, no conversation history, memory, tools, MCP servers, fallback, or model retry, then discards provider output and raw errors. Candidates must accept text and be enabled. The lowest catalog estimate for 64 input plus 4 output tokens wins; ties follow effective route order then model id. Multiple candidates are not checked when any price is unknown; exactly one unknown-price candidate is the sole exception. A zero catalog price on subscription/OAuth providers means no cataloged incremental token price, not unlimited or free access. A pass proves only the checked provider/credential/model tuple at that moment and may refresh OAuth or consume quota.

Requests whose Pi provider id is opencode or opencode-go, or whose custom model uses the exact hostname opencode.ai, include these provider-facing headers automatically:

  • x-opencode-client: mono-agent
  • x-opencode-session: <provider session id>

The session value is the raw internal provider-session identity. It is not a Telegram chat id, Slack channel id, web thread id, cron id, or other channel conversation id. Continuous conversations reuse it across turns and rotate it after a session reset, invalidation, or requested-primary model change. A stable model override reuses the continuous conversation identity. Per-message, isolated, and otherwise unkeyed direct runtime calls use a fresh one-shot identity. A same-model retry or fallback attempt also gets a fresh one-shot identity, never the primary’s (see Fallback & failover).

Existing auth, model, and request headers win case-insensitively; an explicit null suppresses the automatic value, and a caller header transform can replace or remove it. Other provider ids and hosts receive no x-opencode-* headers.

For a manual transport check, run a throwaway agent configured with opencode-go:deepseek-v4-pro through a local TLS inspection proxy that redacts Authorization, then inspect the outbound request to opencode.ai/zen/go/v1. A transient dummy OPENCODE_API_KEY is sufficient to capture header presence before the expected authentication failure; testing two successful turns requires an explicitly authorized real provider call. This manual check is not performed in CI and must never use a live agent directory.

providers.piNative.promptCacheDiagnostics (default false) enables metadata-only request fingerprints in existing run artifacts. It never emits prompt text, tool arguments, raw cache keys, endpoints or credentials. See Prompt-cache measurement for the artifact reader.

providers.piNative.cacheRetention defaults to "long" (one hour); set "short" (five minutes) to opt out. JSON wins over the "long" default, and the resolved value overrides Pi’s ambient PI_CACHE_RETENTION. The runtime forwards retention only to Anthropic Messages, including child routes. Pi’s supportsLongCacheRetention model check remains authoritative; unsupported models receive no one-hour TTL.

One-hour writes cost 2× normal input, reads 0.1×, versus 1.25× for short-cache writes. Model support is required, and no cache hit is guaranteed. Metadata-only diagnostics record the requested setting and observed cache TTL; an ephemeral Anthropic cache control without an explicit TTL denotes five minutes. Evaluate the measurement gates before separately authorizing spending.

The default benefits agents whose turns arrive 5–60 minutes apart. In a measured maintainer-console workload, 72% of Anthropic cache writes were 5–60-minute re-writes, with an estimated 27% reduction in Anthropic input-equivalent cost. This is workload-specific evidence, not a billing guarantee. Agents that only chain turns within five minutes pay slightly more with long retention and can set "short" instead.

Durable session storage is configured separately with runtime.session.root.