---
title: Pi provider (your chosen model, in Pi's harness)
---

# Pi Provider

## What it is

Omniscio can spawn Claude-Code-style sessions backed by **Pi** (pi.dev — the `@earendil-works/pi-coding-agent` CLI, run as `pi --mode rpc`) as an alternative provider, alongside `claude` (the default), `codex`, `gemini`, `antigravity`, the Anthropic-compatible `deepseek` / `kimi` / `glm` / `minimax` / `meta`, `opencode`, `cursor`, `hermes`, `grok`, and `openclaw` (the registry defines 26 session-provider keys, 20 of them user-selectable/pickable; the non-pickable ones include the managed-PTY `terminal` engine and the internal-only `openclaw`). Pi is a local CLI coding agent that talks to model providers directly and runs **any model you pick** — Claude, GPT, Gemini, or anything reachable through OpenRouter — so a Pi session is "your chosen model in Pi's harness," not Claude in a different wrapper. Pi can even run a **local model on your own machine** via Ollama (`ollama/<tag>`) — free, private, and offline, with no API key (see "Local models" below).

## Where to find it

**Settings → Accounts** — the Pi section stays hidden until you flip **Show alternative AI providers**. Pi has no sidebar project of its own, so you spawn a Pi session inside any real project.

## How it behaves

### What the user sees

A Pi session looks almost identical in the sidebar and main pane to a Claude session — same status dots, same streaming bubbles, same Ctrl+Enter to send, same archive / pause / snooze. The differences:

- **Pi glyph** (a small **π** mark) in the session header next to the title. `ProviderBadge` is icon-only (no text pill) by product decision; Pi is one of the monochrome providers (like Codex and Kimi), so its glyph renders in the neutral surface color rather than a tinted pill — colored marks (Gemini, Anti-Gravity, Cursor) keep their brand color. The header shows every started session's engine mark next to the title — Claude included (in its brand orange), on desktop and mobile alike.
- **Token-by-token streaming.** `pi --mode rpc` emits a JSON event stream, so the reply appears incrementally; one final push replaces the bubble with the full text when the turn completes.
- **Tool-use auto-approval is ON.** Pi runs its tools (file writes, shell commands) without per-call prompts. The "Allow Pi sessions" toggle gates whether you can spawn it at all; once spawned it does not ask before acting. (This is the v1 scope — Pi's interactive approval protocol is deferred; see "v1 scope" below.)
- **Real per-session dollar cost.** Pi reports authoritative cost and token counts for every turn, so the Cost line on a Pi session shows actual spend — unlike Codex (records tokens but $0) and Cursor (records $0). Spend rolls up into the Stats virtual project the same way Claude's does.

### How to enable

> **Master toggle required first.** Pi is an alternative provider — by default Omniscio ships as a Claude-only product, and the entire Pi setup section is hidden in Settings → Accounts. Flip **Settings → Accounts → Show alternative AI providers** to ON (off by default) and the **Pi (CLI provider)** panel appears. With the master off, the per-launch provider switcher hides Pi — it effectively does not exist in the UI even if every gate below is configured. Your "Allow Pi sessions" toggle and model choice are preserved across master-toggle flips.

> **Fastest path — "Set it up for me."** The Pi panel leads with a **Set it up for me** button: one click turns Pi on, **installs the `pi` CLI for you** (via Omniscio's toolchain installer), and — because Pi is model-agnostic — **sets it up with whichever provider key you already have saved** (Anthropic, OpenAI, Google, or OpenRouter), choosing that provider's default model so Pi is usable immediately. Keys are checked in priority order (Anthropic → OpenAI → Google → OpenRouter) entirely in the main process — the key value never crosses to the UI. The outcome shows **in place, right in the Pi card**: "Pi is set up to use GPT-4o — ready," an installing or error state, or — when you have **no** provider key at all — a clear "Pi needs an API key from Anthropic, OpenAI, Google, or OpenRouter; add one in the sections above" note. Pi's own toggle stays **off** until a usable key exists, so once you add one, click the button again to finish. (Your Claude **subscription** sign-in can't be used by Pi — it needs an actual API key.) The three manual gates below are exactly what that button automates — governed by [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md).

Three gates must all pass (`getProviderSpawnReadiness` enforces them in order: toggle → binary → model/key resolver):

> **The toggle leads and gates the panel UI.** The Pi panel shows ONLY the **Allow Pi sessions** toggle until you turn it on; the binary-install note and the model picker appear once it's enabled — so a model can't be picked for a switched-off feature. When off, a one-line hint stands in and still carries the Settings-search anchors (`pi-binary`, `pi-model`) so deep-links land.

1. **Allow Pi sessions** toggle — off by default. Same security stance as the other auto-approving providers (Gemini, Cursor): enable only because you intend to use it. Lives in the Pi panel as **Allow Pi sessions in any project**, and it's the first thing you see (revealing the steps below).
2. **Pi CLI binary** — the `pi` binary must be on your PATH. The **Set it up for me** button installs it for you (`npm install -g @earendil-works/pi-coding-agent` under the hood, clearing the binary-resolver cache so the install is seen immediately); to do it by hand, run that command and restart Omniscio so the running process picks up your updated PATH. (Pi is registered in Omniscio's toolchain installer with `defaultSelected:false`, so it's installable on demand but never auto-installed in the first-run "install recommended tools" sweep.)
3. **Model + API key** — pick a model in the panel's **Model** dropdown (or type any `provider/model` id in the Custom box). Pi **reuses the provider keys you already entered above** — there is no separate Pi key. The required key depends on the model you pick (see "Model routing" below); if that provider's key is missing, the gate reports `key-missing` and deep-links to the right field.

Once all three are green, you launch a Pi session three ways:

- **Per-project default** — the Edit-Project "Default provider" radios and the "+ New Session" split-button include Pi (it's a pickable provider). New sessions in that project spawn Pi automatically.
- **Per-launch override** — on a fresh (zero-message) session, the **Change-provider button** in the new session's main panel (the launch-config pickers on a fresh session) lets you pick Pi before sending the first message. Non-ready providers appear disabled with a "Set up… / Install… / Add key" suffix that deep-links into the relevant Settings panel.
- **Programmatically** — anything that creates a session with `provider: 'pi'` (recipes, the CLI control server, agent-driven sessions). The same three readiness gates apply on the backend.

### Model routing — any model, your own keys

Pi's chosen model is the `piModel` setting, a `provider/model` id (e.g. `anthropic/claude-sonnet-4-5`, `openai/gpt-4o`, `google/gemini-2.5-pro`, `openrouter/moonshotai/kimi-k2`). The **provider prefix** (first path segment) selects which env var carries the credential and which stored key Omniscio injects. An unset / empty `piModel` resolves to the concrete default `anthropic/claude-sonnet-4-5`.

| Prefix       | Env var Pi reads     | Key Omniscio injects (reused from)                                                                                                                               |
| ------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `anthropic`  | `ANTHROPIC_API_KEY`  | the OpenCode (Anthropic) key                                                                                                                                     |
| `openai`     | `OPENAI_API_KEY`     | the OpenAI key (shared with Codex)                                                                                                                               |
| `google`     | `GEMINI_API_KEY`     | the Gemini key — note **`GEMINI_API_KEY`, not `GOOGLE_*`** (Pi-specific gotcha)                                                                                  |
| `openrouter` | `OPENROUTER_API_KEY` | the **raw user** OpenRouter key (cost guard — never Omniscio's internal-fallback key, so your full coding session can't bill Omniscio's shared account)          |
| `ollama`     | — (none)             | **none** — a LOCAL model served by Ollama; Omniscio writes a managed `models.json` and injects `PI_CODING_AGENT_DIR` instead of a key (see "Local models" below) |

The key always rides the spawn **environment**, never the command line. Verified against Pi's own source (`packages/ai/src/env-api-keys.ts`): Pi reads these vars straight from `process.env` with no `/login` flow and no `~/.pi` config file required, so injecting the key at spawn is sufficient for headless operation. A model whose prefix isn't one of the four cloud providers **or** the local `ollama` prefix reports `model-unsupported` (the v1 scope; Bedrock/Vertex/Mistral/Groq env vars are documented in Pi but unverified here).

### Local models (Ollama) — free, private, offline

Pick a model prefixed `ollama/` (e.g. `ollama/qwen2.5-coder:7b`) to run it **on your own machine** through [Ollama](https://ollama.com) instead of a cloud provider — no API key, no dollar cost, nothing leaving your computer. The Pi **Model** dropdown lists the models you've pulled in Ollama automatically (fetched live via the ungated `pi:list-local-models` IPC → `/api/tags`); you can also type any `ollama/<tag>` in the Custom box.

**How it works.** Pi has no base-URL flag — it learns about a custom provider only from a `models.json` under its config dir. So Omniscio writes its OWN `models.json` (a custom OpenAI-compatible provider pointed at Ollama's local endpoint `http://127.0.0.1:11434/v1`, with a dummy keyless `apiKey`) into an Omniscio-managed home (`<userData>/pi-local-home/`), and injects `PI_CODING_AGENT_DIR` at spawn **for local sessions only** — so your real `~/.pi` config is never touched. Verified against pi 0.79.1: `PI_CODING_AGENT_DIR=<D>` makes pi read `<D>/models.json`. The file lists a few seed coding models, but **any** model you've pulled runs — pi clones a seed's routing for an unlisted tag — so the file is static (written once, never refreshed from your installed set). The model resolver adds a keyless `ollama/<tag>` route (kept out of the keyed cloud prefixes) and allows `:` in tags.

**Pre-flight + errors.** A synchronous readiness gate can't probe the network, so Omniscio checks Ollama at spawn (reusing Local Chat's `listInstalledModels`): if Ollama isn't running you get **"Ollama isn't running — start Ollama…"**, and if the model isn't pulled, **"…isn't pulled yet. Run: `ollama pull <tag>`"** — both as clear turn errors that deep-link to the Pi settings, never a cryptic pi/Ollama failure mid-turn.

**Cost + quality.** Local turns record a truthful **$0** — Ollama reports real token counts but no dollar cost, so the `turn_end.message.usage` cost is `0` (cost-is-real-and-accumulated-per-turn path unchanged). Local models are meaningfully weaker coders than Claude: best for light edits, offline work, and privacy-sensitive tasks — pick a coding-tuned model like `qwen2.5-coder`, not a chat model.

**Caveats (v1):** the local picker is **Settings-only** (the per-session pre-first-message picker and accurate per-model context-window sizing are fast-follows); the endpoint is fixed to Ollama's default port; reasoning models (e.g. `gpt-oss`) that need extra `compat` flags aren't specially handled. See provider-registry-contract **a-local-model-routes-keyless-in-an-owned-home**.

### Error states and fixes

Three gap codes (same shape as Codex / Gemini / OpenCode — toggle, binary, then a model/key resolver gate):

| Gap                 | What it means                                      | Click-to-fix lands you at…                      |
| ------------------- | -------------------------------------------------- | ----------------------------------------------- |
| `toggle-off`        | "Allow Pi sessions" is OFF in Settings             | Settings → Accounts → Allow Pi sessions toggle  |
| `binary-missing`    | `pi` CLI not found on PATH                         | Settings → Accounts → Pi CLI binary note        |
| `key-missing`       | The chosen model's provider has no key configured  | Settings → Accounts → that provider's key field |
| `model-unsupported` | The `piModel` id isn't a routable `provider/model` | Settings → Accounts → Pi Model picker           |

### No virtual project

Pi has **no dedicated `__pi__` sidebar entry** (unlike `__codex__` / `__gemini__` / `__antigravity__`), so the "Allow Pi sessions" toggle always applies — you spawn Pi inside any real project. (A `PI_PROJECT_ID = '__pi__'` sentinel is reserved in the readiness config for a future virtual project, but none is created in v1, so the sentinel bypass never fires.) Pi IS a per-project default and IS switch-to-able, which makes it "first-class" like Cursor — unlike OpenCode.

### v1 scope (what's deferred)

Pi v1 keeps the surface small and is `{ images: false, mcp: false, ssh: false, permissionPrompts: false, multiTurn: true }`:

- **No image input** — text-only messages.
- **No MCP** — Pi sessions don't get Omniscio's MCP servers, and Pi is absent from the provider-config-sync targets.
- **No approval prompts** — tools auto-execute. Pi _does_ have an interactive `extension_ui_request` protocol; wiring it through Omniscio's shared permission UI (like Codex) is future work.
- **Resume is Omniscio context-replay, not Pi's native session restore** — Pi's `switch_session` + `sessionPath` resume-by-file-path exists in the protocol but isn't wired. Instead Omniscio resumes a stopped Pi session by re-spawning it and replaying the stored conversation as a context prefix on the next message (the shared base helper), so you continue seamlessly even though Pi's own file-path resume is unused.
- **Protocol verified against source, not a live binary.** The exact RPC message field names were confirmed against the `badlogic/pi-mono` TypeScript during the build, not a live `pi --mode rpc` run. A live end-to-end smoke test with the real binary is the recommended next verification. See the contract's "Known gaps."

## For agents

### How it works under the hood

**One long-lived child process per session.** Pi is a `persistent-external` provider (the same runtime class as Codex). For each session Omniscio spawns `pi --mode rpc --model <provider/model>` once and keeps it alive; multi-turn is native — the same process handles every message, no `--resume` flag needed during the session's lifetime. Spawning is **lazy by default**: `launch()` registers an in-memory session (`status='ready'`) with no child, and the process starts on the first message (kill switch: `AMC_DISABLE_PI_LAZY_SPAWN=1` spawns eagerly at launch). When Omniscio quits, the child dies (Windows: via the orphan-kill Job Object). On Windows `pi` launches only INSIDE that job — the job-gate launcher holds it until Omniscio has put it there — and a launch that cannot be contained is refused ("Failed to start Pi session: Windows process-tree ownership …") rather than run outside it. A forced stop kills the whole process tree. Pi keeps no on-disk transcript to natively rehydrate, but — like Codex and OpenClaw — **sending a message to a stopped Pi session re-spawns it and replays Omniscio's stored conversation as context** (the shared `applyResumeContextPrefix` helper on `BaseExternalSessionManager`), so you continue where you left off (2026-06-10).

**Strict JSONL framing (the load-bearing gotcha).** Pi speaks newline-delimited JSON over stdin/stdout. The client (`pi-rpc-client.ts`) splits stdout on `\n` **only**, strips a trailing `\r`, buffers a partial trailing line across chunks, and tolerates non-JSON lines — it must **never** use a generic line reader like Node's `readline`, which also splits on Unicode separators (U+2028) that can occur inside a JSON string payload and would corrupt the stream. Pi's protocol differs from Codex's JSON-RPC in two ways the client relies on: there is **no startup handshake** (the process is usable the moment it spawns — the session comes from the `--model` flag), and agent **events are forwarded verbatim**, keyed by a top-level `type`, with no envelope. Only command _results_ are wrapped as `{ type: 'response', command, success, id? }`, correlated by an optional `id` the client echoes.

**Streaming translation.** Assistant text arrives as `message_update` events whose `assistantMessageEvent.delta` (when its `type` is `text_delta`) is the chunk to append — streamed to the bubble via `SESSION_OUTPUT` with `streaming: true`. Each completed tool action (`tool_execution_end`) emits the same `▸ call` / `← result` marker pair Claude and Codex use, so the shared real-conversation layout folds a Pi turn's intermediate activity exactly the way it folds Claude's. The run settles on a single `agent_end` event → Omniscio finalizes the message (one `SESSION_OUTPUT` with `streaming: false` carrying the full text, REPLACE semantics) and moves the session to **Needs You**.

**Real cost tracking.** Pi computes actual dollar cost from its own pricing table and reports it on every `turn_end` inside `message.usage` (`{ input, output, totalTokens, cost: { total } }`). The manager accumulates per-turn usage across a run and writes the real `costUSD` + token counts to `api_cost_log` via `trackApiCostRaw` — `source: 'pi'`, synthetic account `pi-shared`, model label = the resolved `provider/model` id. This is why Pi sessions show a non-zero Cost line where Codex/Cursor show $0.

**Crash recovery.** A pi process that exits without a graceful stop (crash / external kill) fires the client's exit handler; the manager surfaces a "Pi process exited unexpectedly" system message, sets the session to `error`, and drops it from the map — so a dead Pi session is never stranded `running` (the liveness reconciler skips non-Claude providers). Status transitions, the stall watchdog, the first-response timer, the terminal-status resurrect guard, and the resume-context replay helper are all inherited from `BaseExternalSessionManager` (shared with Codex / OpenClaw).

**Routing.** A `SESSION_LAUNCH` with `provider: 'pi'` resolves `piSessionManager` from the session-manager registry (`getAltSessionManager('pi')`) and goes through the shared gated-launch path — no per-handler branch. The one hand-written Pi branch is the send path in `session-service.ts` (`provider === 'pi'`): it persists the operator row and routes to `piSessionManager.sendMessage` (text-only, no QuestionWidget hint). Interrupt / terminate / restart / change-provider all dispatch through the same registry, so Pi inherits the full lifecycle with no extra wiring. Telemetry: every successful Pi spawn fires the `spawn_non_claude_session` feature event recording `{ provider: 'pi' }` only — no session id, project id, or prompt content.

### Files

- [src/main/services/engines/pi-session-manager.ts](../../src/main/services/engines/pi-session-manager.ts) — multi-turn lifecycle, lazy spawn, real-cost accumulation, crash recovery (singleton `piSessionManager`); extends `BaseExternalSessionManager`
- [src/main/services/engines/pi-rpc-client.ts](../../src/main/services/engines/pi-rpc-client.ts) — the `pi --mode rpc` stdio JSONL client; **first place to look on output drift** (strict `\n`-split framing, event→callback mapping, usage extraction)
- [src/main/services/engines/pi-model-resolver.ts](../../src/main/services/engines/pi-model-resolver.ts) — pure `provider/model` → `--model` arg + env var + reused user key router (plus the keyless local `ollama/<tag>` route)
- [src/main/services/engines/pi-local-home.ts](../../src/main/services/engines/pi-local-home.ts) — the Omniscio-managed local home + static `models.json` writer + the Ollama reachable/pulled pre-flight (`preparePiLocalHome`, reusing Local Chat's `ollama-client`)
- [src/main/services/engines/pi-binary-resolver.ts](../../src/main/services/engines/pi-binary-resolver.ts) — locate `pi` on PATH (cached)
- [src/main/services/providers/main-registry.ts](../../src/main/services/providers/main-registry.ts) — Pi readiness entry (toggle + binary + the model-resolver gate)
- [src/main/services/providers/session-manager-registry.ts](../../src/main/services/providers/session-manager-registry.ts) — `SESSION_MANAGERS.pi = piSessionManager` dispatch
- [src/shared/providers/registry.ts](../../src/shared/providers/registry.ts) — `PROVIDERS.pi` descriptor (`persistent-external`, pickable, pickerOrder 8)
- [src/shared/types/provider-readiness.ts](../../src/shared/types/provider-readiness.ts) (`ProviderId` union), [src/shared/types.ts](../../src/shared/types.ts) (`PI_PROJECT_ID`), [src/shared/types/settings/accounts-providers-settings.ts](../../src/shared/types/settings/accounts-providers-settings.ts) (`allowPiSessionSpawn`), [src/shared/types/settings/ai-features-settings.ts](../../src/shared/types/settings/ai-features-settings.ts) (`piModel`)
- [src/main/services/session/session-service.ts](../../src/main/services/session/session-service.ts) — the one `provider === 'pi'` send branch
- [src/renderer/src/components/ui/PiIcon.tsx](../../src/renderer/src/components/ui/PiIcon.tsx) (π mark), [src/renderer/src/features/sessions/ProviderBadge.tsx](../../src/renderer/src/features/sessions/ProviderBadge.tsx) (icon-only badge; `pi` registered as a monochrome `colored: false` entry), [src/renderer/src/features/sessions/ChangeProviderButton.tsx](../../src/renderer/src/features/sessions/ChangeProviderButton.tsx) (per-launch switcher)
- [src/renderer/src/features/settings/sections/accounts/AccountSettings.tsx](../../src/renderer/src/features/settings/sections/accounts/AccountSettings.tsx) — the Pi (CLI provider) Settings section: allow-toggle, binary note, and the model picker (cloud quick-picks + a live "local (Ollama)" list + Custom box)
- [.claude/memory/contracts/provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md) — the feature contract (9 invariants incl. a-local-model-routes-keyless-in-an-owned-home local-Ollama routing + residual-branch note + known gaps)
- [.claude/memory/contracts/provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md) — the one-click "Set it up for me" flow (toolchain install + key reuse in main + arm-toggle-last) that automates the three gates above

### Comparison with other providers

| Concern             | Pi                                                 | Codex                          | OpenCode                                |
| ------------------- | -------------------------------------------------- | ------------------------------ | --------------------------------------- |
| Auth                | Your provider's own key (env, reused)              | OpenAI API key                 | Anthropic API key (`ANTHROPIC_API_KEY`) |
| Underlying model    | **Any** (Anthropic / OpenAI / Google / OpenRouter) | OpenAI Codex models            | Claude (Sonnet) via your key            |
| Process model       | Long-lived `pi --mode rpc` (JSONL stdio)           | Long-lived JSON-RPC app-server | Shared persistent `opencode serve`      |
| Multi-turn          | Native; stopped-session resume by context-replay   | Persistent process             | Native (HTTP to shared server)          |
| Cost emitted        | **Yes — real $ per turn**                          | Tokens only (records $0)       | Yes — authoritative, to `api_cost_log`  |
| Approval prompts    | No (auto-exec; protocol exists, deferred)          | **Yes** (shared permission UI) | No                                      |
| Readiness gates     | 3 (toggle + binary + model/key resolver)           | 3 (toggle + binary + key)      | 3 (toggle + binary + key)               |
| Per-project default | **Yes**                                            | Yes                            | No (excluded from radios)               |

## Related

### Related

- [codex-provider.md](codex-provider.md) — the closest analog: same `persistent-external` long-lived child + per-project default, but OpenAI-only and with real approval prompts
- [opencode-provider.md](opencode-provider.md) — the closest analog for the any-model credential routing (same `provider/model` → env-var pattern)
- [cursor-provider.md](cursor-provider.md) — another first-class provider (pill + per-project default), but a per-turn one-shot runner
- [ai-providers.md](ai-providers.md) — landing page for the full provider matrix

