Pi Is a Coding Agent That Skips Plan Mode and Sub-Agents on Purpose
💡 Tool Tip:Agent Sandbox Guide, Local LLM Runner, Prompt Template Library, Text Diff
Pi is a terminal coding agent from Earendil, MIT licensed, and it reached its first stable release on October 1, 2026 with v1.4.0 following on October 21. Its self-description is one line: a minimal, extensible agent harness that you can make your own. Then it says the thing that made me look twice. Pi ships with powerful defaults but deliberately skips features like sub-agents and plan mode. Ask Pi to build what you want, or install a package that does it your way. My first reaction was that this was a roadmap gap. After reading how its packages and four run modes fit together, I think it is a position.
Four extension surfaces: prompt templates, themes, skills, extensions.
During install, notice the unglamorous thing the installer does
The recommended path is a curl script on macOS and Linux, PowerShell on Windows. The installer pins all dependencies, and you update explicitly with pi update. That sounds boring until you are debugging why the same prompt behaved differently twice and discover the harness moved between runs.
The npm route exists but explicitly does not pin transitive dependencies. Add --ignore-scripts, because the docs state Pi does not need dependency lifecycle scripts for a normal install, which makes allowing them unnecessary attack surface.
Node.js 22.19 or newer is required, and the installers can supply it. Nix users can build from source with nix run github:earendil-works/pi/stable, which is reproducible by construction.
In CI, pin the version. That is the entire difference between a build that reproduces and one that fails on a Tuesday for no reason.
# Install, and note what the installer actually does: it pins dependencies.
# That sounds boring until you have debugged a harness that changed under you
# between two runs of the same prompt.
curl -fsSL https://pi.dev/install.sh | sh # macOS / Linux
# powershell -c "irm https://pi.dev/install.ps1 | iex" # Windows
pi --version
pi update # the pinned path, update explicitly
# The npm route exists but does not pin transitive dependencies:
# npm install -g --ignore-scripts @earendil-works/pi-coding-agent
#
# Requirement: Node.js 22.19 or newer. The macOS/Linux/Windows installers can
# install Node if it is missing. `--ignore-scripts` is not paranoia: Pi states
# it does not need dependency lifecycle scripts for a normal install, so
# allowing them is unnecessary surface area.
#
# Nix users get a reproducible build from source:
# nix run github:earendil-works/pi/stable
# nix profile add github:earendil-works/pi/stable
# Then, in the project you want it to work on:
cd /path/to/project
pi
# Inside, run /login to connect a subscription or an API key.
# For CI, pin the version. This is the whole difference between a build that
# reproduces and a build that fails on a Tuesday for no reason.
npm install -g @earendil-works/[email protected] --ignore-scripts
pi --version | grep -q "1.4.0" || { echo "pi version drifted" >&2; exit 1; }Skipping plan mode and sub-agents is an architectural stance
The README is unusually direct about it: powerful defaults, but no sub-agents and no plan mode. If you want them, ask Pi to build them or install a package that implements them your way.
Why I read that as a stance rather than a gap: plan mode and sub-agent orchestration are policy decisions. They encode how much an agent may do unattended and where parallelism lives. Hard-coding them means every team inherits one team's answer to a question they never got to ask.
What it gives you instead is four extension surfaces in increasing order of effort: prompt templates for reusable instruction text, themes for appearance with zero behavioural risk, skills for capabilities the agent can invoke, and extensions that hook the harness in code.
A Pi package bundles those and ships over npm or git. The mental model is npm for agent behaviour, and it carries the same consequence: your agent's behaviour becomes a dependency you version.
# The design statement is one sentence: "a minimal, extensible agent harness
# that you can make your own." Pi ships powerful defaults but deliberately
# skips sub-agents and plan mode. Ask it to build what you want, or install a
# package that does it your way.
#
# That is a real architectural position, not a gap in the roadmap. Plan mode
# and sub-agent orchestration are policy decisions: how much the agent may do
# unattended, and where parallelism lives. Hard-coding them means every team
# inherits one team's answer.
#
# Four extension surfaces, in increasing order of effort:
#
# prompt-template reusable instruction text
# theme terminal appearance; zero behavioural risk
# skill a capability the agent can invoke
# extension code that hooks the harness
#
# A Pi package bundles them and is shared over npm or git. The mental model is
# npm for agent behaviour, and it has the same consequence: your agent's
# behaviour is now a dependency you version.
mkdir -p ~/.pi/packages/team-conventions
cat > ~/.pi/packages/team-conventions/package.json <<'JSON'
{
"name": "pi-pkg-team-conventions",
"version": "0.1.0",
"pi": { "promptTemplates": ["templates"], "skills": ["skills"] }
}
JSON
mkdir -p ~/.pi/packages/team-conventions/templates
cat > ~/.pi/packages/team-conventions/templates/review-diff.md <<'MD'
Review the current diff against these rules, in order:
1. Does every changed file appear in the task description? List any that do not.
2. Is there a test that fails without this change? Name it, or say "none".
3. Any new dependency? If yes, stop and ask.
4. Any edit to an applied migration? If yes, stop and refuse.
Output: a verdict line, then at most five bullet findings. No summary paragraph.
MD
# Why this is the interesting bet: conventions become installable artefacts
# instead of an onboarding doc. A new repository gets your review rules with
# one install, and updating them is a version bump, not a Slack announcement.Four ways to drive it, and the wrong one is the usual mistake
Interactive for a human at a keyboard. Print or JSON mode for one-shot, machine-readable, scriptable runs. RPC for a long-lived process driven by another program. A TypeScript SDK for building an application on top of the harness.
Most teams start with the TUI, discover they cannot test anything, and then rewrite their integration around print mode. That detour is worth skipping deliberately.
Print mode belongs in CI and cron. JSON mode belongs in a pipeline where something else decides the next step.
Design for one failure mode up front: an agent that does nothing and exits zero. Always assert on content rather than exit status, because an empty result in a non-empty repository is a signal, not a success.
# Four ways to drive it, and picking the wrong one is the usual integration
# mistake. Most teams start with the TUI, discover they cannot test it, and
# then rewrite everything around print mode.
#
# 1. interactive a human at a keyboard
# 2. print / JSON one shot, machine readable, scriptable
# 3. RPC a long-lived process driven by another program
# 4. TypeScript SDK build an application on top of the harness
#
# Print mode is the one that belongs in CI and in cron jobs. JSON mode is the
# one that belongs in a pipeline where something else decides what to do next.
# One-shot, human readable
pi "Summarise the failing test in test/api/search.spec.ts and propose a fix"
# One-shot, JSON, for a script that parses the result
pi --json "List every file that imports the deprecated config module" \
> /tmp/imports.json
# The failure mode to design around: agents that exit 0 while doing nothing.
# Always assert on content, never on exit status alone.
python3 - <<'PY'
import json, subprocess, sys
def run(prompt):
p = subprocess.run(["pi", "--json", prompt], capture_output=True, text=True, timeout=600)
try:
return json.loads(p.stdout)
except json.JSONDecodeError:
print("non-JSON output, harness probably errored:", p.stdout[:200], file=sys.stderr)
return None
out = run("Which modules import the deprecated config module? Return file paths only.")
if not out:
sys.exit(1)
files = out.get("files") or []
print(f"found {len(files)} importers")
if not files:
# Empty result on a non-empty repository is a signal, not a success.
print("WARNING: zero importers returned; verify the prompt or the repo path", file=sys.stderr)
sys.exit(2)
PYPrint and JSON modes belong in CI. The TUI belongs in exploration.
RPC and the SDK: when the harness is a component, not a tool
Pi is a monorepo and exposes its internals as packages: chord, a typed host runtime for hot-reloadable plugins, contracts, replicated state and RPC over JSON links; pi-telemetry, with vendor-neutral telemetry contracts, a reference adapter and conformance tests; and pi-ai, a unified multi-provider LLM API.
pi-ai is quietly the most useful of the three. Provider differences are the least interesting part of any agent project, and they are the part everyone rebuilds from scratch.
In your own wrapper, enforce two things: maxTurns always, because an unconstrained agent is an unconstrained bill, and a filesystem allowlist, because "do not change source files" is a request rather than a permission boundary.
Telemetry matters more than it sounds. A neutral contract lets you swap providers without rewriting dashboards. Provider-specific spans couple your observability to your model choice.
# RPC and the SDK are for when the harness is a component rather than a
# tool. The reason to care: Pi's monorepo exposes its libraries as packages,
# so the pieces are usable separately.
#
# @earendil-works/chord typed host runtime for hot-reloadable plugins,
# contracts, replicated state, RPC over JSON links
# @earendil-works/pi-telemetry vendor-neutral telemetry contracts + adapters
# @earendil-works/pi-ai unified multi-provider LLM API
#
# That last one is quietly the most useful. Provider differences are the least
# interesting part of any agent project, and it is the part everyone rebuilds.
# SDK shape, per the official docs (see packages/coding-agent/docs/sdk.md):
cat > probe.mjs <<'JS'
import { PiAgent } from "@earendil-works/pi-coding-agent";
const agent = await PiAgent.create({
cwd: process.cwd(),
// model/provider comes from your /login, or from pi-ai config
});
const result = await agent.run(
"Add a regression test for the empty-cart total. Do not change source files.",
{ maxTurns: 12 }
);
console.log(result.summary);
console.log("tool calls:", result.toolCalls?.length ?? 0);
// Two properties worth enforcing in your own wrapper:
// 1. maxTurns, always. An unconstrained agent is an unconstrained bill.
// 2. a filesystem allowlist. "Do not change source files" is a request,
// not a permission boundary.
await agent.close();
JS
node probe.mjs
# Telemetry matters more than it sounds. If the harness emits a neutral
# contract, you can swap vendors without rewriting dashboards. If it emits
# provider-specific spans, your observability is coupled to your model choice.The cost of minimal is that you build the containment
Minimal means fewer policy defaults, which means more of the fence is your job. That is the actual trade, not "fewer features so it is lighter".
Three things you have to supply: a permission boundary that is enforced rather than requested, a diff size ceiling that fails the run, and a record of what was executed kept outside the working tree.
What I do: create a detached git worktree and let the agent work only in that copy, removing it on exit. Unset cloud and registry credentials from the environment before the run, because a coding agent that reads files reads everything your shell exported. Apply both a time limit and a turn limit and let whichever trips first end it. Then read git diff --stat instead of the agent's own summary.
Back to the stance. If what you want is an agent that runs unattended all night out of the box, Pi is not that product and does not appear to want to be. If what you want is a component that slots into your own pipeline with versionable behaviour, the complexity it removes is real. Those are different needs, and picking wrong will hurt.
#!/usr/bin/env bash
# Running a minimal harness safely. Minimal means fewer policy defaults, so
# more of the containment is yours to build. That is the actual trade.
set -euo pipefail
REPO="${1:?usage: run-pi.sh /path/to/repo}"
echo "== version pin =="
pi --version
echo "== containment: work on a copy, not the original =="
WORK="$(mktemp -d)/repo"
git -C "$REPO" worktree add --detach "$WORK" HEAD
trap 'git -C "$REPO" worktree remove --force "$WORK" 2>/dev/null || true' EXIT
cd "$WORK"
echo "== containment: no credentials in the sandbox =="
# A coding agent reads files. If your shell has AWS keys exported, so does it.
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY GITHUB_TOKEN NPM_TOKEN || true
echo "== run with hard limits =="
# Time limit AND turn limit. Whichever trips first ends the run.
pi --json "Run the test suite and fix only the failures in src/pricing/" \
> /tmp/pi-out.json 2>/tmp/pi-err.log || echo "pi exited non-zero"
echo "== review what changed, not what it said =="
git add -A
git diff --cached --stat
CHANGED=$(git diff --cached --numstat | awk '{s+=$1+$2} END {print s+0}')
echo "changed lines: $CHANGED"
[ "$CHANGED" -le 400 ] || echo "WARN: diff exceeds 400 lines"
echo "== keep the evidence =="
cp /tmp/pi-out.json /tmp/pi-err.log . 2>/dev/null || true
echo "worktree: $WORK (removed on exit)"
# The three things a minimal harness does not give you, and you must supply:
# 1. a permission boundary that is enforced, not requested
# 2. a diff size ceiling that fails the run
# 3. a record of what was actually executed, kept outside the worktreeThe cost of minimal: permission boundary, diff ceiling, execution record.
📌 Frequently Asked Questions
What is Pi, who makes it, and what licence?
Pi is a minimal, extensible terminal coding agent harness maintained by Earendil under the earendil-works organisation, MIT licensed. Pi 1.0 was the first stable release on October 1, 2026, followed by v1.4.0 on October 21. The repository is earendil-works/pi on GitHub and the docs live at pi.dev.
Does it really have no plan mode and no sub-agents?
The official README says Pi ships with powerful defaults but skips features like sub-agents and plan mode, and suggests asking Pi to build what you want or installing a package that implements it your way. So they are not built in, not impossible.
Which of the four run modes should I use?
Interactive when a human is exploring at a keyboard; print or JSON mode for CI, cron jobs and pipelines that parse output; RPC when another long-lived program drives the agent; the TypeScript SDK when you are building an application on top of the harness.
Why install with --ignore-scripts?
The docs state Pi does not require dependency lifecycle scripts for a normal npm installation, so disabling them does not break the install and removes supply-chain surface. The recommended curl and PowerShell installers also pin every dependency, which is more reproducible than the npm route.
What safety measures does a minimal harness leave to me?
At minimum: run inside a detached git worktree and clean it up afterwards; strip cloud and code-host credentials from the environment before the run; set both a time limit and a turn limit; and verify what changed with git diff --stat rather than trusting the agent's own report.
🔧 Recommended Tools
Agent Sandbox Guide
A minimal harness means you build the fence; start with the boundary.
Local LLM Runner
Point the harness at a local model and keep code inside your network.
Prompt Template Library
Turn team conventions into a reusable prompt template.
Text Diff
Read what actually changed instead of the agent's summary.
Code Complexity Analyzer
Pick a defensible threshold for your diff size ceiling.