Pi is a minimal, open-source coding-agent harness created by Mario Zechner (author of the libGDX game framework) and published by earendil-works under the MIT License. First released in August 2025 and shipping frequently since (v0.80.3 on June 30, 2026; ~69k GitHub stars), Pi is built around a single, contrarian thesis: modern frontier models are already trained to write code, so the harness around them should be tiny and yours to reshape, not a feature-bloated “spaceship.” Its tagline captures it exactly: “There are many agent harnesses — but this one is yours.”
Where the Agent Harness entry describes the concept, Pi is the cleanest concrete embodiment of it — small enough to read end-to-end, yet competitive with much larger tools.
The core thesis: four tools and a tiny prompt
Pi’s most striking design decision is what it leaves out. The agent ships with exactly four built-in tools:
| Tool | Purpose |
|---|---|
| read | Read a file |
| write | Write a file |
| edit | Modify part of a file |
| bash | Run a shell command |
Paired with these is a deliberately minimal system prompt — small enough (well under 1,000 tokens) that the model’s own training, not lengthy scaffolding instructions, does the heavy lifting. Anything else — sub-agents, plan mode, MCP, background bash, to-do management — is intentionally absent from the core and added only when you want it, as an extension.
Architecture: a small, layered monorepo
Pi is a TypeScript monorepo of four focused packages. Each is usable on its own, which is why Pi doubles as a toolkit for building agents, not just a CLI for using one.
flowchart TB
classDef default fill:#ffffff,stroke:#4338CA,stroke-width:2px,color:#0F172A
classDef system fill:#4338CA,stroke:#4338CA,stroke-width:2px,color:#ffffff
classDef mid fill:#EEF0F7,stroke:#6366F1,stroke-width:2px,color:#0F172A
classDef accent fill:#0D9488,stroke:#0D9488,stroke-width:2px,color:#ffffff
CLI[pi-coding-agent\ninteractive CLI · 4 tools · modes]:::system
CORE[pi-agent-core\nagent loop · tool calls · state · verify]:::accent
AI[pi-ai\nunified API · 15+ providers · BYOK]:::mid
TUI[pi-tui\nterminal UI · ~600 LOC · diff render]:::mid
CLI --> CORE
CLI --> TUI
CORE --> AI
AI --> P([Anthropic · OpenAI · Google · xAI ·\nMistral · Groq · Cerebras · Ollama · …]):::default
@earendil-works/pi-ai— a unified multi-provider LLM API spanning 15+ providers (Anthropic, OpenAI, Google/Vertex, Azure, Bedrock, Mistral, Groq, Cerebras, xAI, Hugging Face, Kimi For Coding, MiniMax, NVIDIA, OpenRouter, Ollama, and more), fully bring-your-own-key (BYOK).@earendil-works/pi-agent-core— the runtime implementing the generalized agent loop with tool invocation, state management, and verification.@earendil-works/pi-tui— a terminal UI library (~600 lines) with differential rendering.@earendil-works/pi-coding-agent— the interactive coding-agent CLI itself, runnable in interactive, print/JSON, RPC, and SDK modes.
The killer feature: self-extension at runtime
Pi’s answer to “but I need feature X” is not a settings menu — it’s code. Extensions are TypeScript files loaded via jiti (no build step) that register new tools, commands, shortcuts, and UI. Critically, Pi can edit its own extensions with hot reloading: when the agent modifies an extension file in your project, the change takes effect immediately, without restarting the session. So you can literally ask Pi to build the tool you’re missing, and it writes, loads, and starts using it in the same run.
flowchart LR
classDef default fill:#ffffff,stroke:#4338CA,stroke-width:2px,color:#0F172A
classDef system fill:#4338CA,stroke:#4338CA,stroke-width:2px,color:#ffffff
classDef step fill:#EEF0F7,stroke:#6366F1,stroke-width:2px,color:#0F172A
classDef done fill:#0D9488,stroke:#0D9488,stroke-width:2px,color:#ffffff
A["Ask Pi:\n'add a run_tests tool'"]:::step --> B[Pi writes a TS\nextension file]:::system
B --> C[jiti hot-reloads it\nmid-session]:::step
C --> D[registerTool adds\na 5th tool]:::system
D --> E([Pi now calls\nrun_tests]):::done
The code example below shows the exact, verified extension API (pi.registerTool({...}) with typebox parameter schemas) used to add a run_tests tool.
Why minimal still wins: benchmarks
The obvious objection to a four-tool agent is that it must be weak. Pi’s headline result rebuts it: on Terminal-Bench’s 82 tasks, Pi ranked #2 using Claude Opus — despite lacking MCP support, sub-agents, plan loading, background bash execution, and built-in to-do management. The lesson mirrors the broader agent-harness insight of 2026: with a strong enough model, a lean harness that gets out of the way can beat a heavy one that boxes the model in.
Getting started
# Install the CLI (npm shown; pnpm, bun, curl, and PowerShell also supported)
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
# Run it in your project — bring your own provider key
export ANTHROPIC_API_KEY=sk-...
pi
Where Pi fits
Pi sits at the “own-your-workflow” end of the agentic AI tooling spectrum — the opposite pole from batteries-included harnesses like Claude Code or Zhipu’s ZCode (built for GLM-5.2). Because it is model-agnostic and BYOK, Pi will happily drive any capable model — including open-weight ones like GLM-5.2 — and because its core is small and readable, it’s also the harness most people point to when they want to understand how a coding agent actually works. For teams that want to accumulate reusable, versioned skills and tools around an agent they fully control, Pi is a reference-quality starting point.
Extending Pi at runtime: register a custom tool (real Pi extension API)
// A Pi extension is a TypeScript file that default-exports a factory
// receiving the ExtensionAPI. Pi loads it via jiti — no build step — and
// hot-reloads it the instant the file changes, even mid-session.
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
export default function (pi: ExtensionAPI) {
// Add a 5th tool on top of Pi's built-in read / write / edit / bash.
pi.registerTool({
name: "run_tests",
label: "Run tests",
description: "Run the project's test suite and return the summary.",
parameters: Type.Object({
path: Type.String({ description: "File or directory to test" }),
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
onUpdate?.({ text: `Running tests in ${params.path}...` });
const { stdout, stderr } = await run(
"pytest", [params.path, "-q"], { signal },
).catch((e) => ({ stdout: "", stderr: String(e) }));
// Every tool returns a content array + a details object.
return {
content: [{ type: "text", text: stdout || stderr }],
details: { path: params.path },
};
},
});
// Extensions can also hook lifecycle events, add slash-commands, etc.
pi.on("session:start", () => {
pi.log?.("run_tests extension loaded");
});
}
// Because Pi can edit its own extensions, you don't have to write this by
// hand — you can ask Pi: "add a run_tests tool that shells out to pytest,"
// and it will author, load, and start using the tool without a restart.
Ready to build?
Leverage AI technologies to build your product stack
Superteams can help you build, deploy and launch AI application stacks using open source technologies — from architecture through to production.
Talk to Superteams