Skip to main content

Pi Harness Architecture and Extension Boundaries

Two judgements from Pi carry over to other tools. The first is to put each customization in its own layer: stable rules go in project context, a reusable opening prompt becomes a template, an on-demand method becomes a skill, and you write an extension only when you truly need a new tool, event hook, or interface. The second is that an extension runs arbitrary code with the authority of the account that started it, so a package is a distribution unit, not a security boundary. Claude Code's hooks are the same: they run outside its sandbox with your full access (sandboxing docs).

Pi keeps the core of its harness small and lets the user add everything else in layers. The core is therefore easier to understand and change, but safety policy and workflow quality do not appear automatically; the complexity has not gone away, it has been deferred to the person building the workflow. Everything below was checked against Pi 1.4.0 (released October 21, 2026) on 2026-10-29.

Pi: A Programmable Minimal Harness​

Pi describes itself as a minimal terminal coding harness. It keeps model calls, the agent loop, tool execution, branchable sessions, and the terminal interface in the core, then leaves workflow-specific behavior to composable resources. According to the CLI documentation, only four tools are enabled by default, read, bash, edit, and write, and the defaultTools setting can change that. Plain tool names in --tools replace the whole selection; since 1.1.0 you can also write +name / -name to add to or remove from the default four, for example pi --tools +codemode,-write. grep, find, and ls are built in too, but appear only when selected explicitly. Since 1.3.0 a resumed session keeps the tools it had enabled.

Beyond the interactive TUI, Pi supports one-shot -p output, a JSON event stream, stdin/stdout RPC, and an SDK for embedding the agent in a Node.js application. Providers can be switched. Sessions are stored as branchable JSONL trees; /tree, /fork, /clone, and /compact cover in-file exploration, separate sessions, and context compression.

What Each Customization Layer Owns​

LayerTypical entry pointGood home forDo not confuse it with
Project contextAGENTS.md, CLAUDE.md, .pi/SYSTEM.mdstable rules, path boundaries, acceptance commandsa plugin or sandbox
Prompt template.pi/prompts/review.md → /reviewa repeatable, parameterized starting promptan automatically executed workflow
Skill (Agent Skills).agents/skills/*/SKILL.md → /skill:nameon-demand procedures, scripts, and referencesa permission boundary or separate process
Extension.pi/extensions/*.tsnew tools, commands, event policy, UI, providers, and session behavioran inherently safe “lightweight plugin”
Pi packagepi install ..., pi configdistribution of extensions, skills, prompts, and themes through npm, Git, or local pathsisolation or trusted signing
SDK / RPCcreateAgentSession(), pi --mode rpcembedding, tests, or cross-language process integrationa mandatory layer for ordinary repositories

Global resources normally live under ~/.pi/agent/; project resources live under .pi/ or .agents/skills/. A prompt template only expands a command such as /review into a prewritten block of text. A skill shows only its name and description until it is needed, and loads its body on demand. An extension changes harness behavior directly. Prefer a skill when the missing piece is a method, a prompt when it is reusable input, and an extension only when a new tool, event hook, or UI is actually required.

How Pi's Agent Loop Runs​

Read against agent-loop.ts and AgentSession, one run has three layers: the session prepares a request, the low-level loop coordinates the model and tools, and the session layer handles persistence, compaction, and retries.

1. Much happens before the model call​

User input does not go straight to the model. In the lifecycle described in the extensions documentation, extension commands run first, the input event can intercept or rewrite the input, and only then do explicitly invoked Skills and prompt templates expand.

If the Agent is already running, a new message does not land in the middle of a tool call. It goes into one of two queues. A Steering message is inserted before the next model call, to correct direction mid-run; a Follow-up message waits until the current task was about to end, then keeps the Agent going.

The session then checks the model and authentication and decides whether old context needs compaction. A before_agent_start event may also append custom messages or alter the system prompt for this run. Only after these steps does the user message enter the low-level Agent Loop.

2. The low-level implementation has two loops​

The inner loop performs successive model calls. Before another call, Pi can refresh the system prompt, tools, model, and thinking level, and inject Steering messages at a safe turn boundary. It then transforms the context into the message format expected by the active provider and streams an Assistant Message.

If that response contains tool calls, Pi executes them, turns every result into a toolResult message, and calls the model again. The inner loop stops only when there are no tool calls and no waiting Steering messages. The outer loop handles Follow-up messages: it retrieves them only after the current tool chain and Steering queue are exhausted, then enters the inner loop again.

In the 1.4.0 keybinding reference, a message submitted with Enter while the agent runs goes to Steering and Alt+Enter queues a Follow-up (Ctrl+Q on Windows and WSL); Alt+Up restores queued messages to the editor (Alt+Q on Windows and WSL), and Escape aborts the run.

3. A tool call passes validation and hooks first​

Pi first checks that the tool exists, then prepares and validates its arguments; a call to an unknown tool or with invalid arguments never executes. An extension's tool_call event can block the call or modify its input, and the tool_result event after execution can rewrite the result. Tool calls may run in parallel, while tools marked Sequential, or a global sequential setting, force ordered execution. If the model's output is cut off by its length limit, every tool call in that response is marked as failed and the model has to submit it again.

4. After agent_end, the run may continue​

The low-level Loop emits agent_end when no tool calls or queued messages remain, or when an error or cancellation ends it. That is not yet idle: a retry, a compaction retry after an overflow, or a queued message can start another round, and an extension can request one last continuation in agent_before_settle. Only agent_settled means Pi will not continue on its own. Tests, schema checks, browser assertions, and human acceptance stay outside the Loop and come after it.

5. Compaction and retries belong to the session layer​

According to the compaction documentation, Pi checks context at three points: before a new Prompt, after tool results are appended and before the next model response, and after a low-level run ends. When the threshold is crossed, older messages become a summary while recent messages remain; when the provider reports a context overflow, Pi compacts and retries once. Since 1.4.0, summaries no longer have a fixed output cap and use the model's own output limit instead.

Messages are also written to a tree-shaped JSONL session. According to the session format, that tree stores messages, tool results, and compaction records, with no filesystem snapshots, so switching a session branch does not undo files already written to disk.

Extensions: Pi's Plugin Layer​

An extension is a TypeScript module loaded directly by Pi. It can:

  • register model-callable tools and user slash commands;
  • hook model turns, tool calls and results, model changes, and session lifecycle events;
  • block or rewrite dangerous calls and add protected paths or approval UI;
  • customize compaction, session state, terminal components, and rendering;
  • register providers, or implement MCP, subagents, plan mode, and sandbox integration.

Subagents, plan mode, and sandbox integration still come from examples or third-party packages and are not core defaults; MCP has been a built-in extension since 0.99.0 (see the next section). A small core permits competing designs while making the installer responsible for their policy, maintenance, and recovery behavior.

Auto-discovered extensions go in ~/.pi/agent/extensions/ or a trusted project's .pi/extensions/; run /reload after changing them. Use pi -e ./extension.ts for a temporary experiment without installing it. Extensions execute arbitrary code with the current user's authority, and a skill may instruct the model to run bundled scripts. Read source and dependencies before installation, pin versions, and enable only the resources the workflow needs.

From 0.99 to 1.4: MCP and Codemode Become Built-in Extensions​

The largest change came in 0.99.0 on September 29: from that release, codemode, tool search, and MCP are built-in extensions, all of which can be disabled in pi config. Codemode lets the model write JavaScript that calls Pi's tools in parallel inside a QuickJS sandbox; only the script's output reaches the model. Tool search finds tools the model has not been told about yet, when they are needed, so the next call can use them. Each of the two corresponds to one tool, codemode and tool_search, and both are off by default; the MCP extension turns them on when a connected MCP server needs them. The same release added classifier models; see Jev and Laya.

Model Context Protocol (MCP) servers can use stdio or Streamable HTTP, support OAuth, and are configured through mcp.json and managed with /mcp and pi mcp add|remove|list|login|logout. Later releases added:

  • 1.0.0: checks the iss value in authorization responses and stores credentials per server; the fullscreen interface becomes the default.
  • 1.0.1: a project's .pi/mcp.json can enable, disable, or adjust a user-level server for that project; registration with a Client ID Metadata Document.
  • 1.0.4: --no-mcp turns MCP off for one run; --tools accepts patterns such as mcp__<server>__*.

Daily Use​

On day one an ordinary project needs only AGENTS.md for rules and acceptance commands; add .pi/prompts/, .agents/skills/, and .pi/extensions/ once there is something that actually repeats. Common commands:

pi --name "parser repair" # interactive session
pi -c # continue the latest session
pi -r # open the session list
pi --no-session -p "Summarize this repository" # ephemeral one-shot
pi --no-session --tools read,grep,find,ls -p "Review the change" # read-only tools only
pi --no-extensions -e ./.pi/extensions/policy.ts # load only the extension under test

Inside a session, /model switches the model, /tree, /fork, and /clone manage branches, /compact compacts by hand, and /reload reloads extensions, skills, and context files.

Safety: Two Pi-Specific Switches​

Pi does not ask for approval before tool calls and has no built-in sandbox; its baseline and the comparison with other tools are in Agent Security. Only two points are specific to Pi:

  1. Project trust only decides whether project .pi settings, MCP configuration, and extensions are loaded; it does not limit what tools can reach. Print, JSON, and RPC modes cannot show the trust prompt, so automated runs must pass --approve or --no-approve explicitly (security documentation).
  2. Use --tools read,grep,find,ls for review work, and widen the tool set only when writes are required.
Explore connectionsOpen network