All posts

What is Pi? A minimal coding agent, explained

A practical introduction to the open-source agent with four tools and one loop.

Xinwei He

Pi logo

Most coding agents grow by adding things: a plan mode, a permission pop-up before each command, sub-agents, a to-do panel. Every feature is reasonable on its own, and together they make an agent that is hard to read and harder to change. Pi went the other way. Its default coding setup has four tools and a short system prompt, and it does not pause for approval before each tool call. Workflow features such as plan mode and sub-agents are extensions you choose to add.

This guide answers three questions: what Pi is made of, how one run works, and how to make Pi fit your workflow. Think of Pi as a kernel rather than an application: a reusable core whose workflow you can shape through instructions and extensions.

The running example is a small TypeScript repo with one failing test. You install Pi, fix the test, and then shape Pi around how you work. The guiding rule is simple: start with plain-text instructions, and write code only when text is not enough.

What is Pi made of?

Pi is an open-source TypeScript project started by Mario Zechner, who also created the libGDX game framework. It began as badlogic/pi-mono and now lives at earendil-works/pi, with packages published under the @earendil-works npm scope. If a tutorial installs @mariozechner/pi-coding-agent, it predates that move.

Project detail Checked September 30, 2026
GitHub stars about 110,000
Version checked 0.99.2, with workspace packages versioned together
Built-in model providers 42 provider entries, plus configurable compatible endpoints
Default tools 4
Example extensions shipped in the repo 70+
License MIT

The examples and implementation details below refer to that release. The main packages divide the work as follows:

  • pi-ai is the provider layer. It hides the differences between OpenAI, Anthropic, Google, Bedrock, OpenRouter, local servers and the rest behind one typed interface, and reports token usage and estimated cost. Its chat-model catalog includes only models that support tool calling; it also has separate image and classifier catalogs.

  • pi-agent-core is the runtime. Its Agent class holds state and runs the turn loop.

  • pi-coding-agent is the pi command you type. It is a thin shell: it reads your flags and files, then hands off to a shared session layer.

  • pi-tui is a terminal UI framework. It uses differential rendering to update changed lines and reduce flicker while answers stream.

Pi package dependencies

The CLI and server use pi-agent-core, which calls pi-ai to reach model providers. Chord and pi-telemetry provide shared foundations.

The CLI and experimental server share the agent core and provider layer. The diagram shows the main runtime dependencies, rather than every package in the repository.

Because the layers are separate, you can use any of them on their own. pi-ai works as a plain multi-provider LLM client, and pi-tui works for any terminal app.

Newer packages sit around that spine: chord for plugin composition, pi-durable for durable conversation, task and document state, pi-telemetry for tracing contracts, and an experimental remote protocol. Older write-ups also describe a Slack bot, a web UI and a GPU deployment tool inside the repo. Those have left the monorepo, and Slack automation now lives in a separate pi-chat project.

How does one Pi run work?

An agent is a loop around a model, not one completion. Pi makes that loop easy to see. Each turn, it prepares the context, streams the model's response, runs any tool calls, adds the results to the conversation and checks whether another turn is needed. In the normal flow, the loop ends when no tool calls or queued messages remain.

Pi agent turn loop

Pi polls steering, transforms context, converts messages, and streams a response. Tool calls pass through beforeToolCall, execute, and afterToolCall before results are appended. Queued follow-ups continue the loop; otherwise agent_end ends the loop.

This is the normal loop, simplified. Lifecycle hooks can request another turn, and session-level recovery can continue after agent_end; agent_settled marks the point when Pi will not continue automatically.

Context preparation polls steering messages, then calls transformContext to filter or enrich the context and convertToLlm to produce pi-ai's shared message format. The session layer manages automatic compaction separately. Pi streams the response through pi-ai. For each tool call, beforeToolCall can block execution; otherwise the tool runs and afterToolCall handles the result. Tool results and queued follow-ups are appended before the next turn.

In the example, the developer types "the date formatting test fails, find out why and fix it." Pi reads the test, reads the source file, runs npm test with bash, makes a change with edit and runs the tests again. That is five tool calls across a few turns, and each one appears in the terminal as it happens.

The default tool set is small on purpose:

Tool What it does
read Reads a text file or image, with offset and limit for long files
bash Runs a command in the working folder, with an optional timeout
edit Replaces exact text; each oldText must match one unique spot in the file
write Creates or overwrites a file

grep, find and ls are available if you turn them on. The built-in read and bash tools normally truncate text output at 2,000 lines or 50 KiB, whichever comes first. Search tools have their own result limits, and custom tools are responsible for limiting their output.

Pi also has built-in MCP support and an optional codemode tool for composing tool calls in JavaScript. The four-tool default is a starting configuration, not a limit on what Pi can expose.

Talking to Pi while it works

You do not have to wait for a run to finish. Press Enter to send a steering message: it waits until the current tool calls finish, then shapes the next step ("use the existing formatDate helper instead"). Press Alt+Enter to queue a follow-up: it waits until Pi is done with everything ("now add a test for leap years"). Escape stops the run.

Sessions you can branch

By default, sessions are saved as JSONL files under ~/.pi/agent/sessions/. Each entry points to its parent, so one file holds a tree. Type /tree to jump back to any earlier message, edit it and try a different path without losing the first one. /fork copies a branch into a new session, and pi -c picks up where you left off.

Pi normally compacts before the context window fills. By default it targets roughly 20,000 recent tokens to retain, reserves 16,384 tokens for the response, and replaces the older part with a structured summary (goal, progress, decisions, next steps, files touched). The original messages stay in the file.

How do you make Pi fit your workflow?

Pi's docs give one rule for customizing: start with the lightest option that does the job. The steps below follow the example from text to code.

1. Install and log in

bash
# Requires Node.js 22.19 or newer
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
# or on macOS and Linux
curl -fsSL https://pi.dev/install.sh | sh
cd my-project
pi

Inside Pi, type /login to connect a subscription (Claude Pro or Max, ChatGPT Plus or Pro, GitHub Copilot and others) or paste an API key. Switch models any time with Ctrl+L, and change how much the model thinks with Shift+Tab.

2. Write an AGENTS.md

The first thing the developer notices is that Pi keeps running npm test when the repo uses pnpm. Add the repository conventions to AGENTS.md:

AGENTS.md
- Use pnpm, never npm.
- Run tests with `pnpm test -- --run`.
- Dates are formatted with the helpers in src/lib/date.ts.

Pi discovers context files in ~/.pi/agent/, the working directory and its parents. It supports AGENTS.md and CLAUDE.md; an AGENTS.override.md takes precedence in the same directory. These context files load independently of project trust.

3. Turn a repeated request into a prompt template

The developer asks for the same pre-commit review every day. A markdown file in .pi/prompts/ becomes a slash command.

.pi/prompts/review.md
---
description: Review staged changes before commit
argument-hint: "[focus]"
---
Review the staged changes. Focus on ${@:-correctness and missing tests}.

Saved as review.md, it runs as /review or /review error handling. ${@:-...} uses all supplied arguments, with a default when none are provided. Run /reload after adding the file; project templates require project trust.

4. Add a skill for a bigger procedure

When a task needs steps plus supporting files (a release checklist and a script, say), use a skill: a folder with a SKILL.md and anything it needs. Pi advertises each skill's name, description and path, then loads its full instructions when needed. This keeps the full procedure out of context until it is used. Pi follows the open Agent Skills format, so the same folders work in other tools that support it.

5. Write an extension when text is not enough

The developer wants a confirmation prompt before simple rm commands. No amount of text in AGENTS.md enforces that, so this is the point where code makes sense.

.pi/extensions/no-rm.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") return undefined;
    const command = event.input.command as string;
    if (!command.includes("rm ")) return undefined;
    const ok = ctx.hasUI && (await ctx.ui.confirm("Delete command", command));
    if (!ok) return { block: true, reason: "Blocked by user" };
  });
}

This small example only checks for the text rm ; it is not a sandbox or a general defense against destructive commands.

Extensions are TypeScript files loaded without a build step. Besides blocking tool calls, they can add tools, slash commands, keyboard shortcuts, UI widgets and whole model providers. The repo ships more than 70 examples, including a plan mode, sub-agents, a to-do list, a sandbox and, for fun, Snake. When you want to share a set of these, bundle them as a Pi package and install it with pi install npm:your-package.

6. Run Pi without the terminal UI

The same agent runs in four other ways, which is how people put it into scripts, CI jobs and their own apps.

Mode Command Good for
Print git diff | pi -p "Review this change" One-shot answers in scripts
JSON pi --mode json "prompt" > events.jsonl Capturing every event for later
RPC pi --mode rpc Driving Pi from another program over stdin and stdout
SDK createAgentSession() Embedding Pi in a TypeScript app
typescript
import { createAgentSession } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession({ tools: ["read", "grep", "find", "ls"] });
try {
  await session.prompt("What does src/lib/date.ts export?");
  console.log(session.getLastAssistantText());
} finally {
  session.dispose();
}

Why Pi works this way

Why only four tools?

A small, fixed tool set is easier for a model to use well and easier for a person to follow. Pi's bet is that extra capability should be something you add on purpose, which is why features other agents build in ship here as example extensions.

Why no approval pop-ups?

Keeping policy out of the core means the same agent can serve a developer at a laptop and a locked-down CI job, with only the extensions changing. You still see every read, command and edit as it happens; Pi simply does not stop to ask.

Why keep the layers separate?

Because each layer can then be reused or replaced alone. You can build a web app or a chat bot on pi-agent-core without touching the terminal code, or swap the model provider without touching the loop. The agent core keeps its own message state and converts it to pi-ai's shared message format before each request. Pi-ai handles provider-specific translation, enabling cross-provider handoffs during a session.

Where it goes wrong

Pi runs with the permissions of whoever started it. Its default file and shell tools are not isolated from the host, and the security docs say plainly that "watching the transcript, using project trust, and reviewing changes do not create a security boundary." Project trust gates most project settings and resources, including project extensions and MCP servers; it does not restrict tool permissions or stop context files such as AGENTS.md from loading. If you run Pi on code or input you do not trust, isolate it: the docs describe Docker, Docker Sandboxes, NVIDIA OpenShell and a micro-VM extension called Gondolin.

Trust also catches people in automation. Print, JSON and RPC modes cannot show the built-in trust prompt. Command-line overrides, a trust-handling extension or a saved trust decision can authorize project resources; otherwise only defaultProjectTrust: "always" loads them. Use --approve for an explicit one-run project decision, or load a reviewed guard directly with -e ./guard.ts.

Older tutorials are a trap. The npm scope, the package list and even the edit tool's parameters have changed, so check anything you copy against the current repo and docs.

Finally, the lean design is a choice, not a default everyone will like. If you want plan mode and approval prompts out of the box, Pi asks you to add them yourself.

Takeaway

  • Install Pi, run /login and give it a real task in a real repo.

  • Put repo rules in AGENTS.md before anything else.

  • Turn repeated requests into prompt templates and bigger procedures into skills.

  • Write an extension only when you need code to enforce or add something.

  • Isolate Pi yourself when it touches anything you do not trust, because the core will not.

Pi is part of our own development workflow at TraceRoot. Its small core makes the surrounding decisions explicit: what context the agent gets, which tools it can use, and how we check its work. In upcoming posts, we’ll walk through those choices using examples from our own projects.

Resources