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 39 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, or via MONO_AGENT_PROVIDERS_JSON as a JSON object with the same shape.
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; env MONO_AGENT_PI_AUTH_PATH). 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), piSessionsRoot (durable JSONL session storage). |
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 or MONO_AGENT_PROVIDERS_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; API-key providers such as OpenCode-Go) resolves through
providers.piAuthPath. Set it up withmono-agent auth login <provider> [--pi-auth-path ...]. - 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 Pi 0.85.1:
- 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.
- 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.
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.
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.
Env form
Section titled “Env form”MONO_AGENT_PROVIDERS_JSON is a JSON object of the same shape — provider ids plus the reserved local/piAuthPath/piNative keys. Prefer the config file, but the env override is the escape hatch for secrets-free ephemeral setups:
export MONO_AGENT_PROVIDERS_JSON='{"ollama": {"type": "ollama"}, "piAuthPath": "~/.pi/agent/auth.json"}'MONO_AGENT_LOCAL_PROVIDERS_JSON is deprecated in favor of this map shape.
Related
Section titled “Related”- Pi runtime & model references — the
<provider>:<model>grammar and rejected legacy spellings. - Local providers — the full local-provider and env reference for self-hosted endpoints.
- Fallback & failover — ordered backup routes using the same providers.
- Environment variables —
MONO_AGENT_PROVIDERS_JSONand friends.
Prompt-cache diagnostics
Section titled “Prompt-cache diagnostics”providers.piNative.promptCacheDiagnostics (default false; env MONO_AGENT_PI_PROMPT_CACHE_DIAGNOSTICS) 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.