Pi's Minimalist Agent Harness: Trading Features for Runtime Control
This article analyzes Pi's minimalist agent harness architecture, contrasting its four-tool core with Claude Code, Codex, and DSH, detailing its session tree, dual-loop execution, context compaction, and the trade-offs of pushing workflows to extensions for greater runtime controllability.
Comparing Agent Harness Approaches
The author examines Pi, Claude Code, Codex, and DSH — four agent runtimes that all face the same question: how much work should the outer program handle once the model starts calling tools, modifying files, and running long tasks? The difference is not merely feature count but which state the system persists, which actions the system decides, and which tasks remain for the user to compose.
Pi deliberately separates product workflows from the core agent runtime. Its default tools are only read, write, edit, and bash. MCP, sub-agents, planning mode, background Bash, and permission dialogs are excluded from the default path; they can be added via Extensions, Skills, CLI, project files, or external sandboxes. This is almost the opposite of Claude Code, which bundles loops, tools, permissions, context, and common workflows into a ready-to-use workbench. Codex focuses on a stable execution process with explicit ownership of model, thread, turn, tool execution, approval, and events, and recently experimented with notes, history, active window switching, and budget control for long tasks. DSH pushes replaceability further: model, files, process, session, and even the agent loop itself have formal capability slots assembled by plugins and runtime.
Core Code Handles Only a Few Concerns
Pi's monorepo is cleanly split: pi-ai handles model provider interfaces, pi-agent-core handles agent messages, tool calls, and events, pi-coding-agent composes coding scenarios, and pi-tui provides the terminal UI. Higher-level workflows attach at the application and extension layers without polluting lower-level contracts. Each package can be used independently.
Despite few default tools, tool execution is not casual. After the model returns a tool call, Pi performs parameter pre-processing and validation, can intercept before execution, and can modify results afterward. If streaming output hits the token limit and tool parameters are only half-generated, the entire batch is rejected rather than executing malformed JSON. These internal steps — what the model received, what the system prepared to execute, what the tool actually executed, and how the result returned to the next context — are traceable for debugging.
The Loop Is Not a Straight Line
A naive agent loop: call model, execute tool, put result back, repeat. In reality, users may interject, tools may run in parallel, tasks may queue after the current turn, and the loop must handle cancellation, failure, and early termination. Pi's agent-loop.ts splits this into inner and outer layers. The inner layer revolves around the current turn: issue model request, receive streaming response, execute tools, put results back into context. If a steering message arrives, the current run can adjust direction. The outer layer checks the followUp queue after the turn ends; if there are appended tasks it continues, otherwise it emits agent_end. A separate nextRun consumes inputs meant for the next run. Separating steer, followUp, and nextRun prevents "change direction now", "continue after current task", and "handle on next startup" from crowding the same queue.
Internally the model uses AgentMessage. Before calling the provider, Pi runs convertToLlm to transform runtime-enriched internal messages into the provider's format; provider differences stay in the adapter layer, so the loop and tools need not know each API's details. Tool batches may execute in parallel, but results are persisted in the order the model gave them. Events are emitted via an event stream; the terminal UI only subscribes and renders, avoiding duplicate business state in pi-tui.
The loop's stop conditions are explicit: natural end when no tool calls, cleanup on model error or abort, tools or hooks can request termination, current batch results may trigger end, and after the inner layer finishes the followUp queue is checked. When the user presses Ctrl+C, completed tool results are still written to history before the model request follows the unified abort path, preventing a session stuck with half a result.
Session Persists What Happened
Long-running agents cannot treat the current context as the database. Context gets compressed, branches switch, and models and tools change; if history is only a summary, many things become unverifiable. Pi's Session uses JSONL, but each entry is not a simple chat message. Entries carry id, seq, parentId, timestamp, and besides message can record model switches, thinking levels, active tools, compaction summaries, branch summaries, and extension data.
Meanwhile, Operation records the run process: when a run started and ended, how many attempts a step made, when tools launched, when queues were enqueued or cancelled, whether delayed writes finished, and token usage. Together these two logs answer both "what happened" and "how did we get here this run".
The context the model actually sees next turn is a projection from the current branch — a working set. buildSessionContext walks the current path reading entries, restores model, thinking level, and active tool state, then assembles messages, compaction summaries, branch summaries, and extension content into the context. Full history stays; current working set is generated on demand. This is the key to Pi's session tree. Using /tree to return to an old node or /fork from a node does not copy text into a new chat; it continues on a different path over the same history.
This differs from Codex's notes, history, and new-window approach. Pi keeps branches, entries, and summaries in one session tree; Codex's experimental path separates handoff info and historical lookup more distinctly. Both face the same reality: the model's visible context can be rebuilt, but the facts the task has already produced cannot rely solely on the model's memory.
Compaction Does Not Delete Old Messages
When context nears the window limit, Pi uses provider-reported usage or character-count estimation to decide whether to compact. The default configuration reserves 16384 tokens for the summary request and output, and tries to keep the most recent ~ 20000 tokens. The cut point cannot be arbitrary: toolResult cannot be a cut point, otherwise tool call and result would be split. If the region to compact falls mid-task, the code identifies that task's start, generates a summary for the prefix alone, then merges it with the existing historical summary.
After compaction, original entries remain in the session. A new compaction entry is added recording the summary, pre-compaction token count, retained tail, and files read/written. Next context build gives the model the summary plus recent content, not all old messages again. Branch summaries follow the same idea: switching branches, Pi finds the nearest common ancestor of the old leaf and target node, summarizes only the departing path, and records goal, constraints, progress, key decisions, next steps, and files read and modified on that branch. Summaries lose information, so they cannot replace raw history; they only bring the model back to a checkpoint where it can continue working. Verification still requires the original session records.
Where the Complexity Goes When Features Stay Out
Pi keeps MCP, sub-agents, planning mode, and background tasks out of the default core because they add tool descriptions, state, and control paths that not every task needs. Mario Zechner notes Playwright MCP's 21 tools consume ~ 13.7K tokens and Chrome DevTools MCP's 26 tools ~ 18K tokens. Pi prefers letting the agent read a README and use CLI and Bash when needed. This does not mean CLI is universally better than MCP; when stable structured protocols, fine-grained authorization, and service governance are required, MCP still has value. Pi simply does not amortize that cost onto every request by default.
Planning files follow the same philosophy: plans go in PLAN.md, tasks in TODO.md, readable by both humans and models, visible to Git. The trade-off is the project must maintain its own conventions; the system will not automatically coordinate state for all tasks. Functionality not in the core does not make complexity disappear. It moves to extensions, CLI, project files, and external runtime environments. For teams building their own workflows this is more flexible; for users wanting zero-setup it adds preparation steps.
Security cannot be waved away with "add a hook later". Pi defaults to the launching process's file, process, network, and credential permissions, with no built-in process sandbox. Extensions and built-in tools run within the Pi process's permission scope. beforeToolCall can do confirmation, allow-lists, and auditing, but it is not process isolation. For untrusted repos, sensitive credentials, or restricted networks, file mounts, network egress, credential injection, and rollback should be handled by Gondolin, Docker, OpenShell, or other environments that truly constrain processes.
Putting the Source Code Back Into Daily R&D
Based on the latest Pi commit f4d802c303cd4ab8cb1fd10d76c6207e217547f0, AgentHarness already bundles model, thinking level, active tools, Skills, Prompt Templates, queues, retries, compaction, run modes, and session projection into a fairly complete contract. However, this contract is not fully implemented: current prompt, compact, navigateTree, resume, queue operations, and parts of hooks and events still return HarnessNotImplemented. It is a forming runtime interface, not a product with all control capabilities ready.
Defining the relationships among model, tools, session, queues, and recovery first, then incrementally filling implementations, avoids redefining state every time a feature is added. Architecting "who persists, who executes, who can interrupt, where to recover after failure" upfront is more robust than filling the menu from day one.
Evaluating a harness across Pi, Codex, DSH, and Claude Code comes down to concrete questions: Are provider differences isolated in an adapter layer? Do model context, tool execution, and run events each have clear records? Are full history and current working set separated? Can project rules be read and edited directly by humans? Does the security policy land where processes and networks are actually controlled? There is no single answer. Putting permissions, background tasks, planning, and sub-agents all in the core saves users effort but makes the runtime heavier; pushing them all outside keeps the core cleaner but shifts assembly and governance work to the project itself.
Pi's trade-offs do not suit every product, but they surface an often-ignored question: only the parts that truly must run on every invocation belong in the core; workflows serving only certain tasks should stay in extensions, project files, or external environments, where they are easier to adjust and easier to retract later.
References
Pi official repo: https://github.com/earendil-works/pi
Pi Coding Agent docs: https://github.com/earendil-works/pi/tree/main/packages/coding-agent
Pi containerization docs: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/containerization.md
Mario Zechner: "What I learned building an opinionated and minimal coding agent": https://mariozechner.at/posts/2025-11-30-pi-coding-agent/
Mario Zechner: "What if you don't need MCP at all?": https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/
Pi open-source session announcement: https://x.com/badlogicgames/status/2037811643774652911
Pi open-source session dataset: https://huggingface.co/datasets/badlogicgames/pi-mono
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
