Pi: The Anti-Claude-Code — a Coding Agent That Ships With Almost Nothing
Most AI coding tools compete by adding features: sub-agents, plan mode, to-do lists, permission pop-ups, plugin protocols. Pi competes by leaving them out.
That sounds like a weakness. It's the entire point. Pi is a coding agent that lives in your terminal — you type what you want in plain English, and an AI model reads your files, writes code, and runs commands to do it. But instead of baking in every feature, Pi ships a tiny core and lets you reshape almost everything with a short TypeScript file. Its own tagline says it best:
"Adapt Pi to your workflows, not the other way around, without having to fork and modify Pi internals."
This post is for developers who've maybe used something like Claude Code and want to understand a fundamentally different design. By the end you'll know what Pi is, how it's built, how it compares to Claude Code, and the one security fact you must understand before you run it. No prior experience with AI agents needed.
New to how agentic coding tools work under the hood? Our Claude Code Architecture Deep Dive covers the batteries-included side of this story — Pi is the opposite bet.
First, a Tiny Vocabulary#
Only the terms you need to follow along. Skim if you already know them.
| Term | Plain meaning |
|---|---|
| LLM | The AI "brain" (Claude, GPT, Gemini) — text in, text out. |
| Agent | An LLM wired up to act in a loop: call a tool, see the result, decide the next move — instead of just replying once. |
| Harness | The program around the LLM that runs the loop, manages tools, saves history, and draws the UI. Pi is a harness. |
| Tool | A function the model is allowed to call — read a file, bash a command. The model asks; the harness runs it and hands back the result. |
| Context window | Everything the model sees for one request, measured in tokens. It's finite. |
| Extension | A short TypeScript file you write to add a tool, command, UI, or hook to Pi. This is how Pi grows. |
What a Coding Agent Actually Does#
A plain chatbot replies to your message once and stops. An agent does more — it acts in a loop:
The model does the thinking — deciding which tool to call next. Pi is the machinery that makes the loop happen: it hands the model a set of tools, runs the ones it asks for, feeds the results back, saves the whole conversation, and draws the terminal UI. That's what "harness" means.
Why Pi Exists#
Most agents bake in a fixed, opinionated feature set. That's convenient — until your workflow needs something the authors didn't anticipate. Then your only real option is to fork the tool and hack its internals, which is painful and miserable to maintain.
Pi takes the opposite bet: keep the core minimal, and make everything else an extension point. The clearest window into how the project thinks is the list of features it deliberately leaves out of the core — and its reasoning for each:
| Feature Pi leaves out | Pi's reasoning | How you'd add it |
|---|---|---|
| MCP (a plugin protocol for tools) | Prefers plain CLI tools documented in READMEs | Build a skill, or an extension that adds MCP |
| Sub-agents (helper agents) | "Many ways to do this" — no single right answer | The subagent example extension, or separate pi instances in tmux |
| Plan mode | Not everyone wants it, or wants it the same way | The plan-mode example extension |
| Built-in to-do lists | "They confuse models" | A TODO.md file, or the todo example extension |
| Permission pop-ups | A real security boundary must come from the OS, not the app | Run in a container, or a confirmation extension |
| Background bash | Loses observability | Use tmux — you can watch and interact directly |
The unifying principle: the core provides one well-instrumented agent loop plus rich extension points; richer behavior is composed on top. That "no permission pop-ups" line has real security consequences — we'll come back to it, because it's the single most important thing to understand about Pi.
The Main Design: A Stack of Layers#
Here's the design decision beginners often miss: Pi isn't one monolithic program. It's a layered set of independently useful libraries, and the pi command you run is just the top layer gluing them together. Read this bottom-to-top — models → engine → product, with the UI on the side:
Each layer solves one problem well:
pi-ai— "talk to any model the same way." Every provider (OpenAI, Anthropic, Google, …) has a slightly different API.pi-aihides all of them behind one interface. This unlocks two superpowers: you can switch models mid-conversation (Claude → GPT → Gemini, and it rewrites the messages so the new model understands them), and you can save and resume any conversation because the whole thing is plain, serializable JSON. Bonus: streaming never throws — errors come back as events, which makes agents much easier to write robustly.pi-agent-core— the agent loop. This turns "send text to a model" into "call tools in a loop, track state, stream progress." It's provider-agnostic and emits a predictable event stream that the UI and extensions subscribe to. All the tool calls in one model response run in parallel by default. Hooks let you intervene:beforeToolCallcan block a call,afterToolCallcan rewrite a result,shouldStopAfterTurncan end the loop early.pi-tui— a smooth terminal UI. It redraws only the lines that changed and wraps updates in a terminal feature that paints them atomically — so you never see flicker or half-drawn frames.pi-coding-agent— the product. Combines the three below and adds the parts that make it a coding agent: real file/shell tools, saved sessions, compaction, extensions, and skills.
Why bother splitting it up? Because the same foundation can power more than one product — the CLI, an embeddable SDK, headless modes for scripting, and an experimental remote server — all reusing the same core.
Extensions: The 20% That Makes the Other 80% Possible#
If you remember one thing about how Pi works, make it this. Extensions are the mechanism behind every "you'd add it with…" cell in that philosophy table. Without them, Pi is just a bare agent with four tools. With them, it's whatever you need.
An extension is a .ts file that default-exports a function receiving Pi's API (conventionally named pi). There's no build step — Pi loads TypeScript directly. Save the file, run /reload, and it works:
export default function (pi) {
// Add a brand-new tool the model can call
pi.registerTool({ /* name, schema, execute... */ });
// Add a /review slash command for you
pi.registerCommand("review", /* ...handler... */);
// React to lifecycle events — and block or rewrite what happens
pi.on("tool_call", (ctx) => {
if (isDangerous(ctx)) return { block: true, reason: "nope" };
});
}
Through the pi.* API an extension can register tools, slash commands, keyboard shortcuts, CLI flags, custom UI, and even new model providers — and subscribe to events at every stage of a session. The powerful hooks can change or cancel what happens. The repo ships around 78 worked examples; a few that show the range:
| Extension | What it does | The pain it solves |
|---|---|---|
permission-gate | Matches dangerous shell patterns (rm -rf, sudo, chmod 777) on every tool_call and blocks or prompts. | Adds the safety net Pi doesn't ship with. The single most useful add-on. |
subagent | Spawns child pi processes as isolated helper agents (single / parallel / chain). | True sub-agents with their own fresh context, without baking them into the core. |
plan-mode | A read-only phase (edits disabled, bash allowlisted) that produces a numbered plan, then an execution phase. | Plan-then-execute, for when you want to review the approach before code changes. |
auto-commit-on-exit | Commits any dirty changes on shutdown with a message built from the last reply. | Never lose the agent's work. |
That last idea — sub-agents as an extension rather than a core feature — is Pi's whole philosophy in miniature. Let's look at it, because it's the question everyone asks.
Lighter-Weight Customization: Skills, Prompts, and Themes#
Not everything needs a full extension. Pi has three lighter ways to shape it that require no code at all:
- Skills are reusable "how to do X" instructions the model loads on demand. A skill is just a
SKILL.mdfile with a name and description. At startup Pi shows the model only the names and descriptions of your skills; when a task matches, the model opens the full file itself withread. This trick is called progressive disclosure — you don't dump every instruction into the context window up front, you let the model pull in only what it needs. (Pi follows the open Agent Skills standard — the same idea we broke down in our post on Agent Skills.) - Prompt templates are reusable prompts you trigger with
/name, with argument substitution — great for a repeatable request like a review checklist. - Themes are JSON color schemes that hot-reload as you edit them.
The rule of thumb: reach for a skill or prompt template when you're teaching the model what to do, and an extension when you're changing what Pi can do.
Does Pi Break Big Tasks Into Steps?#
Two answers, and the difference is the interesting part.
Within one conversation: yes, and it's built in. A single Pi agent handles complex work through its turn loop — the model plans, calls a batch of tools, reads the results, and repeats, all sharing one context:
Notice Pi imposes no planner — the model decides each next move. "Breaking a big task into sub-steps" is simply the model issuing successive tool batches over many turns, in the same context.
Separate helper agents with their own memory ("sub-agents"): not built in, on purpose. The entire product creates exactly one agent. If you want true sub-agents — a specialist sent off with a fresh context to do X and report back — you use the shipped subagent extension. Each call launches a brand-new headless pi process as a helper, with its own independent context window, model, and tool set. There are three ways to delegate:
| Mode | You pass | What happens |
|---|---|---|
| Single | { agent, task } | One helper does one task. |
| Parallel | { tasks: [...] } | Several helpers run at once (max 8 tasks, 4 at a time). |
| Chain | { chain: [...] } | Sequential steps; each step's output feeds the next. Stops at the first failure. |
The example even ships ready-made agent definitions — scout (fast recon on Haiku), planner, reviewer, and worker (on Sonnet) — plus workflows that chain them, like /implement (scout → planner → worker). A feature Claude Code hard-codes is, in Pi, something you compose from primitives it already has: headless mode plus an extension.
A Few More Mechanics Worth Knowing#
- Four built-in tools:
read,write,edit,bash— that's the default set (three more are optional:grep,find,ls). Tool output is capped at 50 KB / 2000 lines so a giant file can't blow up your context window, and file writes are serialized so parallel tools don't clobber each other. - Sessions are a tree, not a list. Each conversation is one file, and every entry has an
idand aparentId. That's what lets you/tree(jump back to any earlier point and branch),/fork(start a new file from an earlier message), and/clone— all without extra bookkeeping. - Compaction keeps long chats alive. As you approach the context limit, Pi automatically summarizes old messages into a structured summary (Goal, Constraints, Progress, Next Steps…). It never cuts between a tool call and its result, and it's lossy for the live context but lossless for history — the full original tree stays in the file.
- Four ways to run it, all backed by the same engine:
| Mode | Start it with | Best for |
|---|---|---|
| Interactive | pi | Everyday work in the full terminal UI |
pi -p "..." | One-shot answers and scripting | |
| JSON | pi --mode json "..." | Feeding every event to another program as JSON lines |
| RPC | pi --mode rpc | Driving Pi from another process or language |
There's also an SDK (import { createAgentSession } from "@earendil-works/pi-coding-agent") to embed Pi inside your own Node app.
Pi vs. Claude Code: Two Philosophies#
This is the comparison most readers came for, so let's be concrete — and fair. These are design trade-offs, not a winner and a loser.
| Dimension | Pi | Claude Code |
|---|---|---|
| Core philosophy | Minimal core + extension points | Batteries-included, opinionated |
| Model / provider | 30+ providers, switch mid-session, local models via llama.cpp | Anthropic (Claude) models |
| Sub-agents, plan mode, to-dos | Add via extensions (examples shipped) | Built in, work out of the box |
| Permission system | None built in — you gate via extension or OS sandbox | Built-in layered permission model |
| Customization ceiling | Very high — tools, commands, UI, providers, compaction all injectable at runtime, no fork | High, but within the tool's design |
| Out-of-box experience | More bare-bones until you add extensions | Rich immediately, zero setup |
| Scriptability | Print, JSON, RPC modes + SDK | CLI + SDK |
| Openness | Open source, MIT | Proprietary (Anthropic) |
| Session model | Tree in one file: branch / fork / clone / time-travel | Linear-ish sessions |
Where Pi pulls ahead:
- Provider freedom. One tool across 30+ providers, with mid-session model switching — swap cost-for-quality on the fly, or run a fully local model with no API key.
- A much higher customization ceiling. Reshape tools, commands, UI, providers, and even compaction at runtime — without forking.
- Reusable building blocks.
pi-ai,pi-agent-core, andpi-tuiare useful on their own, in your own projects. - Fully open and inspectable (MIT).
Where Claude Code is the easier choice:
- You want sub-agents, plan mode, to-dos, and permission prompts working instantly with zero setup.
- You want a hardened, self-contained tool with built-in approvals and don't want to manage a sandbox yourself.
- You're happy on Claude models and value polish over configurability.
The One Thing You Must Understand: Pi Is Not Sandboxed#
Read this part carefully — it's the one that can actually hurt you.
Pi runs with your account's full permissions. It can read, write, and delete any file you can, and run any command you can — and so can any extension or package you load. There is no built-in "are you sure?" gate and no sandbox.
Think of running Pi like handing your terminal to a very fast, very literal assistant who will do exactly what the model decides — including mistakes. That's fine on a trusted project you're watching. It's dangerous on untrusted code or when running unattended.
A few clarifications that trip people up:
- This is deliberate. The project argues that a half-sandbox living inside Pi would be worse than none — it would feel like a safety boundary while still depending on your host shell, filesystem, and credentials. Real isolation has to come from the operating system.
- "Project trust" is a loading gate, not a sandbox. When you open a folder, trust decides whether Pi loads that project's settings and extensions before you approve them. It does nothing to restrict what tools can do once you're working.
- Prompt injection is an accepted, unpreventable risk. Because the model reads your files, a malicious instruction hidden in a repo's code or docs can try to hijack the agent. Pi can't reliably prevent this — it's expected local-agent risk.
So when should you not run Pi directly on your machine? When you're working with untrusted repositories, generated code you won't review closely, or unattended automation. In those cases, isolate it — the docs describe three patterns: Gondolin (a micro-VM that keeps your logins on the host), plain Docker (run all of Pi in a container), and OpenShell (a policy-controlled sandbox). And don't mount your host ~/.pi/agent into a container — it holds your logins and sessions.
If you take nothing else from this section: the missing sandbox is a feature, not a bug — but only if you supply the isolation yourself when the work is risky.
When to Use Pi (and When Not To)#
Reach for Pi when you want:
- A local, terminal-first agent that runs your real tools (tests, linters, git).
- Model flexibility — one tool across 30+ providers, with mid-session switching.
- Something scriptable or embeddable (print, JSON, RPC, or the SDK) for CI and integration.
- A highly customizable harness you shape with extensions instead of forking.
- Reusable building blocks for your own projects.
Pi is a poor fit when:
- You need built-in approvals/sandboxing and can't run it inside your own container.
- You're running untrusted code or unattended automation and won't set up isolation.
- You want sub-agents, plan mode, MCP, or to-dos working out of the box with zero setup.
- You need a stable remote/multi-user server today (that stack is still experimental).
Try It in Five Minutes#
# 1. Install (--ignore-scripts is recommended; Pi needs no install scripts)
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
# 2. Give it a key (or run `pi` then `/login` to use a subscription)
export ANTHROPIC_API_KEY=sk-ant-...
# 3. Run it, and just talk
pi
> Explain what this project does and where the entry point is
That's the whole starting experience. From here you can switch models with /model, resume your last session with pi -c, or drop into a read-only mode with pi --tools read,grep,find,ls. It needs Node.js 22.19.0 or newer.
One recommendation before you let it loose on a machine you care about: add a safety net. Pi's permission-gate example blocks dangerous shell commands (rm -rf, sudo, …) before they run. Grab it from the repo's examples/extensions/, then either load it for one run or install it so Pi picks it up automatically:
# Load it for a single run
pi -e ./permission-gate.ts
# ...or install it globally so it always loads
cp permission-gate.ts ~/.pi/agent/extensions/
Takeaways#
- Pi's identity is "small core, big extension surface" — the deliberate opposite of batteries-included.
- Its real superpowers are provider-agnosticism and serializable conversations — switch models mid-chat, and save, resume, or branch anything.
- Features other agents hard-code are things you compose in Pi — more setup up front, a much higher ceiling long-term.
- You own the security boundary. No sandbox is a feature, not a bug — but only if you isolate risky work yourself.
If you want the batteries-included counterpart to this design, read our Claude Code Architecture Deep Dive. And if you're curious how the on-demand "skills" Pi loads actually work, we broke that standard down in our post on Agent Skills.
The real question isn't "which tool has more features." It's "do you want a tool you accept as-is, or one you reshape into exactly what you need?"