On this page
Pi Agent: The Minimal Harness That Became My Multi-Tool Glue
After 2 months switching between Claude Code, Codex, Gemini CLI, and Pi Agent for different coding tasks, Pi's 'primitives not features' philosophy turned it into the connective tissue between heavier tools.

I run four coding agents daily. Claude Code for planning and implementation. Codex for review loops. Gemini CLI (now Antigravity) for frontend-heavy visual work. And for the past two months, Pi Agent has quietly become the one I reach for when none of the others fit.
Not because Pi does more. Because it does less.
The problem is the spaces between these tools. The moments when you need a coding agent but don’t want 10,000 tokens of system prompt or 47 permission dialogs. Pi fills that gap.
What Pi Actually Solves
The problem isn’t “we need another coding agent.” We have plenty. The problem is that every existing harness assumes it’s the only tool you use.
%%{init: {"layout": "dagre"}}%%
flowchart TD
subgraph problem["The Sealed-Harness Problem"]
A[Claude Code] -->|locked to Anthropic| X[Vendor lock-in]
B[Codex] -->|locked to OpenAI| X
C[Gemini CLI] -->|locked to Google| X
X --> Y[Can't mix models mid-task]
X --> Z[Context overhead per session]
end
subgraph solution["Pi's Answer"]
D[Minimal core: 4 tools, <1000 tokens] --> E[Any provider, any model]
E --> F[Switch mid-session]
D --> G[Zero permission friction]
D --> H[Self-extending via TypeScript]
end
problem -.->|"Pi bridges the gaps"| solution
Claude Code injects thousands of tokens of system prompt on every turn. Codex adds its own orchestration overhead. Gemini CLI brings Google’s safety layers. Each tool makes assumptions about what you need, and those assumptions cost context tokens.
Pi’s thesis: the models already know how to code. They’ve been RL-trained for it. Give them read, write, edit, bash, and get out of the way.
The Multi-Tool Reality Nobody Talks About
Most agent content assumes you pick one tool and commit. That’s not how production workflows actually work.
| Tool | Where I Use It | Why |
|---|---|---|
| Claude Code | Planning (Opus), implementation (Sonnet + Haiku sub-agents) | Deep context, extended thinking, workflow orchestration |
| Codex | Code review, system improvement loops | Anti-gravity mode, iterative refinement |
| Gemini CLI | Frontend generation, slide decks, video templates | Long context (1M+), multimodal input, visual reasoning |
| Pi Agent | Glue tasks, quick explorations, extension authoring | Sub-1000 token prompt, zero friction, model flexibility |
The switch points matter:
%%{init: {"layout": "dagre"}}%%
flowchart LR
Task[New Task] --> Decision{What kind?}
Decision -->|"Architecture, deep reasoning"| CC[Claude Code + Opus]
Decision -->|"Review loop, iterative improvement"| CDX[Codex]
Decision -->|"Frontend visual, HTML gen, slides"| GEM[Gemini CLI]
Decision -->|"Quick, multi-model, custom tooling"| PI[Pi Agent]
CC --> Done[Ship]
CDX --> Done
GEM --> Done
PI --> Done
The diagram tells the full story. The part it doesn’t show: heavyweight agents add context overhead that actively hurts simple tasks. Pi’s entire system prompt and tool definitions fit in under 1,000 tokens. More context window goes to actual work.
Why “Primitives Not Features” Actually Works
Pi’s creator, Mario Zechner, made a series of contrarian choices that sound wrong until you use them daily:
- No MCP support
- No sub-agents
- No plan mode
- No background bash
- No built-in to-dos
- No permission prompts
Each of these is a deliberate subtraction. And each one forces you into a pattern that’s actually better.
No MCP: Progressive Disclosure via CLI Tools
MCP servers like Playwright (21 tools, 13.7k tokens) or Chrome DevTools (26 tools, 18k tokens) dump their full tool descriptions into every session. That’s 7-9% of your context window gone before you type a single character.
Pi’s alternative: build CLI tools with README files. The agent reads the README only when it needs the tool:
%%{init: {"layout": "dagre"}}%%
sequenceDiagram
participant User
participant Pi
participant Tool as CLI Tool
User->>Pi: "search the web for X"
Pi->>Pi: reads ~/.pi/tools/websearch/README.md
Pi->>Tool: websearch "query" (via bash)
Tool-->>Pi: results
Pi-->>User: summarized answer
Note over Pi: Token cost paid ONLY this turn
This is progressive disclosure applied to agent tooling. You pay for what you use, when you use it. Compare that to MCP dumping 13k tokens into every single session whether you search the web or not.
No Sub-Agents: Full Observability
In Claude Code, sub-agents are black boxes. You see the output but not the reasoning, not the context gathering, not the missed files. Pi’s approach: spawn yourself via bash.
---
description: Run a code review sub-agent
---
Spawn yourself as a sub-agent via bash to do a code review: $@
Use `pi --print` with appropriate arguments.
Pass a prompt asking it to review for bugs, security, error handling.
Report the sub-agent's findings.
The output streams to your terminal. You see everything. If the sub-agent misses something, you know immediately because you’re watching the full output, not a summary.
No Background Bash: tmux Instead
Background process management in coding agents is a solved problem. It’s called tmux.
# Pi can start a dev server in tmux
tmux new-session -d -s devserver 'npm run dev'
# Check output later
tmux capture-pane -t devserver -p
# List all sessions
tmux list-sessions
You get observability for free. You can hop into the session yourself. And after context compaction, the agent can still query tmux list-sessions to find its running processes. No special tooling needed.
Beyond Coding: Pi as a Generalized Agent
Here’s something most people miss about Pi. It’s marketed as a “coding agent,” but the architecture is completely general-purpose. The core is just: take a system prompt, give the model tools, run a loop until it’s done.
Swap the tools and system prompt, and you have a different agent entirely:
%%{init: {"layout": "dagre"}}%%
flowchart TD
subgraph core["Pi Core (unchanged)"]
A[Agent Loop]
B[Tool Dispatch]
C[Event Stream]
D[Session Management]
end
subgraph configs["Different SYSTEM.md + Extensions"]
E["Coding Agent<br/>tools: read, write, edit, bash"]
F["SRE Agent<br/>tools: kubectl, terraform, aws-cli"]
G["Research Agent<br/>tools: websearch, fetch, summarize"]
H["Data Pipeline Agent<br/>tools: dbt, sql, spark-submit"]
end
E --> core
F --> core
G --> core
H --> core
I’ve used Pi for:
- Infrastructure work:
SYSTEM.mdthat says “you are an SRE, use terraform and kubectl, never run apply without showing the plan first” - PRD review:
SYSTEM.mdin “product-review mode” with read-only tools, prompting the agent to critique a requirements doc - Data exploration: extensions that add
sqlandchartcommands for ad-hoc analysis
The extension system makes this trivial. Write a TypeScript module, add your custom tools, reload. You’re not fighting the harness to do something it wasn’t designed for. You’re configuring it to be exactly what you need.
This isn’t hypothetical at scale, either. Pi is the harness powering OpenClaw, Peter Steinberger’s personal-agent project that hit 100,000+ GitHub stars and 2 million users within a week before Steinberger joined OpenAI to lead its foundation in February 2026. Same architecture. Same four primitives. Different SYSTEM.md. The generalized-agent thesis proved out at consumer scale before most people even noticed Pi existed.
Why this matters: Claude Code, Codex, and Gemini CLI are all opinionated about being coding tools. Their system prompts, tools, and workflows assume code editing. Pi assumes nothing. The SYSTEM.md override lets you replace the entire persona.
Code Change Highlighting: The One Gap
Every coding agent struggles with the same UX problem: showing you what changed. Pi uses a standard edit tool with exact text matching (old text → new text), similar to Claude Code and Codex. The diffs render as inline text in the terminal.
Is there a better approach? Honestly, not really. The current state of the art across all harnesses:
| Harness | Diff Display | Limitation |
|---|---|---|
| Claude Code | Inline diff blocks in terminal | No side-by-side, hard to scan large changes |
| Codex | Patch-style output | Better for review, worse for streaming |
| Gemini CLI | Standard diff format | Minimal formatting |
| Pi | Raw old/new text in tool output | Partial JSON streaming shows diffs as they form |
Pi does one thing slightly better: partial JSON parsing during tool call streaming. As the model streams the edit tool arguments, Pi progressively parses them so you can see the diff forming in real-time before the call completes. It’s subtle but makes large edits feel more responsive.
For anything more sophisticated (side-by-side diffs, syntax-highlighted changes, file-level diff views), you’re looking at extensions or external tooling. Pi’s headless JSON mode (--mode json) makes it straightforward to pipe tool outputs into a custom diff renderer if you want one. Someone will build that extension eventually. Or you can ask Pi to build it for you.
Context Engineering Without the Abstractions
Pi loads context files hierarchically:
~/.pi/agent/AGENTS.md → Global instructions (all sessions)
./AGENTS.md → Project-specific rules
./SYSTEM.md → Override/append to system prompt
That’s it. No dynamic injection framework. No RAG pipeline. No memory database. Just markdown files that get prepended to your conversation.
| Context Layer | What Goes Here | Token Cost |
|---|---|---|
SYSTEM.md | Full system prompt override | Replaces default ~200 tokens |
Global AGENTS.md | Cross-project rules, coding style | ~500-1000 tokens |
Project AGENTS.md | Architecture, conventions, deployment | ~500-2000 tokens |
| Skills (on-demand) | Capability packs, loaded when needed | 0 until invoked |
That’s the static layer. But the hierarchy isn’t the ceiling — it’s the default. Extensions can inject messages before each turn, filter message history, or implement custom RAG and long-term memory. Compaction itself is fully pluggable: swap in topic-based summaries, code-aware compression, or route summarization through a cheaper model entirely. The primitives philosophy extends all the way down to context management.
Why this matters for multi-tool workflows: I maintain one AGENTS.md per project that works across all my tools. Claude Code reads CLAUDE.md. Codex reads its own instructions. Pi reads AGENTS.md. The project context is portable.
The Extension System: Build What You Need
Pi ships with four tools: read, write, edit, bash. Everything else is a TypeScript extension.
Need a plan mode? Build it. Need permission gates? Build them. Need MCP? Wrap it.
The pattern that unlocked Pi for me: asking Pi to extend itself.
You: "Write an extension that adds a /deploy command which
runs our staging deploy script and waits for health checks"
Pi: *writes extension, saves to .pi/extensions/deploy.ts*
You: /reload
You: /deploy
Self-modification, hot-reload, keep going. No pull requests to an upstream repo. No waiting for the next harness release. The tool adapts to you in real-time.
Multi-Model Switching Mid-Session
This is where Pi shines for exploratory work. Claude Code locks you to Anthropic. Codex locks you to OpenAI. Gemini CLI locks you to Google. Pi talks to 15+ providers and lets you switch mid-conversation:
Ctrl+L → pick model from list
Ctrl+P → cycle through favorites
/model deepseek-chat → switch instantly
My daily pattern:
- Start with a cheap model (DeepSeek, Haiku) for exploration and context gathering
- Switch to Sonnet or GPT-5.1 for the actual implementation
- Drop to a local Ollama model for sensitive code that shouldn’t leave my machine
The context carries over between model switches. Pi handles the translation between provider-specific formats (thinking traces, tool call schemas, signed blobs) automatically. This is the pi-ai layer doing the heavy lifting: converting Anthropic thinking traces to <thinking> tags for OpenAI, normalizing token reporting across providers, handling signed blobs that need replay.
The cost story is worth noting too. Pi’s session files are plain JSONL with token counts per message, which means community tools like agent-cost-dashboard can read them directly to produce spend timelines, per-model cost breakdowns, and cache-hit-rate analysis. Zero-dependency observability that comes free from the session format. When you’re switching between $0.25/MTok and $15/MTok models in the same session, knowing exactly where your budget went matters.
Session Branching: The Underrated Killer Feature
Multi-model switching is useful. But what makes it practical for exploration is Pi’s tree-structured session system. Sessions are stored as JSONL trees where every entry has an id and parentId. This means you can:
/tree— visualize your conversation as a branching tree, jump to any prior point/fork— branch from any earlier message without losing the abandoned path/clone— duplicate a branch to try a different approach/exportand/share— render the session as an HTML transcript, push to a GitHub gist
Pi auto-summarizes abandoned branches so context isn’t lost when you switch directions. The combination with model switching is where this gets powerful: start exploring with DeepSeek, hit a wall, /fork back three messages, switch to Sonnet, retry. Both paths preserved. Both paths navigable.
%%{init: {"layout": "dagre"}}%%
flowchart TD
A[Initial prompt] --> B[Exploration with DeepSeek]
B --> C[Dead end]
A --> D["/fork → Switch to Sonnet"]
D --> E[Better approach found]
E --> F["/fork → Try GPT-5.1 variant"]
E --> G[Ship this one]
style C fill:#f9f,stroke:#333,stroke-dasharray: 5 5
For someone juggling multiple models daily, this is arguably a bigger differentiator than the four-tools minimalism. It’s the piece that makes exploratory branching across providers a real workflow instead of a party trick.
The Benchmark Validation
Pi’s minimalist philosophy sounds like cope until you see the numbers. On Terminal-Bench 2.0, Pi with Claude Opus scored competitively against Codex, Cursor, and Windsurf with their native models. All with a system prompt under 1,000 tokens and four tools.
The even more telling result: Terminus 2, a minimal agent that just gives the model a raw tmux session (no tools, no file operations, just terminal interaction), also holds its own on the leaderboard. The models have been RL-trained to understand coding tasks. The harness overhead is largely wasted context.
The One Thing I Wish Were Different: It’s TypeScript
I’d have loved Pi in Python. Not because TypeScript is wrong for this. It’s clearly the right choice for a terminal TUI with streaming and event-driven architecture. But my extension and automation ecosystem is Python-heavy. Data pipelines, ML tooling, internal scripts. When I want to extend Pi with something that touches my existing infrastructure, there’s a translation layer I shouldn’t need.
The Python alternatives for minimal agent harnesses are thin on the ground:
| Option | Language | Philosophy | Trade-off |
|---|---|---|---|
| Pi | TypeScript | Primitives, not features | Best minimal harness, wrong language for my infra |
| Tau | Python | Readability-first, educational | Clean architecture, less battle-tested as a daily driver |
| opencode | Go | Claude Code clone, open source | Minimal philosophy but not truly extensible |
Build your own on tau_agent | Python | Full control | You’re now maintaining an agent harness |
Tau is the closest Python equivalent. HuggingFace built it to be understood, and the architecture is genuinely the same three-layer split (provider, brain, application shell). The 200-line core loop is identical in spirit to Pi’s agent loop. But Tau optimizes for pedagogy. Pi optimizes for daily use.
If I were starting a Python-native minimal agent today, I’d seriously consider building on tau_agent as the core and layering Pi-style ergonomics on top: the hierarchical context files, the extension hot-reload, the tree-structured sessions. The architecture is proven. The implementation gap is mostly UX.
For now, I live with the TypeScript trade-off. Pi’s extension system is productive enough that writing TypeScript extensions doesn’t slow me down for agent-level tooling. But the dream of pip install pi-agent and writing extensions in the same language as my ML stack remains unfulfilled.
Getting Started
Pi installs in one command and requires zero configuration to start using:
# Install globally
npm install -g @earendil-works/pi-coding-agent
# Or run directly
npx @earendil-works/pi-coding-agent
# Start with your preferred provider
export ANTHROPIC_API_KEY=sk-...
pi
# Or use OAuth for Claude Pro/Max
pi --oauth
The minimum useful setup takes about 5 minutes:
- Install Pi and set one API key (any provider works)
- Create
~/.pi/agent/AGENTS.mdwith your global coding preferences - Add
AGENTS.mdto your project root with project-specific instructions - Pick 2-3 favorite models and configure
Ctrl+Pcycling
# Minimal global AGENTS.md
mkdir -p ~/.pi/agent
cat > ~/.pi/agent/AGENTS.md << 'EOF'
- Use TypeScript unless told otherwise
- Prefer functional patterns over classes
- Run tests after changes
- Commit messages: conventional commits format
EOF
From there, let friction guide you. Hit a wall? Ask Pi to write an extension. Need a skill? Install a package. The tool grows with your workflow, not ahead of it.
Key resources:
| Resource | What You Get |
|---|---|
| pi.dev | Official docs, philosophy, package registry |
| GitHub mono-repo | Source, 50+ extension examples, skills |
| Mario’s blog post | Architecture deep-dive, design decisions |
| YouTube crash course | 17-minute walkthrough covering 90% of features |
When to Reach for Pi
After two months of daily use across all four tools, my routing heuristic:
| Task Type | Tool | Reason |
|---|---|---|
| Architecture planning | Claude Code (Opus) | Extended thinking, deep context |
| Feature implementation | Claude Code (Sonnet + sub-agents) | Workflow orchestration, parallel agents |
| Code review loops | Codex | System review loops, iterative refinement |
| Frontend visual work | Gemini CLI | 1M+ context, multimodal, visual reasoning |
| HTML/slide generation | Gemini CLI | Long output, template iteration |
| Quick exploration | Pi (cheap model) | Zero overhead, instant start |
| Multi-model experiments | Pi (model switching) | 15+ providers, mid-session swaps |
| Custom tooling | Pi (extensions) | Self-modifying, hot-reload |
| Non-coding agent tasks | Pi (SYSTEM.md override) | General-purpose harness, not locked to coding |
| Sensitive code | Pi (local Ollama) | Nothing leaves the machine |
The pattern: use the heavyweight tools for heavyweight tasks. Use Pi for everything in between.
The Bottom Line
Pi isn’t trying to replace Claude Code, Codex, or Gemini CLI. It’s trying to be the agent equivalent of a good text editor: fast to start, minimal by default, infinitely customizable when needed.
The “primitives not features” philosophy means you build exactly the tool you need. Nothing more. No cruft accumulates. No system prompt changes break your workflow between releases. And crucially, it’s not locked into being a coding agent. Swap the system prompt and tools, and it becomes whatever agent you need it to be.
After two months, my honest assessment: Pi handles 40-50% of my daily agent interactions. Not the complex ones. The ones where I used to open Claude Code and immediately feel the overhead of 47 tools, permission prompts, and context injection I didn’t ask for.
Sometimes less tooling produces more output.
Using multiple coding agents in your workflow? I’d love to hear how you route between them. Reach out on LinkedIn.