10x Design in Claude Code and Codex

TL;DR
The practical guide to earendil-works/pi: verified install and auth steps, all four run modes from TUI to SDK, JSONL session trees with branch, fork and resume, and the rough edges nobody advertises.
Last updated: August 23, 2026
The first post in this series covered why pi's architecture matters: a small core, a unified multi-provider LLM API, and everything else pushed into extensions. This is the hands-on layer. Every command below was pulled verbatim from the official documentation at pi.dev and the GitHub repository on August 23, 2026, and anything composed or adapted rather than copied is labeled as such.
| Source | What it covers |
|---|---|
| earendil-works/pi on GitHub | Main repo - MIT license, 95.9k stars and 11.9k forks as of August 23, 2026 |
| pi.dev/docs/latest | Documentation index - quickstart, usage, providers, sessions |
| Session Format reference | The JSONL file format and SessionManager API |
| SDK, RPC mode, JSON event stream | The three programmatic surfaces |
| Extensions and Pi Packages | Customization and distribution |
Pi ships as an npm package. The documented install command is:
npm install -g --ignore-scripts @earendil-works/pi-coding-agentThe --ignore-scripts flag disables dependency lifecycle scripts during install. The docs note pi does not require install scripts for normal npm installs, so nothing breaks without them. On Linux or macOS there is also a curl installer:
curl -fsSL https://pi.dev/install.sh | sh
To uninstall, use whichever tool installed it:
npm uninstall -g @earendil-works/pi-coding-agent
Uninstalling leaves settings, credentials, sessions, and installed pi packages in place under ~/.pi/agent/.
Then run it inside a project directory:
cd /path/to/project
pi
By default the model gets four built-in tools: read, write, edit, and bash. Three more read-only tools (grep, find, ls) are available through tool options. There is no built-in approval popup before those tools run - more on that in the gaps section.
Pi reuses existing paid plans through OAuth where providers allow it. In interactive mode, run /login and pick a provider. Built-in subscription logins include:
Tokens land in ~/.pi/agent/auth.json and auto-refresh when expired. One billing detail worth reading twice: the providers doc states that Anthropic subscription auth works for Claude Pro/Max accounts, but third-party harness usage draws from extra usage and is billed per token, not against Claude plan limits. If you assume your Max plan absorbs it, you will be surprised.
The API-key route needs one environment variable before launch:
export ANTHROPIC_API_KEY=sk-ant-...
pi
You can also store keys in auth.json via /login; that file is created with 0600 permissions and takes priority over environment variables. Resolution order when pi looks for credentials: the CLI --api-key flag first, then auth.json, then environment variables, then custom provider keys from models.json. The key field supports shell-command lookups like "!security find-generic-password -ws 'anthropic'" and environment interpolation like "$MY_ANTHROPIC_KEY" - both verbatim patterns from the providers doc.
Launch pi bare and you get the terminal interface. Four areas matter: the startup header (loaded context files, templates, skills, extensions), the message transcript, the editor (its border color tracks the current thinking level), and a footer showing working directory, session name, tokens, cost, context usage, and model.
The editor shortcuts that change how you work day to day:
| Feature | How |
|---|---|
| Reference files | Type @ to fuzzy-search project files, Tab completes paths |
| Shell command into context | !command runs it and sends output to the model |
| Hidden shell command | !!command runs without sending output to the model |
| External editor | Ctrl+G opens $VISUAL, $EDITOR, or nano |
| Images | Paste with Ctrl+V or drag into the terminal |
Message queueing is the underrated feature: pressing Enter while the agent works queues a steering message delivered after the current turn finishes its tool calls, and Alt+Enter queues a follow-up delivered only when the agent stops. Escape aborts and restores queued messages.
Sessions persist automatically. The session flags from the usage doc:
pi -c # Continue most recent session
pi -r # Browse and select a session
pi --no-session # Ephemeral mode; do not save
pi --name "my task" # Set session display name at startup
pi --session <path|id> # Use a specific session file or session ID
pi --fork <path|id> # Fork a session into a new session file
Model switching supports provider prefixes and thinking-level shorthands, both straight from the docs' examples:
pi --model openai/gpt-4o "Help me refactor"
pi --model sonnet:high "Solve this complex problem"
And a read-only review pass looks like this:
pi --tools read,grep,find,ls -p "Review the code"
For scripts, -p prints a response and exits. Print mode also merges piped stdin into the initial prompt:
cat README.md | pi -p "Summarize this text"
When plain text is not enough, --mode json emits every session event as JSON lines on stdout:
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'
That jq filter is the documentation's own example. The first stdout line is the session header, followed by lifecycle events (agent_start, turn_start, message_start, tool_execution_start) and streaming deltas. Two details from the JSON mode doc worth knowing before you build on it: message_update records are delta-only (they omit the cumulative snapshot to keep stream size linear, and message_end carries the authoritative final message), and the top-level usage field may stay zero until completion because some providers only report usage at the end.
pi --mode rpc [options]
RPC mode speaks newline-delimited JSON over stdio: commands go in on stdin, responses with "type": "response" and asynchronous events come out on stdout. Common startup options include --provider, --model, --no-session, and --session-dir.
A minimal exchange looks like:
{"id": "req-1", "type": "prompt", "message": "Hello, world!"}
{"id": "req-1", "type": "response", "command": "prompt", "success": true}
The command set covers the whole agent surface: prompt, steer, follow_up, abort, set_model, set_thinking_level, compact (with optional customInstructions), set_auto_compaction, set_auto_retry, plus session operations we care about later - get_entries, get_tree, fork, clone, get_fork_messages, and switch_session. All commands accept an optional id field for request/response correlation.
One protocol trap the docs call out explicitly: RPC uses strict JSONL framing split on \n only, and Node's readline module is not compliant because it also splits on the Unicode separators U+2028 and U+2029, which are valid inside JSON strings. Use a manual buffer-and-split reader instead.
The RPC doc includes a complete basic Python client, reproduced here exactly:
import subprocess
import json
proc = subprocess.Popen(
["pi", "--mode", "rpc", "--no-session"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True
)
def send(cmd):
proc.stdin.write(json.dumps(cmd) + "\n")
proc.stdin.flush()
def read_events():
for line in proc.stdout:
yield json.loads(line)
# Send prompt
send({"type": "prompt", "message": "Hello!"})
# Process events
for event in read_events():
if event.get("type") == "message_update":
delta = event.get("assistantMessageEvent", {})
if delta.get("type") == "text_delta":
print(delta["delta"], end="", flush=True)
if event.get("type") == "agent_end":
print()
break
For TypeScript, the maintainers point at src/modes/rpc/rpc-client.ts for a typed client and recommend using the in-process AgentSession class directly rather than spawning a subprocess when you are already in Node.
From the archive
Aug 23, 2026 • 10 min read
Aug 23, 2026 • 7 min read
Aug 23, 2026 • 9 min read
Aug 22, 2026 • 7 min read
npm install @earendil-works/pi-coding-agentThe SDK ships in the main package. The SDK doc's quick-start sample, reproduced exactly:
import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
const modelRuntime = await ModelRuntime.create();
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
modelRuntime,
});
session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What files are in the current directory?");
AgentSession exposes prompt(), steer(), followUp(), subscribe(), navigateTree() for in-file branching, compact(), and abort(). A wider createAgentSessionRuntime() layer handles session replacement - newSession(), switchSession(), fork() - and the same runtime factory backs pi's own interactive, print, and RPC modes. The package also exports its tool factories (createReadTool, createBashTool, createGrepTool, createFindTool, createLsTool and friends), which is exactly what lets you embed pi's execution layer inside another program - a pattern from the community section below.
When to pick which: the SDK doc says use the SDK for type safety and same-process access; use RPC from other languages or when you want process isolation; use JSON mode when you just need structured events out of a one-shot run.
This is the capability almost no competing agent ships, and it deserves its own walkthrough.
Sessions auto-save to ~/.pi/agent/sessions/, organized by working directory. The documented path pattern is:
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
Each file is JSONL. The first line is a header with no id/parentId:
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}
Every following entry carries an 8-character hex id and a parentId. Message entries wrap an AgentMessage:
{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
Other entry types include model_change, thinking_level_change, compaction, branch_summary, label (user bookmarks), custom (extension state, excluded from LLM context), custom_message (extension-injected content that IS sent to the model), and session_info (display names). Version history matters if you parse old files: v1 was linear, v2 introduced the tree, v3 renamed the hookMessage role to custom, and older sessions migrate automatically on load.
Because every entry points at a parent, a session file is a tree and the current position is just the active leaf. The sessions doc draws it like this:
├─ user: "Hello, can you help..."
│ └─ assistant: "Of course! I can..."
│ ├─ user: "Let's try approach A..."
│ │ └─ assistant: "For approach A..."
│ │ └─ user: "That worked..." ← active
│ └─ user: "Actually, approach B..."
│ └─ assistant: "For approach B..."
Three commands operate on that tree, and they do different things - this table is lifted from the docs:
| Feature | /tree | /fork | /clone |
|---|---|---|---|
| Output | Same session file | New session file | New session file |
| View | Full tree | User-message selector | Current active branch |
| Typical use | Explore alternatives in place | Start a new session from an earlier prompt | Duplicate current work before continuing |
| Summary | Optional branch summary | None | None |
Inside /tree, arrow keys navigate, Shift+L sets a label, Shift+T toggles label timestamps, and Ctrl+O cycles filter modes (default, no-tools, user-only, labeled-only, all). Selection behavior is deliberate: picking a user message moves the leaf to that message's parent and drops its text in the editor so you can edit and resubmit - that resubmission creates the new branch. Picking an assistant or tool entry moves the leaf there and lets you continue from that point.
Both mechanisms reuse one structured summary format (Goal, Constraints, Progress, Key Decisions, Next Steps, Critical Context, plus tracked read/modified file lists). Auto-compaction triggers when contextTokens > contextWindow - reserveTokens, with reserveTokens defaulting to 16384 and keepRecentTokens defaulting to 20000 - both tunable:
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
A compaction writes a compaction entry storing the summary and either a firstKeptEntryId or, in newer sessions, a retainedTail array that acts as a self-contained checkpoint so context rebuilds without walking old entries. When you switch branches via /tree, pi offers to summarize the abandoned branch and attach a branch_summary entry at the new position - your choice of no summary, the default prompt, or custom focus instructions.
The SessionManager API (full list in the session-format reference) includes SessionManager.open(path) / .create(cwd) / .continueRecent(cwd) / .forkFrom(sourcePath, targetCwd), plus instance methods getTree(), getBranch(fromId), getChildren(parentId), branch(entryId), branchWithSummary(entryId, summary), and buildContextEntries(). Over RPC, get_tree returns the nested node structure with the current leafId, and get_entries accepts a since cursor - pass the last entry id you saw and get only newer entries, even across client restarts. The docs describe entry ids as durable cursors, which is exactly what you want for building external UIs on top of a live session.
Extensions are TypeScript modules loaded through jiti, so no compilation step. Drop them in ~/.pi/agent/extensions/*.ts (global) or .pi/extensions/*.ts (project-local, loaded only after you trust the project), or point at anything with pi -e ./my-extension.ts. Here is the extensions doc's quick-start sample, reproduced exactly - it wires an event hook that blocks destructive bash calls, registers a custom tool, and adds a slash command:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// React to events
pi.on("session_start", async (_event, ctx) => {
ctx.ui.notify("Extension loaded!", "info");
});
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
}
});
// Register a custom tool
pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
parameters: Type.Object({
name: Type.String({ description: "Name to greet" }),
}),
async execute(toolCallId, params, signal, onUpdate, ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: {},
};
},
});
// Register a command
pi.registerCommand("hello", {
description: "Say hello",
handler: async (args, ctx) => {
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
},
});
}
That is the real API shape: pi.on() for lifecycle events (the docs chart around thirty, from before_agent_start to session_before_compact), mutable event.input in tool_call hooks, { block: true } returns for gating, ctx.ui.confirm/select/input/notify for dialogs, and pi.appendEntry() for state that survives restarts.
Distribution uses pi packages. The documented install forms:
pi install npm:@foo/bar@1.0.0
pi install git:github.com/user/repo@v1
pi install https://github.com/user/repo # raw URLs work too
pi list # show installed packages from settings
pi update --all # update pi and packages
Versioned specs are pinned and skipped by updates. A package declares resources in package.json under a pi key (extensions, skills, prompts, themes) or just uses conventional directories; adding the pi-package npm keyword lists it in the gallery at pi.dev/packages. Runtime dependencies go in dependencies (installs run production npm install), while pi's own bundled packages must be listed as peerDependencies with a "*" range.
The ecosystem around this is growing fast, and Herdr integrations are the busiest corner. Verified example: pi-yahe (marv1nnnnn/pi-yahe, "Yet Another Herdr Extension") installs with pi install npm:pi-yahe, requires Pi 0.83+ and Herdr 0.7.5+, and exposes one composable herdr tool so the current agent can spawn visible worker agents in Herdr panes and receive their results back automatically. Its README shows workers shaped per task with native pi flags rather than a new profile format:
{
"action": "run_pi",
"label": "auth boundary scan",
"prompt": "Trace authentication trust boundaries. Cite files and lines. Do not edit files.",
"piArgs": ["--model", "sonnet:high", "--tools", "read,grep,find,ls"]
}
Its README also maps the neighboring packages honestly: @ogulcancelik/pi-herdr for typed Herdr primitives, @andrewjacop/pi-herdr for cross-agent fleet orchestration, pi-herdr-subagents for resumable subagents with automatic result steering, pi-herdr-squad for enforced read-only investigation squads, and pi-herdr-btw for conversation fork and merge. We did not independently verify each of those repositories beyond their appearance in pi-yahe's comparison table.
Three real workflows from public threads, attributed as published:
A native client on top of headless pi. On Show HN (August 16, 2026), HN user polyglotfacto described filing pi issue #7730 about the macOS TUI - CPU load as sessions grow large, shifting history making copy-paste unreliable - then writing uni03C0 over a weekend: a macOS-native client driving headless pi through RPC mode, with rendering optimizations and OS-native sandboxing applied by default. It is the clearest public demonstration that the RPC surface is complete enough to replace the bundled UI entirely.
Pi's tool layer as someone else's MCP server. In an Ask HN post (May 17, 2026), user jakemattison described importing pi's exported tool factories - he lists createReadToolDefinition, createWriteToolDefinition, createGrepToolDefinition, createFindToolDefinition, createLsToolDefinition from @earendil-works/pi-coding-agent - registering them as MCP tools behind a Cloudflare Worker, and giving every AI tool he uses access to one persistent filesystem workspace. His post predates the current exports page, which now lists createReadTool and siblings; treat his snippet as his implementation, not current API spelling.
Policy layers assembled from extensions. In the Hacker News thread on "Pi's Minimalism Is Its Advantage" (551 points, August 4, 2026), user ptgamr described a permission-sandbox setup built from three community extensions in the erichll/pi-packages repo, including an approved escape hatch out of the sandbox, and noted network sandboxing was still an open gap tracked in that repo's issue tracker. The same thread's overall read, summarized well by commenter zdp7: pi is a starter kit whose job is letting you build personal workflows, not dictating one.
~/.pi/agent rather than XDG paths on Linux, linked to GitHub issue #534. Cosmetic, but it annoys people.Yes - MIT licensed, and the harness itself has no paid tier. You pay whatever your model provider charges, whether that is API keys or subscription usage drawn through OAuth logins.
Yes, via /login. ChatGPT Plus/Pro (Codex) is officially endorsed per the docs; for Claude Pro/Max, be aware usage is billed per token from extra usage rather than drawn against your plan limit.
/tree branches inside the same JSONL file so alternatives stay together. /fork starts a new session file from an earlier user message. /clone duplicates the current active branch into a new file before you keep going.
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl, one JSONL tree file per session, organized by working directory. Delete the file to delete the session, or press Ctrl+D in the /resume picker, which prefers the trash CLI when available.
Not built in - that is an explicit design decision, alongside sub-agents and background bash. You can bridge MCP yourself (community members have wrapped pi's tools as MCP endpoints) or find a package that does.
Use pi -p "prompt" for plain-text one-shots, pi --mode json for structured events you can filter with jq, or pi --mode rpc --no-session for full bidirectional control. Non-interactive modes skip the project trust prompt and fall back to your defaultProjectTrust setting.
TypeScript, loaded through jiti without a compile step. An extension exports a default function receiving an ExtensionAPI object with on(), registerTool(), registerCommand(), and dialog methods on ctx.ui.
When context exceeds the window minus a 16,384-token reserve, pi summarizes older turns into a compaction entry, keeps roughly the last 20,000 tokens verbatim, and rebuilds context from the summary forward. Branch switches can attach similar summaries of the path you left behind. Both are customizable through extension hooks like session_before_compact.
Pi rewards the hour you spend wiring it to your setup: subscriptions reused through /login, four run modes covering everything from a REPL to an embedded SDK, and session trees that make experimentation cheap instead of destructive. Start with the TUI, graduate your repetitive loops to print mode, and only reach for RPC or the SDK once you know which slice of the agent you actually need. For the architectural why behind all of this, read the series opener on pi's toolkit design; for adjacent automation recipes, see our guides to OpenCode cron automations, browser-driven Claude Code workflows, recurring Codex engineering work, and running an agent fleet on Herdr. The series closes next time with pi compared head-to-head against the other coding CLIs.
Read next
How a one-developer protest against bloated coding harnesses became a 95,000-star agent toolkit: pi's five-package architecture, branching JSONL session trees, four run modes, and the philosophy that refuses to build sub-agents, plan mode, or MCP.
10 min readAn agent CLI plus a cron schedule turns recurring dev chores into background work: dependency bumps, doc freshness checks, morning briefs. The pattern, the guardrails, and where to run it - your own hardware or a cloud host.
11 min readClaude Code can now control Chrome using your existing authenticated sessions. No API keys needed. Gmail, Sheets, Figma - your agent works across tabs like you do.
7 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.
Open-source terminal agent runtime with approval modes, rollback snapshots, MCP servers, LSP diagnostics, and a headless...
View ToolAnthropic's agentic coding CLI. Runs in your terminal, edits files autonomously, spawns sub-agents, and maintains memory...
View ToolGives AI agents access to 250+ external tools (GitHub, Slack, Gmail, databases) with managed OAuth. Handles the auth and...
View ToolLocal-first markdown knowledge base with wikilinks. My entire DevDigest pipeline lives here - research, scripts, conte...
View ToolFree SSL + DNS + domain expiry monitor with email/Slack alerts. One paste, lifetime peace of mind.
View AppAnswer a few task questions and get a practical model recommendation with cost and latency tradeoffs.
View AppCoordinate launch content, social, email, and community work without dropping the sequence.
View AppInstall Claude Code, configure your first project, and start shipping code with AI in under 5 minutes.
Getting StartedDeep comparison of the top AI agent frameworks - LangGraph, CrewAI, Mastra, CopilotKit, AutoGen, and Claude Code.
AI AgentsStage, commit, branch, and open PRs without leaving the session.
Claude Code
Check out CopilotKit on GitHub at https://go.copilotkit.ai/copilotkit to view the demo + more featured in this video. While you're there, star ⭐️ their repository and support open source....

In this video, I demonstrate Claude Code, a tool by Anthropic currently in limited research preview. This enables developers to delegate tasks directly from the terminal. I walk through installatio...

In this video, I showcase an innovative application I built using generative AI to create custom APIs. I'll guide you through its configuration, functionality, and underlying technology. You...

How a one-developer protest against bloated coding harnesses became a 95,000-star agent toolkit: pi's five-package archi...

An agent CLI plus a cron schedule turns recurring dev chores into background work: dependency bumps, doc freshness check...

Claude Code can now control Chrome using your existing authenticated sessions. No API keys needed. Gmail, Sheets, Figma...

Codex automations are useful when recurring engineering work has clear inputs, reviewable outputs, and safe boundaries....

The hands-on guide to running a fleet of coding agents on Herdr: verified install and config steps, three fleet patterns...

An Ask HN reply asked what Herdr fills that pi and plain tmux scripts don't already cover. We compared all three against...

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.