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.modelor aruntime.fallbacks[]entry — routing through a provider you did not mean to support is not possible, so those count as declared, ollamaandlmstudiowhen 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.
Reserved keys
Section titled “Reserved keys”Three keys inside providers are reserved for Pi runtime configuration; every other key is a provider id:
| Key | Purpose |
|---|---|
piAuthPath | Path to the Pi auth store (default ~/.pi/agent/auth.json). OAuth/account credentials and API keys live here. |
piNative | Pi transport and retry tuning: transport (auto/sse/websocket/websocket-cached), piMaxRetries (0–8, default 2), maxRetryDelayMs (default 60000). |
local | Legacy compatibility projection for providers.local[]. New configs prefer the provider-map shape below. |
Declaring a provider
Section titled “Declaring a provider”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.
Zero-config local autodiscovery
Section titled “Zero-config local autodiscovery”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 id | Endpoint |
|---|---|
ollama | http://localhost:11434 |
lmstudio | http://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.
Widening selection
Section titled “Widening selection”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.modelandruntime.fallbacks[](<provider>:<model>refs)- per-trigger
modeloverrides (cron jobs, webhook endpoints and request bodies) - the per-conversation model selector surfaced by Telegram/Slack/web
runtimeControls - agent-host
memory.llm.modelwhen 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.
Credential resolution
Section titled “Credential resolution”- Pi-level auth (OAuth/account providers such as Anthropic, GitHub Copilot, OpenAI Codex, and ChatGPT on
openai; API-key providers such as OpenCode-Go andopenai) resolves throughproviders.piAuthPath. Set it up withmono-agent auth login <provider> [--pi-auth-path ...]; foropenaichoose--auth-method oauth|api-keywhen headless. - Provider-level keys resolve through the definition’s
apiKeyEnv: keep the value in.envand reference only its variable name in config, so the committed file carries no secret.
Provider authentication in Agent Settings
Section titled “Provider authentication in Agent Settings”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.
openaioffers Sign in with ChatGPT or OpenAI API key. A successful selection replaces the previous storedopenaimethod, leaving other providers unchanged; cancellation leaves it intact. The ChatGPT callback listens on the agent host at127.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.
OpenCode request attribution
Section titled “OpenCode request attribution”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-agentx-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.
Related
Section titled “Related”- Pi runtime & model references — the
<provider>:<model>grammar and rejected legacy spellings. - Local providers — self-hosted endpoint configuration.
- Fallback & failover — ordered backup routes using the same providers.
- Operational environment variables — secret references and process plumbing.
Prompt-cache diagnostics
Section titled “Prompt-cache diagnostics”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.
Anthropic cache retention
Section titled “Anthropic cache retention”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.