Files
calvana/PI_VS_OPEN_CODE.md
2026-02-26 15:55:17 -06:00

30 KiB

Pi Agent vs OpenCode — Customization & Control Comparison

Pi v0.52+ vs OpenCode v1.1+ (Feb 2026)

Thesis: Pi and OpenCode are both MIT-licensed, open-source, model-agnostic terminal coding agents. But they represent fundamentally different architectures. Pi is a programmable platform — a minimal harness with 25+ in-process TypeScript hooks that let you build your own agent experience. OpenCode is a configurable product — a full-featured Claude Code alternative with JSON-driven settings and a plugin system for extras. The distinction matters: Pi gives you control at the runtime level. OpenCode gives you control at the configuration level.


The Core Architectural Split

Dimension Pi Agent OpenCode
What it ships 4 tools, ~200-token system prompt, 25+ extension events. Everything else is opt-in. 12+ tools, built-in sub-agents, Plan mode, LSP, web search, MCP, permissions, desktop app.
Extension model In-process TypeScript. Extensions run in the same runtime as the agent loop. They can intercept, block, modify, and transform any event in real-time. Out-of-process plugins. JS/TS files in a config directory that subscribe to events and register tools.
Customization ceiling Effectively unlimited — you can replace the entire UI, override any tool, inject custom system prompts per-turn, build full overlay applications (Doom, Space Invaders, QA tools), and orchestrate multi-agent pipelines. Bounded — you can add tools, hook into tool execution, customize compaction, and configure permissions via JSON. But you cannot modify the TUI, inject custom UI components, or intercept the input/agent lifecycle at the same depth.
Philosophy "If I don't need it, it won't be built. Build what you need." "Ship a polished, complete product. Configure what you need."
Closest analogy A race car chassis + engine. You design the body, aero, and electronics. A production car with a tuning package. You adjust settings and bolt on accessories.

Extension / Plugin System — Deep Comparison

This is the single most important comparison between these two tools. Pi's extension system is architecturally different from OpenCode's plugin system — not just in API surface, but in where code runs and what it can touch.

Architecture

Feature Pi Extensions OpenCode Plugins Winner
Runtime model In-process — extensions execute in the same Bun/Node.js runtime as the agent loop. Zero serialization overhead. Direct access to session state, UI context, and the event stream. Separate module — plugins are loaded from .opencode/plugins/ or npm. They receive a context object and return hook handlers. Communication happens through the SDK client. Pi
Build step None — TypeScript executed via jiti at runtime. Write .ts, run immediately. None — TypeScript supported natively via Bun loader. Tie
Composability Stack multiple extensions with -e flags: pi -e ext1.ts -e ext2.ts. Extensions can communicate via pi.events shared bus. Multiple plugins loaded from directory. No built-in inter-plugin communication channel. Pi
Ephemeral testing pi -e npm:@foo/bar — try a package without installing Not possible — must add to config or plugin directory Pi

Event Coverage

This is where the gap is widest. Pi exposes 25+ typed events across 7 categories. OpenCode exposes ~20 events but skips critical lifecycle hooks.

Lifecycle Point Pi Extension Event OpenCode Plugin Hook Gap Analysis
Session starts session_start session.created Comparable
User submits prompt input — can block, transform text, or handle entirely Not available Pi-only. This is huge — Pi can gate all user input, inject context, redirect prompts, or prevent execution before the agent ever sees it. OpenCode has no equivalent.
Before agent processes prompt before_agent_start — can modify system prompt, images, prompt text per-turn Not available Pi-only. Dynamic system prompt injection on every turn. The purpose-gate extension uses this to inject session intent into every agent call.
Agent turn lifecycle agent_start, agent_end, turn_start, turn_end session.idle (only fires when agent finishes) Pi has 4x granularity. OpenCode only knows when the agent is done, not when turns start/end within a session.
Before tool executes tool_call — block with reason, modify args, typed per-tool via isToolCallEventType() tool.execute.before — can modify args, throw to block Both can intercept. Pi has typed narrowing per tool (bash, read, write, edit).
After tool executes tool_result — modify results, log, transform output tool.execute.after — react to results Comparable
Tool execution streaming tool_execution_start, _update, _end Not available Pi-only. Real-time streaming of tool output as it happens — critical for building live progress UIs.
Bash spawn intercept BashSpawnHook — modify command, cwd, env vars before the process spawns shell.env — inject env vars into shell execution Pi intercepts at process spawn level. OpenCode only injects env vars.
Message streaming message_start, message_update, message_end — token-by-token access message.part.updated, message.updated Both have message events; Pi is more granular with token-level streaming.
Model changed model_select (source: set/cycle/restore) Not available Pi-only. React to model switches programmatically.
Context window access context — deep copy of all messages, can filter and prune Not available Pi-only. Direct manipulation of what's in the context window. No other tool offers this.
Before compaction session_before_compact — can replace compaction logic entirely experimental.session.compacting — inject context or replace prompt Both can customize. Pi can replace the entire compaction flow; OpenCode can replace the prompt.
Session branching session_before_fork, session_fork, session_before_switch, session_switch, session_before_tree, session_tree Not applicable (no branching model) Pi-only. Pi's JSONL tree session format supports forking/branching. OpenCode uses linear SQLite sessions.
Permission events Not applicable (YOLO by default) permission.asked, permission.replied OpenCode-only — but Pi can build equivalent or better permission systems via tool_call blocking.
LSP events Not built-in lsp.client.diagnostics, lsp.updated OpenCode-only — native LSP integration.
File watcher Not built-in file.watcher.updated OpenCode-only.
Todo events Not built-in todo.updated OpenCode-only.

Summary: Pi has 8+ hook points that OpenCode simply doesn't expose, including the critical input, before_agent_start, agent lifecycle, tool execution streaming, context window access, and session branching hooks. These aren't minor — they're the hooks you need to build fundamentally different agent behaviors.

UI Customization

This is the other dimension where Pi is in a different category entirely.

Feature Pi Extensions OpenCode Plugins Winner
Custom header ctx.ui.setHeader() — replace the logo and keybinding hints with any content Not possible Pi
Custom footer ctx.ui.setFooter() — git branch, token stats, cost tracking, tool counters, anything Not possible Pi
Status line ctx.ui.setStatus() — themed colors, turn tracking, custom data Not possible Pi
Widgets ctx.ui.setWidget(key, renderFn) — persistent UI panels above/below the editor. Used for subagent progress, task lists, tool counters, purpose display. Not possible Pi
Overlays Full overlay applications — session replay timeline, game overlays (Doom), QA tools Not possible Pi
Dialogs ctx.ui.select(), confirm(), input(), editor() — interactive prompts with custom rendering Not available to plugins (only built-in permission dialogs) Pi
Custom editors vim modal editor, emacs bindings, rainbow editor — all via extensions Not possible Pi
Notifications ctx.ui.notify() from any handler osascript or desktop app notifications via plugin Both
Theme system 51 color tokens, hot-reload, dark/light, custom themes via packages Theme customization via tui.json Pi

OpenCode's TUI is polished but closed. It's built with Bubble Tea (Go) and the Ink React renderer, and it looks great out of the box. But you can't inject custom UI components, widgets, or overlays into it from a plugin. What you see is what you get.

Pi's TUI is a canvas. The extensions API gives you full control over every UI surface — header, footer, status line, widgets above/below the editor, fullscreen overlays, and interactive dialogs. The pi-vs-claude-code repo demonstrates this with 16 extensions that completely transform the agent experience.

Registration APIs

What you can register Pi OpenCode
Custom tools pi.registerTool() — in-process, streaming results, custom rendering tool() helper in plugins — Zod schema, execute function
Override built-in tools Register tool with same name → replaces built-in Plugin tool with same name → takes precedence
Custom slash commands pi.registerCommand() + prompt templates in .pi/prompts/ Markdown files in .opencode/commands/
Custom CLI flags pi.registerFlag() — adds flags to the pi CLI Not possible
Custom keyboard shortcuts pi.registerShortcut() Configurable via tui.json keybinds (JSON, not code)
Custom providers pi.registerProvider() with OAuth support JSON config in opencode.json with npm AI SDK packages
Persistent extension state pi.appendEntry() — survives restarts, stored in session JSONL Not available to plugins
Inter-extension communication pi.events shared event bus Not available

What OpenCode Does Better (And Why It Still Matters)

The thesis isn't "Pi is better." It's "Pi is more controllable." OpenCode has real advantages that come from its product-first approach:

Feature Why OpenCode Wins Pi's Alternative
Batteries included 12+ tools, LSP, web search, MCP, permissions, todo tracking — zero configuration needed. You install and start coding. You build what you need with extensions, or install pi packages. Higher ceiling, higher floor.
Built-in Plan mode Tab to switch between Build (full access) and Plan (read-only). No setup. No plan mode. "Just tell the agent to think." Or build it with an extension.
Native MCP support stdio and remote MCP servers configured in JSON. First-class integration. Not built-in by design (argues 7-14K token overhead). Available via extensions.
Permission system Granular allow/deny/ask with glob patterns per tool, per agent, per command pattern. .env files denied by default. Doom loop detection. YOLO by default. The damage-control extension builds equivalent functionality — but you have to build or install it.
LSP integration go-to-definition, find references, hover, document symbols, call hierarchy — all built in. Not available. Would need to be built as an extension.
Client/server architecture TUI is one client. Desktop app, VS Code extension, web UI, and mobile are others. opencode serve exposes an HTTP API. Terminal only. RPC mode over stdin/stdout for programmatic access, but no multi-client architecture.
Desktop app Native app for macOS, Windows, Linux. No.
GitHub/GitLab integration Comment @opencode on issues/PRs. GitLab Duo OAuth. Via bash.
Massive community 104K stars, 735 contributors, 9,200+ commits. Issues get fixed fast. ~8.9K stars (pi-mono), solo maintainer + approved contributors. Smaller but passionate community.
Organizational config .well-known/opencode endpoint for enterprise defaults. Not available.

Concrete Examples: What Pi's Control Enables

These aren't theoretical. They're actual extensions in the pi-vs-claude-code repo.

1. Purpose Gate (Input Interception + Dynamic System Prompts + Custom Widgets)

Forces the engineer to declare session intent before any work begins. Uses input to block all prompts until purpose is set, before_agent_start to inject purpose into the system prompt on every turn, and setWidget to display it persistently.

Could OpenCode do this? Partially. You could create a custom agent with a specific prompt, but you can't block user input, you can't inject per-turn system prompts, and you can't add a persistent widget to the UI.

2. Damage Control (Safety Auditing via tool_call Interception)

Intercepts every tool call, checks against YAML-defined rules (dangerous bash patterns, zero-access paths, read-only paths, no-delete paths), and blocks or prompts for confirmation. Uses typed narrowing (isToolCallEventType) to extract tool-specific args.

Could OpenCode do this? Partially — OpenCode's tool.execute.before can throw to block tool calls, and the built-in permission system covers common cases. But Pi's version is fully programmable with custom rules and custom UI (confirm dialogs, status line updates, persistent logging via appendEntry).

3. Subagent Widget (Multi-Agent Orchestration with Live UI)

/sub spawns background Pi processes as sub-agents. Each gets its own persistent session file, live-streaming progress widget, and independent model. /subcont continues a subagent's conversation. Widgets stack in the UI showing real-time status.

Could OpenCode do this? OpenCode has built-in subagents (@general, custom agents), but you can't add live-updating widgets to the UI, you can't show streaming progress from multiple agents simultaneously, and you can't continue a specific subagent's conversation across turns.

4. Agent Team (Dispatcher Orchestration with Grid Dashboard)

The primary agent becomes a pure dispatcher — it reads your prompt, picks a specialist from a YAML roster, and delegates via a dispatch_agent tool. A grid dashboard widget shows all agents and their status.

Could OpenCode do this? OpenCode has custom agents, but the dispatcher pattern with a grid dashboard UI and real-time status widgets isn't possible through plugins.

5. Theme Cycler (Full TUI Theming)

Ctrl+X/Ctrl+Q keyboard shortcuts cycle through custom themes. /theme command opens a selector. Hot-reload with 51 color tokens.

Could OpenCode do this? OpenCode has themes via tui.json, but no keyboard shortcuts for cycling and no extension-level theme registration.


The OpenCode Clone Thesis

You asked whether OpenCode is more of a "Claude Code copycat." The evidence supports this framing. Here's what Claude Code ships vs. what OpenCode ships vs. what Pi ships:

Feature Claude Code OpenCode Pi
Built-in sub-agents Native Task tool, 7 parallel General subagent + custom agents Build with extensions
Plan mode Built-in plan mode Tab to switch to Plan agent "Just tell the agent to think"
Permission system 5 modes, deny-first allow/deny/ask with glob patterns YOLO by default
MCP support Native, first-class Native, stdio + remote Not built-in
Web search Built-in Built-in (Exa AI) Build with extensions
LSP integration Not built-in Native Not built-in
IDE extensions VS Code, JetBrains VS Code Terminal only
Desktop app Desktop app Desktop app (beta) Terminal only
AGENTS.md / CLAUDE.md CLAUDE.md AGENTS.md AGENTS.md (or CLAUDE.md)
Slash commands .claude/commands/ .opencode/commands/ .pi/prompts/ + extensions
Skills Agent Skills standard SKILL.md loading Agent Skills standard
Session sharing /export to HTML /share creates link /export to HTML
GitHub Actions bot Native @opencode bot Not built-in
Todo tracking Built-in Built-in Build with extensions

OpenCode systematically reproduced Claude Code's feature set, added LSP support and a desktop app, swapped the single-provider lock-in for model-agnostic support, and open-sourced it under MIT. It's a very good execution of this strategy.

Pi rejected the feature set entirely and built a minimal, extensible harness that trusts the engineer to compose their own agent experience. It's a different bet — that the right set of primitives (events, UI APIs, tool registration, session branching) is more valuable than a pre-built feature set.


Summary: The Decision Framework

Choose Pi if you are building a custom agent workflow — you want to control input interception, dynamic system prompts, UI components, multi-agent orchestration with live dashboards, safety auditing with custom rules, or anything that requires programmatic control over the agent loop. Pi's extensions are code that runs inside the agent. The ceiling is whatever you can build in TypeScript.

Choose OpenCode if you want a production-ready Claude Code replacement — you need LSP, MCP, permissions, Plan mode, sub-agents, web search, GitHub integration, a desktop app, and a massive community shipping updates daily. OpenCode's plugin system handles common customization needs (tool hooks, custom tools, notifications, compaction), and the JSON-driven config covers agent definitions, permissions, and provider setup without writing code.

The one-line version: Pi is a platform. OpenCode is a product. Both are open source. The question is whether you want to build your experience or configure an existing Claude Code like experience.