Pi
7 min read
Pi is an open-source terminal AI coding agent and minimal agent harness, built by Earendil Inc. The design goal is to keep the core small and let the workflow be adapted through TypeScript extensions, skills, prompt templates, themes and shareable packages — "adapt Pi to your workflows, not the other way around".
sh
curl -fsSL https://pi.dev/install.sh | sh
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi
Everything Pi stores lives under ~/.pi/agent/ globally and .pi/ per project.
On Nix, nixpi builds Pi declaratively (settings, providers, extensions and skills as a programs.pi module).
Philosophy
Pi is deliberately less batteries-included than opencode. MCP support, subagents, plan mode, permission prompts, todo systems and background bash are not core features. They can be built as extensions, installed as Pi packages, or handled externally with tmux, Docker or another sandbox.
This makes Pi a good fit when the goal is to customise the agent harness itself rather than adopt a fixed workflow. It also means a bare install does less out of the box than most competitors.
Modes
| Mode | Invocation | Use |
|---|---|---|
| Interactive | pi | Full TUI |
pi -p "…" | One response, then exit — scripting, pipes | |
| JSON | pi --mode json | Structured event stream |
| RPC | pi --mode rpc | Server over stdin/stdout, for non-Node integrations |
| SDK | — | Embed Pi in a Node.js application |
sh
pi @src/api.ts "why does this 500?" # include files as context
pi -p "explain this" < notes.txt # pipe input
pi -c # continue the last session
pi -r # interactive session picker
pi --model openai/gpt-4o "…" # pick a model for this run
Interactive use
• @ fuzzy-searches project files to reference them in a message.
• !cmd runs a shell command and sends the output; !!cmd runs it silently.
• Images paste with Ctrl+V (Alt+V on Windows) or drag-and-drop into the terminal.
• Shift+Enter for multi-line input, Ctrl+G opens $VISUAL/$EDITOR.
• While the agent is working, Enter queues a steering message and Alt+Enter a follow-up; Escape aborts.
• / opens the command palette — /model, /settings, /tree, /resume, /export, /share, /skill:<name>, plus anything extensions or prompt templates register.
Sessions
Sessions are JSONL files in ~/.pi/agent/sessions/, grouped by working directory, and are stored as a tree: every entry has an id and a parentId, and the current position is the active leaf.
/tree visualises and navigates that history — jump back to any earlier point and keep going, which branches in place without creating a new file. Filters (default / no-tools / user-only / labeled-only / all) and branch labels (Shift+L) keep large trees readable.
• /fork <path|id> — split an earlier prompt out into a separate session
• /clone — copy the active branch into a new session
• /name <name> — give the session a human-readable name
• /export [file] — render the session to HTML
• /share — upload as a private GitHub gist
Entries record messages, model changes, thinking-level changes, labels, compactions and extension data. History auto-compacts as it grows (compaction settings).
Models
15+ providers are supported — Anthropic, OpenAI, Google, Azure, Bedrock, Mistral, Groq, Ollama, OpenRouter and others. Switch mid-session with /model (the model file is re-read each time it opens, so config can be edited live) or cycle favourites with Ctrl+P.
Thinking effort has seven levels — off, minimal, low, medium, high, xhigh, max — and each model declares which it supports via thinkingLevelMap. Custom models and providers go in ~/.pi/agent/models.json, declaring baseUrl, api type and a model list; keyless local servers accept a dummy key.
Context engineering
• AGENTS.md — project instructions, the same convention opencode and others use.
• SYSTEM.md — replaces the system prompt outright.
• Prompt templates — ~/.pi/agent/prompts/*.md (global) or .pi/prompts/*.md (project). The filename becomes the command, so review.md is /review. Optional frontmatter takes description and argument-hint (<required> / [optional]). Bodies substitute $1, $2, $@/$ARGUMENTS, defaults ${1:-fallback} and slices ${@:N:L}. Discovery is non-recursive.
• Skills — SKILL.md files under ~/.pi/agent/skills/, ~/.agents/skills/, .pi/skills/ or .agents/skills/, with name (lowercase/hyphens, ≤64 chars) and description (≤1024 chars) frontmatter. Loading is progressive: only names and descriptions enter the system prompt, and the full body is read on demand — automatically when the task matches, or explicitly via /skill:<name> [args]. The description is what decides selection, so it should be specific.
Extensions
Extensions are TypeScript, auto-discovered from ~/.pi/agent/extensions/*.ts (or a directory with index.ts) and, once the project is trusted, .pi/extensions/*.ts. They can be single files or multi-file packages with their own package.json and dependencies.
ts
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Warning", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
}
});
}
The default export is a factory (sync or async — async ones finish before the session starts, which is how remote model configs get fetched) receiving the ExtensionAPI:
• pi.registerTool() — new tools callable by the model, with Typebox parameter schemas and optional custom rendering
• pi.registerCommand() — slash commands; these run only in interactive mode and get session control the event handlers don't have (ctx.newSession(), ctx.fork(), ctx.switchSession())
• pi.on(event, …) — lifecycle hooks (session_start, before_agent_start, input, tool_call, tool_result, …) that can intercept, block or rewrite behaviour
• pi.registerShortcut() — keybindings
• ctx.ui — notify(), confirm(), select(), input(), setStatus(), setWidget(), custom() for full TUI components
The example above is the point of the design: the permission system other agents ship as a core feature is ~8 lines of extension here.
Packages
A Pi package bundles extensions, skills, prompts and themes for sharing. Declare them under a pi key in package.json, or drop them in the conventional extensions/, skills/, prompts/, themes/ directories and let Pi auto-discover:
json
{
"name": "my-package",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"],
"themes": ["./themes"]
}
}
sh
pi install npm:@scope/pkg@1.2.3 # → ~/.pi/agent/npm/ or .pi/npm/
pi install git:github.com/user/repo@v1 # → ~/.pi/agent/git/<host>/<path>
pi install ./local/path # referenced in place, not copied
Versioned npm specs and pinned git tags/commits stay pinned across updates. The pi-package keyword lists a package in the gallery.
Settings
JSON, two tiers, project merging over global (nested objects merge rather than replace):
| File | Scope |
|---|---|
~/.pi/agent/settings.json | global |
.pi/settings.json | project |
json
{
"defaultProvider": "anthropic",
"defaultModel": "claude-sonnet-4-20250514",
"defaultThinkingLevel": "medium",
"theme": "dark",
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
Other notable keys: hideThinkingBlock, showCacheMissNotices, externalEditor, tuiMode (regular / experimental fullscreen), defaultProjectTrust (ask / always / never), httpProxy, retry.* (with per-provider timeouts), and the resource arrays packages, extensions, skills, prompts, themes (glob patterns, exclusions supported). /settings edits the common ones interactively.
Security
Pi has no built-in sandbox and no permission system. Built-in tools read, write and edit files and run shell commands with the permissions of the pi process, and Pi treats everything writable by that user as one local trust boundary. This is intentional — the maintainers argue a partial sandbox gives a false impression of safety while still depending on host resources.
Project trust is the one guard, and it is narrow: it decides whether Pi loads project-local settings, resources, packages and extensions from .pi/, so a cloned repo can't silently reconfigure the agent before you approve it. It does nothing about untrusted code, untrusted prompts or untrusted model output.
For untrusted repos or unattended runs, isolate the process. The docs describe three patterns:
• Gondolin — a local Linux micro-VM that routes the built-in tools into isolation while Pi itself stays on the host (pi -e ~/.pi/agent/extensions/gondolin)
• Plain Docker — run the whole pi process in a container; simplest option
• OpenShell — a policy-controlled sandbox, local or remote, with credential management
Read-only mounts limit damage, and output should be reviewed before being copied back to a trusted system. Version control remains the practical safety net for reviewing what the agent changed.