Pi Integration in Chatons

This page documents how Chatons uses the Pi Coding Agent SDK. Verified against electron/ipc/workspace.ts, electron/pi-sdk-runtime.ts, and electron/main.ts as of March 10, 2026.

Related: Developer Guide, User Guide


1. What Pi Does in Chatons

Pi is responsible for:

  • Model and provider handling (registry, credentials, API compatibility)
  • Per-conversation session creation and management
  • Tool execution inside conversations (read, write, edit, bash)
  • Skills commands (install, uninstall, list)
  • Parts of the settings and diagnostics workflow

Chatons wraps Pi with:

  • Electron IPC layer for cross-process communication
  • Per-conversation runtime lifecycle management
  • SQLite database for app-level persistence
  • React UI and workspace state
  • Extension runtime for third-party integrations, including sidebar, main-view, quick-action, and topbar UI contributions, plus compact topbar widget hosting with standardized context delivery for richer extension controls. Main-view and widget HTML now support packaged JS/CSS/assets resolved from chaton-extension://<extension-id>/..., which allows framework-built UIs from npm-installed extension packages and bundled React widgets used by builtins such as Automation, Memory, IDE Launcher, and TPS Monitor. Packaged desktop builds also ship a bundled npm CLI for extension install/update/publish flows and execute it through Electron's embedded Node runtime instead of relying on system node/npm in the GUI environment.
  • Dev-only desktop automation can opt out of the normal single-instance app lock with CHATON_ALLOW_AUTOMATION_INSTANCE=1 so a QA harness can launch a second real Electron window against the same development build.

Cloud projects are the main exception to this local-runtime architecture. They do not create or use a local Pi runtime. In cloud mode, the desktop app is only a client for a remote Chatons runtime hosted by the cloud service, and provider credentials remain organization-owned in the cloud. runtime-headless now materializes a managed Pi agent directory from the cloud control-plane grant and serves a real Pi session remotely, rather than a synthetic reply path. For repository-backed cloud projects, the remote runtime now keeps a managed project source checkout and creates a dedicated Git worktree per conversation instead of cloning a separate repository per session. Cloud access is now gated by a cloud subscription on the authenticated user account, and the chosen tier limits how many remote cloud sessions that user may run in parallel. That quota is derived server-side by cloud-api; the desktop no longer sends or decides the effective parallel-session limit. The cloud control plane also owns cloud project, conversation, and transcript state. The desktop may cache that state locally for rendering, but the bootstrap API and cloud conversation message endpoints remain the server-side source of truth for cloud threads. Cloud memory now follows the same ownership model. Durable structured memory records live in cloud-api, and runtime-headless exposes memory tools by forwarding to authenticated control-plane endpoints instead of keeping a separate local memory store. The web onboarding flow at cloud.chatons.ai follows the same ownership model: browser signup/login and organization/provider setup are only presentation flows around cloud-api, and provider secrets entered there remain cloud-owned instead of being copied into the desktop Pi environment. When that web onboarding needs to hand off into the desktop app, it should open chatons://cloud/connect?base_url=.... That link is only a desktop launch trigger; the desktop must start its own OIDC flow from there and only complete cloud auth after receiving the real chatons://cloud/auth/callback deep link. That OIDC flow now depends on the browser already having a valid Chatons Cloud web session. If the user is not signed in on the web, cloud-api redirects /oidc/authorize to the normal /cloud/login or /cloud/signup experience with a return_to link back to the exact authorize request. In hosted deployments where cloud.chatons.ai routes directly to cloud-api, the control plane must also serve lightweight fallback GET pages for /cloud/login and /cloud/signup so the redirect target exists even without the separate landing app in front of it. The authorize page itself is a consent screen that shows the signed-in account and asks the user to approve or deny desktop access; it no longer asks for freeform email or display-name input. For the hosted landing deployment, browser auth/bootstrap requests should default to https://cloud.chatons.ai unless a specific VITE_CHATONS_CLOUD_API_URL override is provided for another environment. In the default hosted Kubernetes setup, cloud-api should also be configured with CHATONS_CLOUD_PUBLIC_URL=https://cloud.chatons.ai; api.chatons.ai can remain a direct alias to the same service, while websocket traffic stays on realtime.chatons.ai. cloud-api should also publish explicit cloud service endpoints to clients: the canonical API base URL, websocket base URL, and runtime service base URL. Desktop Chatons should prefer those published endpoints over guessing sibling ports from the API base URL. Desktop cloud sessions also now refresh through the same /oidc/token surface before expiry, using the stored desktop refresh token. If refresh fails, the desktop should demote the instance back to a reconnect-required state instead of continuing to issue stale cloud requests. The same applies to account recovery: email verification and password reset are cloud-side flows backed by SMTP delivery from cloud-api, not by any desktop Pi configuration. Admin-granted complimentary subscriptions are also a control-plane concern. If an admin assigns a temporary or unlimited plan override to a user, that override becomes the effective cloud quota source until it expires, and the desktop must treat the cloud account payload as authoritative.


2. Managed Pi Directory

Chatons keeps its own Pi directory, separate from any user-global ~/.pi install:

<userData>/.pi/agent/

Where <userData> is <appData>/Chatons (overridden from Electron's default in main.ts).

Directory Structure

PathPurpose
settings.jsonPi settings: enabledModels, defaultProvider, defaultModel, theme
models.jsonProvider definitions and model metadata
auth.jsonProvider credentials (API keys and OAuth tokens)
sessions/Pi session state files
worktrees/chaton/Git worktree storage
bin/Fallback Pi binary location

Why Not the User's Global Pi?

  • App behavior is deterministic and isolated
  • Credentials are app-scoped
  • Model scope is app-scoped
  • Multiple Chatons versions or Pi-based apps can coexist
  • All CLI executions force PI_CODING_AGENT_DIR to this directory

3. Bootstrap Process

ensurePiAgentBootstrapped() in electron/ipc/workspace.ts runs on startup:

  1. Creates the directory tree if missing
  2. Writes default settings.json if missing ({ enabledModels: [], defaultProvider: null, defaultModel: null })
  3. Writes default models.json if missing ({ providers: {} })
  4. Creates auth.json as {} if missing
  5. Syncs API keys between models.json and auth.json
  6. Migrates openai-codex base URL if incorrectly set
  7. Cleans up stale settings.json.lock files (older than 5 minutes)
  8. Creates the global workspace at <userData>/workspace/global

This bootstrap remains authoritative only for local projects and local conversations. Connected cloud instances instead hydrate desktop cloud state from an authenticated cloud bootstrap API using the stored desktop session.


4. Configuration Files

settings.json

{
  "enabledModels": ["openai/gpt-4o", "anthropic/claude-3.5-sonnet"],
  "defaultProvider": "openai",
  "defaultModel": "openai/gpt-4o",
  "theme": "dark"
}

enabledModels is the single source of truth for scoped model selection. When a user stars/unstars a model in the UI, this array is updated in the actual file, not just in UI state.

models.json

{
  "providers": {
    "openai": {
      "name": "OpenAI",
      "baseUrl": "https://api.openai.com/v1",
      "models": [
        { "id": "gpt-4o", "maxTokens": 4096 }
      ]
    }
  }
}

This is the registry Pi uses to discover available models. Chatons reads it to populate model lists.

Provider model entries remain in models.json even when API keys are moved into auth.json. Credential migration must not strip models, otherwise custom providers can appear as 0 models despite a valid auth.json entry. Separately, Pi's own ModelRegistry still validates custom providers with explicit models as if they require an apiKey field in models.json. Chatons therefore preserves Pi-compatible provider entries even when the canonical credential is stored in auth.json, so packaged runs do not drop every custom provider after reload.

auth.json

{
  "openai": {
    "type": "api-key",
    "key": "sk-..."
  },
  "openai-codex": {
    "type": "oauth",
    "access": "...",
    "refresh": "...",
    "expires": 1234567890
  }
}

Credentials are stored here. Chatons synchronizes credentials between models.json and auth.json for compatibility (both directions). Known local no-auth providers such as LM Studio, Ollama, local, and localhost are excluded from API-key backfill and have stale api_key entries removed during sync so runtime sessions can omit Authorization headers when the backend permits keyless local access. When those providers also define explicit models entries, Chatons can keep apiKey: "!" in models.json as a schema-compatibility sentinel for Pi. Runtime resolution must preserve that bare sentinel as a literal placeholder rather than treating it as a shell-command config value, otherwise Pi loses the fallback credential path and local model selection fails even though the provider is intentionally keyless.


5. CLI Execution

For Pi CLI commands (model sync, skill management), Chatons resolves the CLI path:

  1. Bundled: @mariozechner/pi-coding-agent/dist/cli.js (preferred, already in node_modules)
  2. Fallback: <userData>/.pi/agent/bin/pi

Key functions:

FunctionPurpose
getBundledPiCliPath()Returns path to bundled CLI or null
getPiBinaryPath()Returns preferred CLI path (bundled or fallback)
runPiExec(args, timeout)Executes Pi with given args, forces managed Pi directory

All executions run with PI_CODING_AGENT_DIR=<userData>/.pi/agent.


6. Model Scope

Two Tiers

TierSourceMeaning
All modelsmodels.jsonEvery model Pi knows about
Scoped modelssettings.json > enabledModelsSubset the user has starred

User-Facing Behavior

  • Model picker: Shows scoped models by default. "More" reveals all models with a filter.
  • Star button: Updates enabledModels in the real Pi settings file.
  • Consistent everywhere: Same behavior in onboarding, composer, and settings.
  • New conversation startup model: When a user creates a new conversation from UI entry points that do not explicitly pass a model, Chatons reuses the current saved composer model selection so the conversation record and the first Pi session start with the same provider/model pair.
  • Runtime wins over stale picker state: Once a conversation exists, the composer model chip must reflect the conversation/runtime model if one is already set, rather than continuing to show an older draft-only picker selection.
  • Startup validation: If no models are scoped and no providers exist, onboarding is shown.

Provider Cards

Grouped by company in onboarding and settings:

  • OpenAI group: "OpenAI" (API key, openai-completions, api.openai.com/v1) and "ChatGPT" (OAuth, openai-codex-responses, chatgpt.com/backend-api)
  • Mistral group: "Mistral" (api.mistral.ai/v1) and "Mistral Vibe" (vibe.mistral.ai/v1)

Each variant maps to its own Pi provider entry.

Base URL Probing

When Chatons resolves provider base URLs, it probes URL variants and prefers endpoints that are compatible with generation (/chat/completions or /responses) in addition to /models.

This prevents selecting a URL that passes onboarding checks but fails at conversation runtime with errors like 404 Cannot POST /chat/completions.

For custom local-network HTTP providers, Chatons performs these probes and model-list fetches through a dedicated Node http/https client in the main process. This avoids packaged-app inconsistencies where Electron runtime fetch can behave differently from development or terminal runs.

When an upstream provider SDK throws a generic transport failure like Connection error. without an HTTP status, Chatons now inspects the nested SDK error cause before surfacing the assistant failure. This preserves useful diagnostics such as DNS failures, refused connections, TLS errors, and timeouts when the provider client exposes them.

GitHub Copilot conversations are now configured against Pi's anthropic-messages transport instead of openai-completions. Although Copilot exposes an OpenAI-like base URL, Chatons verified that Copilot Claude models accept Anthropic-style message payloads reliably while the OpenAI-compatible chat-completions path can still fail with 400 Bad Request on minimal prompts. Chatons therefore keeps the Copilot auth headers and base URL, but routes Copilot through the Anthropic request shaper for durable runtime compatibility.


7. Per-Conversation Sessions

PiSessionRuntimeManager in electron/pi-sdk-runtime.ts manages one PiSdkRuntime per conversation.

ACP Over Pi

Chatons now treats ACP as an internal orchestration layer above Pi instead of a user-facing replacement runtime.

  • Pi still owns model/provider resolution, credentials, sessions, and developer tools.
  • ACP is the typed envelope used when internal specialists delegate to each other.
  • Runtime-backed subagents remain Pi sessions, but Chatons now persists ACP messages, agent status, and task-list history in SQLite so orchestration survives reloads and remains auditable.
  • The renderer can show planner/coder/reviewer style progress in the side panel while keeping the main user experience as one conversation thread.

For local coding sessions, this means the practical execution stack is:

  1. The main conversation runtime decides to delegate.
  2. ACP records a typed task/status/result envelope.
  3. PiSessionRuntimeManager starts or updates a runtime-backed subagent.
  4. The subagent still uses the normal Pi tools and model path.
  5. The renderer rehydrates ACP state from SQLite and streams live updates over IPC.

Working Directory Selection

  1. Conversation worktree path (if worktree is enabled)
  2. Project repository path (if conversation is project-linked)
  3. Global workspace directory (<userData>/workspace/global)

Meta-Harness Layer

Chatons now has a first-phase Meta-Harness layer around the normal Pi runtime. This is implemented as an outer-loop runtime wrapper in electron/pi-sdk-runtime.ts, not as a second conversational persona and not as a change to the underlying provider/model stack.

The current implementation is intentionally narrow and typed:

  • a HarnessCandidate controls bounded prompt additions, environment bootstrap, tool-discovery posture, subagent posture, tool permissions, hook behavior, and scoring objectives
  • the active candidate is loaded from the managed Pi archive under <userData>/.pi/agent/meta-harness/
  • before the first model turn, the runtime may gather a short environment snapshot from the workspace or tool cwd
  • when a snapshot is captured successfully, Chatons injects it into the initial system prompt as an additive Environment Snapshot section
  • startup prompt text, candidate metadata, scores, and evaluation traces are archived under the managed Pi directory for later comparison

The first production milestone mirrors the paper's environment-bootstrap pattern: a short, best-effort probe with a timeout and silent failure behavior. If the snapshot fails, runtime startup continues normally.

The harness layer now also maps its tool permissions and hook policy onto Pi's native tool-call primitives. That means allow/deny decisions and post-failure annotations are enforced at runtime through beforeToolCall / afterToolCall, rather than living only as prompt text.

Chatons now also supports a hybrid human-in-the-loop harness mode. When the setting is enabled, new local conversations record which harness candidate was active at startup, the topbar shows a clickable badge for the active harness, and the thread footer offers thumbs-up / thumbs-down feedback after the agent has replied. That user feedback is stored over time in SQLite, surfaced in the Meta-Harness panel, and folded back into optimizer scoring as a bounded human-feedback bonus rather than replacing benchmark signals.

Worktree enablement is now strict: Chatons only persists a conversation worktree after the created directory is confirmed to be a valid Git worktree/repository. Failed worktree creation is cleaned up instead of leaving an empty directory in the conversation runtime path.

Access Mode

ModeTool cwdBehavior
secureConversation working directoryTools restricted to project scope
open/ (Unix) or drive root (Windows)Full filesystem access

The runtime exposes get_access_mode so the model can check the live mode during a session. When the user switches access mode on an existing local conversation, Chatons also sends a technical system steer describing the new mode so the agent can adapt immediately after the runtime restarts. That steer is intentionally hidden from the visible transcript and excluded from the persisted conversation message cache shown back to the user.

Prompt Injection

At session creation, Chatons appends system prompt guidance about:

  • Access mode constraints and behavior
  • Thread action suggestion format
  • How to explain secure-mode limitations to users
  • Available extension tools (prompt snippets and guidelines from extension manifests)
  • Channel-specific delivery constraints for channel-owned conversations

For the local @thibautrey/chatons-channel-even-realities channel, this includes explicit runtime guidance that the assistant is replying for smart glasses and should default to concise, fast answers suited to a limited display. The glasses transport may send a model field, but Chatons ignores it and still uses the conversation's configured model like any other channel thread.

Chatons no longer injects retrieved memories automatically at conversation start. Instead, memory stays available as runtime tools such as memory.search, memory.get, memory.markUsed, and memory.stats, and the model can query it when the current task would genuinely benefit from recalled context.

This keeps the initial prompt clean and reduces the chance that stale memory is mistaken for an instruction. Memory results should be treated as fallible background context and verified against the current user request, repository state, and direct tool evidence.

Hidden Ephemeral Sessions

Some internal LLM features reuse the same Pi runtime path through short-lived hidden conversations instead of calling the Pi CLI directly.

This is used for:

  • Structured memory capture and maintenance
  • AI conversation-title refinement
  • Channel message ingestion subagents used by channel extensions

Memory capture is now structured rather than summary-only. Completed conversations enqueue a capture checkpoint, hidden Pi sessions extract typed memories such as decisions, preferences, facts, profile notes, repo conventions, and task state, and the runtime only keeps a fallback summary when no higher-signal structured item covers the same topic.

For AI conversation-title refinement, Chatons now strips model-emitted reasoning blocks such as <think>...</think> or <thinking>...</thinking> before validating the generated title. This keeps reasoning-capable models from being rejected solely because they exposed internal thinking text ahead of an otherwise valid short title.


8. Worktree Cleanup and Safety

Conversation worktrees under <userData>/.pi/agent/worktrees/chaton/ are treated as disposable only when they are both:

  • no longer referenced by any conversation
  • free of staged or unstaged changes

When native Git is available, Chatons now removes conversation worktrees through git worktree remove --force instead of deleting the directory directly. This keeps the main repository's worktree metadata consistent and avoids orphaned entries in .git/worktrees/.

Startup cleanup also inspects the actual directory entries under the Chatons worktree root instead of relying on an extra nested marker folder. This matches the real layout created by git worktree add.

The flow is the same as a normal runtime-backed conversation:

  1. Create a hidden conversation row
  2. Start a Pi runtime for that conversation
  3. Optionally send set_model
  4. Send a prompt
  5. Read the assistant reply from the runtime snapshot
  6. Stop the runtime

This avoids drift between packaged-app runtime behavior and CLI behavior, especially for auth-sensitive providers.

Channel Subagents

The runtime manager also supports ephemeral subagents for channel message ingestion, keyed by conversation ID. These are separate from user-facing conversation runtimes.

Runtime-backed coding subagents also now participate in ACP. Their registration, status, result, and task-list changes are persisted as ACP state and broadcast to the renderer through chaton:acp:event.


8. Local Provider Detection

Chatons can detect locally running providers during onboarding and provider setup.

Ollama

CheckMethod
Binary installedcommand -v ollama (Unix) / where ollama (Windows)
API runningGET http://127.0.0.1:11434/api/tags
Default base URLhttp://localhost:11434/v1

LM Studio

CheckMethod
App installedChecks platform-specific paths (see below)
API runningGET http://127.0.0.1:1234/v1/models
Default base URLhttp://localhost:1234/v1

LM Studio app paths:

  • macOS: /Applications/LM Studio.app
  • Windows: %LOCALAPPDATA%\Programs\LM Studio
  • Linux: ~/LM-Studio or ~/Applications/LM-Studio

VS Code

Detected via which code (macOS/Linux) or where code (Windows). Used for the worktree "Open in VS Code" integration, not for provider setup.


9. Event Bridge

Pi runtime events are forwarded to the renderer via the pi:event IPC channel and broadcast to all browser windows.

Events include:

  • Message lifecycle (create, update, complete)
  • Tool execution lifecycle
  • Compaction events
  • Retry events
  • Extension UI requests (requirement sheets, etc.)
  • Runtime status and error events

Events are also routed through the logging pipeline with source: "pi".


10. OAuth Authentication

Supported Flows

ProviderFlowHow It Works
ChatGPT (openai-codex)PKCE + local serverPi SDK starts HTTP server on port 1455; browser callback is automatic
Claude Pro (anthropic)PKCE, user pastes codeUser copies authorization code from claude.ai
GitHub Copilot (github-copilot)Device flowUser enters code on github.com; polling completes automatically

IPC Channels

ChannelDirectionPurpose
pi:oauthLogin (invoke)renderer -> mainStart OAuth flow
pi:oauthEvent (send)main -> rendererFlow events: auth, prompt, progress, success, error
pi:oauthPromptReply (send)renderer -> mainUser's answer to interactive prompt
pi:oauthPromptCancel (send)renderer -> mainUser cancelled prompt
pi:oauthLoginCancel (send)renderer -> mainAbort entire login flow
pi:getAuthJson (invoke)renderer -> mainRead auth.json for status display

Limitation

OAuth tokens are not auto-refreshed when they expire. If a token expires during use, the conversation fails. The user must re-authenticate via Settings > Providers & Models.


11. Settings Lock Robustness

Pi uses settings.json.lock to prevent concurrent modifications.

Chatons implements:

  • Stale lock cleanup: Removes .lock files older than 5 minutes on startup
  • Retry with backoff: SettingsManager.create() retried up to 3 times with exponential backoff (100ms, 200ms, 400ms)

12. Skills Integration

Skills are managed through Pi CLI commands, not through the Chatons extension runtime.

The Skills panel uses runPiExec() to:

  • List installed skills
  • Search available skills
  • Install skills
  • Uninstall skills

This is one of the clearest examples of the distinction between Pi-managed skills and Chatons-managed extensions.


13. Diagnostics

Settings > Diagnostics surfaces:

  • Pi binary path
  • Settings file path
  • Models file path
  • Runtime validation checks

Manual Verification

# Check the managed Pi directory
ls -la ~/Library/Application\ Support/Chatons/.pi/agent/

# Inspect model scope
cat ~/Library/Application\ Support/Chatons/.pi/agent/settings.json | grep enabledModels

# Check available models
cat ~/Library/Application\ Support/Chatons/.pi/agent/models.json | head -50

# Validate JSON
jq . ~/Library/Application\ Support/Chatons/.pi/agent/settings.json > /dev/null

14. Source Files

The authoritative source for Pi integration lives in:

FileWhat It Controls
electron/ipc/workspace.tsBootstrap, CLI resolution, provider sync, base URL normalization
electron/pi-sdk-runtime.tsSession creation, access mode, prompt composition, event bridge, lock handling
electron/ipc/workspace-handlers.tsIPC handler registration for all workspace operations
electron/ipc/pi.tsOAuth IPC handlers

15. What Is Safe to Rely On

These behaviors are clearly implemented and stable:

  • Internal managed Pi directory at <userData>/.pi/agent
  • Model scope driven by settings.json > enabledModels
  • Per-conversation Pi sessions with conversation-specific cwd
  • Access mode controlling tool cwd (secure vs open)
  • Pi runtime events bridged to renderer
  • Pi as the backend for skills management
  • Credential sync between models.json and auth.json

What to Document Carefully

  • OAuth tokens are not auto-refreshed
  • The project terminal is a separate host command runner, not Pi tool execution
  • Global shell Pi setup is not the source of truth for the desktop app
  • Extension hot-reload in the same process is not guaranteed

On this page