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 bundlednpmCLI for extension install/update/publish flows and execute it through Electron's embedded Node runtime instead of relying on systemnode/npmin the GUI environment. - Dev-only desktop automation can opt out of the normal single-instance app lock with
CHATON_ALLOW_AUTOMATION_INSTANCE=1so 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
| Path | Purpose |
|---|---|
settings.json | Pi settings: enabledModels, defaultProvider, defaultModel, theme |
models.json | Provider definitions and model metadata |
auth.json | Provider 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_DIRto this directory
3. Bootstrap Process
ensurePiAgentBootstrapped() in electron/ipc/workspace.ts runs on startup:
- Creates the directory tree if missing
- Writes default
settings.jsonif missing ({ enabledModels: [], defaultProvider: null, defaultModel: null }) - Writes default
models.jsonif missing ({ providers: {} }) - Creates
auth.jsonas{}if missing - Syncs API keys between
models.jsonandauth.json - Migrates
openai-codexbase URL if incorrectly set - Cleans up stale
settings.json.lockfiles (older than 5 minutes) - 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:
- Bundled:
@mariozechner/pi-coding-agent/dist/cli.js(preferred, already innode_modules) - Fallback:
<userData>/.pi/agent/bin/pi
Key functions:
| Function | Purpose |
|---|---|
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
| Tier | Source | Meaning |
|---|---|---|
| All models | models.json | Every model Pi knows about |
| Scoped models | settings.json > enabledModels | Subset the user has starred |
User-Facing Behavior
- Model picker: Shows scoped models by default. "More" reveals all models with a filter.
- Star button: Updates
enabledModelsin 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:
- The main conversation runtime decides to delegate.
- ACP records a typed task/status/result envelope.
PiSessionRuntimeManagerstarts or updates a runtime-backed subagent.- The subagent still uses the normal Pi tools and model path.
- The renderer rehydrates ACP state from SQLite and streams live updates over IPC.
Working Directory Selection
- Conversation worktree path (if worktree is enabled)
- Project repository path (if conversation is project-linked)
- 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
HarnessCandidatecontrols 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 Snapshotsection - 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
| Mode | Tool cwd | Behavior |
|---|---|---|
secure | Conversation working directory | Tools 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:
- Create a hidden conversation row
- Start a Pi runtime for that conversation
- Optionally send
set_model - Send a
prompt - Read the assistant reply from the runtime snapshot
- 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
| Check | Method |
|---|---|
| Binary installed | command -v ollama (Unix) / where ollama (Windows) |
| API running | GET http://127.0.0.1:11434/api/tags |
| Default base URL | http://localhost:11434/v1 |
LM Studio
| Check | Method |
|---|---|
| App installed | Checks platform-specific paths (see below) |
| API running | GET http://127.0.0.1:1234/v1/models |
| Default base URL | http://localhost:1234/v1 |
LM Studio app paths:
- macOS:
/Applications/LM Studio.app - Windows:
%LOCALAPPDATA%\Programs\LM Studio - Linux:
~/LM-Studioor~/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
| Provider | Flow | How It Works |
|---|---|---|
ChatGPT (openai-codex) | PKCE + local server | Pi SDK starts HTTP server on port 1455; browser callback is automatic |
Claude Pro (anthropic) | PKCE, user pastes code | User copies authorization code from claude.ai |
GitHub Copilot (github-copilot) | Device flow | User enters code on github.com; polling completes automatically |
IPC Channels
| Channel | Direction | Purpose |
|---|---|---|
pi:oauthLogin (invoke) | renderer -> main | Start OAuth flow |
pi:oauthEvent (send) | main -> renderer | Flow events: auth, prompt, progress, success, error |
pi:oauthPromptReply (send) | renderer -> main | User's answer to interactive prompt |
pi:oauthPromptCancel (send) | renderer -> main | User cancelled prompt |
pi:oauthLoginCancel (send) | renderer -> main | Abort entire login flow |
pi:getAuthJson (invoke) | renderer -> main | Read 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
.lockfiles 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:
| File | What It Controls |
|---|---|
electron/ipc/workspace.ts | Bootstrap, CLI resolution, provider sync, base URL normalization |
electron/pi-sdk-runtime.ts | Session creation, access mode, prompt composition, event bridge, lock handling |
electron/ipc/workspace-handlers.ts | IPC handler registration for all workspace operations |
electron/ipc/pi.ts | OAuth 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 (
securevsopen) - Pi runtime events bridged to renderer
- Pi as the backend for skills management
- Credential sync between
models.jsonandauth.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