Pi Coding Agent
Core Metadata
TypeScript
Extensions are TypeScript modules "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput" linux, macos, windows
`~/Library/Application Support/Code/User/keybindings.json` `~/.config/Code/User/keybindings.json` # Windows Setup Edit
str_replace
const editSchema = Type.Object({ Edit a file by replacing exact text. Anchored by matching oldText in file content: exact match first, then fuzzy-normalized fallback; replacement must be unique.
Find the old text using fuzzy matching (tries exact match first, then fuzzy) Find oldText in content, trying exact match first, then fuzzy match. if (occurrences > 1) { Rejects on missing match, non-unique match, or no-op replacement; applies single replacement and writes full file (no partial merge).
if (!matchResult.found) if (occurrences > 1) if (baseContent === newContent) await ops.writeFile(absolutePath, finalContent); No edit-specific auto-retry loop in harness code; edit errors are surfaced to the agent as tool failures.
reject(error); } catch (e) { - file_not_found
- old_text_not_found
- non_unique_old_text
- no_effect_change
- aborted
File not found: ${path} Could not find the exact text in ${path}. Found ${occurrences} occurrences of the text No changes made to ${path}. reject(new Error("Operation aborted")); Tools
read, write, edit, bash, grep, find, ls, extension_tools
Available built-in tools: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls` **Custom tools** - Register tools the LLM can call via `pi.registerTool()` TypeBox JSON-schema-like parameter objects, validated before tool execution.
export interface Tool<TParameters extends TSchema = TSchema> const editSchema = Type.Object({ const validatedArgs = validateToolArguments(tool, toolCall); No built-in permission popup/approval sandbox; bash runs in configured cwd, and file tools resolve relative-to-cwd or absolute paths.
No permission popups. if (isAbsolute(expanded)) { Config
- path: ~/.pi/agent/settings.json scope: global precedence: lower
- path: .pi/settings.json scope: project precedence: higher
- path: ~/.pi/agent (base dir, overridable via env) scope: global precedence: base
`~/.pi/agent/settings.json` | Global (all projects) `.pi/settings.json` | Project (current directory) project/overrides take precedence const envDir = process.env[ENV_AGENT_DIR]; - name: PI_CODING_AGENT_DIR meaning: Override agent config directory (default ~/.pi/agent)
- name: PI_PACKAGE_DIR meaning: Override package asset directory
- name: PI_SKIP_VERSION_CHECK meaning: Skip startup version check
- name: PI_CACHE_RETENTION meaning: Configure prompt cache retention policy
- name: VISUAL meaning: External editor selection
- name: EDITOR meaning: External editor selection fallback
- name: Provider API key vars meaning: Provider-specific env vars (e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY)
`PI_CODING_AGENT_DIR` | Override config directory `PI_PACKAGE_DIR` | Override package directory `PI_CACHE_RETENTION` `VISUAL`, `EDITOR` const envMap: Record<string, string> = { Extensions
true
Skills are self-contained capability packages ~/.pi/agent/skills/, .pi/skills/, package skills/ or pi.skills in package.json, settings skills[] paths, --skill <path>
Pi loads skills from: CLI: `--skill <path>` Markdown SKILL.md with frontmatter (Agent Skills spec).
A skill is a directory with a `SKILL.md` file. ### SKILL.md Format TypeScript extension API plus pi package ecosystem; extensions can register tools/commands/flags/providers and intercept tool calls/results.
Extensions are TypeScript modules that extend pi's behavior. registerTool(tool: ToolDefinition): void Pi packages bundle extensions, skills, prompt templates, and themes No built-in MCP in core harness; MCP integration is expected via extensions.
No MCP. MCP server integration Providers
amazon-bedrock, anthropic, google, google-gemini-cli, google-antigravity, google-vertex, openai, azure-openai-responses, openai-codex, github-copilot, xai, groq, cerebras, openrouter, vercel-ai-gateway, zai, mistral, minimax, minimax-cn, huggingface, opencode, kimi-coding
export type KnownProvider = Supports OAuth login and API keys. Resolution order: --api-key runtime override, auth.json entry, environment variable, then models.json fallback resolver.
Use `/login` in interactive mode 1. CLI `--api-key` flag Priority: Interactive `/model` command and CLI flags (`--provider`, `--model`); defaults and cycling are configurable in settings.
select any model from that provider via `/model` `--provider <name>` `--model <pattern>` Ux
CLI/TUI interactive interface, plus non-interactive print, JSON stream, RPC, and SDK embeddings.
Pi runs in four modes: interactive, print or JSON, RPC for process integration, and an SDK for embedding in your own apps. (default) | Interactive mode Yes. Assistant/token/tool events stream; JSON mode emits event stream lines.
Outputs all session events as JSON lines to stdout. session.subscribe((event) => { No built-in permission popup approvals in core; confirmation gates can be added by extensions.
No permission popups. pi.on("tool_call", async (event, ctx) => { Sessions persist as JSONL trees under ~/.pi/agent/sessions (unless --no-session/in-memory mode).
Sessions are stored as JSONL files with a tree structure. Sessions auto-save to `~/.pi/agent/sessions/` Compute the default session directory for a cwd. Context
Auto-compaction summarizes old context when threshold exceeded; manual /compact is also supported; settings control reserve/keep token budgets.
Compaction | Context exceeds threshold, or `/compact` contextTokens > contextWindow - reserveTokens return this.settings.compaction?.reserveTokens ?? 16384; On context overflow, pi removes the overflow error message from active context, auto-compacts, then retries the agent run.
if (sameModel && !errorIsFromBeforeCompaction && isContextOverflow(assistantMessage, contextWindow)) { await this._runAutoCompaction("overflow", true); this.agent.continue().catch(() => {}); Reliability
Built-in transient-error retry with exponential backoff; configurable (`retry.enabled`, `maxRetries`, delays) and disable-able.
Match: overloaded_error, rate limit, 429, 500, 502, 503, 504 const delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1); `retry.enabled` | boolean | `true` Two recovery loops: (1) overflow-triggered auto-compaction + continue; (2) transient-error auto-retry loop. Both emit start/end events.
Check auto-retry and auto-compaction after agent completes this._emit({ type: "auto_compaction_start", reason }); type: "auto_retry_start" Integration
Supported TypeScript/Node SDK in @mariozechner/pi-coding-agent (createAgentSession/AgentSession API).
# SDK import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } npm install @mariozechner/pi-coding-agent interactive, print, json_event_stream, rpc, sdk_embedding
Pi runs in four modes: interactive, print or JSON, RPC for process integration, and an SDK for embedding in your own apps. `--mode json` | Output all events as JSON lines `--mode rpc` | RPC mode for process integration JSON lines protocol over stdio. RPC commands on stdin, responses/events on stdout; JSON mode streams AgentSession events as JSONL.
**Commands**: JSON objects sent to stdin, one per line **Events**: Agent events streamed to stdout as JSON lines Each line is a JSON object. Customization
System prompt can be replaced by SYSTEM.md (project over global), appended via APPEND_SYSTEM.md, and overridden/appended by CLI flags.
Replace the default system prompt with `.pi/SYSTEM.md` (project) or `~/.pi/agent/SYSTEM.md` (global). const projectPath = join(this.cwd, CONFIG_DIR_NAME, "SYSTEM.md") `--system-prompt <text>` | Replace default prompt Markdown prompt files loaded from global/project/package/settings/CLI paths, invoked as slash commands with argument substitution.
Global: `~/.pi/agent/prompts/*.md` ## Format export function substituteArgs(content: string, args: string[]): string Customizable via JSON at ~/.pi/agent/keybindings.json; user config overrides defaults.
customized via `~/.pi/agent/keybindings.json` User config overrides defaults. const configPath = join(agentDir, "keybindings.json") Themes are JSON files loaded from built-ins, global/project dirs, packages, settings, or CLI paths; active custom theme hot-reloads.
Themes are JSON files Pi loads themes from: pi reloads it automatically Distribution
npm package: @mariozechner/pi-coding-agent, [object Object], optional compiled binary via bun build --compile
npm install -g @mariozechner/pi-coding-agent "bin": { "build:binary": "npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets", Packages
global npm install root and ~/.pi/agent/git/<host>/<path>, project-local .pi/npm/node_modules and .pi/git/<host>/<path>, temporary extension installs under tmpdir for one-off -e/--extension
Use `-l` to write to project settings (`.pi/settings.json`) instead. - Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project). return join(this.cwd, CONFIG_DIR_NAME, "npm", "node_modules", source.name); return join(this.agentDir, "git", source.host, source.path); Extras
Pi has explicit steering vs follow-up queue semantics and modes.
- **Enter** queues a *steering* message, delivered after current tool execution (interrupts remaining tools) async steer(text: string, images?: ImageContent[]): Promise<void> { async followUp(text: string, images?: ImageContent[]): Promise<void> { RPC mode maps extension UI calls into protocol messages; some UI capabilities are unavailable in RPC.
* Create an extension UI context that uses the RPC protocol. // Working message not supported in RPC mode - requires TUI loader access // Custom UI not supported in RPC mode Pi supports include/exclude and exact force include/exclude patterns (`!`, `+`, `-`) for package resources.
`+path` and `-path` are exact paths relative to the package root. * Apply patterns to paths and return a Set of enabled paths. function applyPatterns(allPaths: string[], patterns: string[], baseDir: string): Set<string> {