General

The Three Kingdoms of CLI Coding Agents: Claude Code vs Pì vs OpenCode

A deep source-code comparison of three terminal coding agents across philosophy, architecture, and autonomy strategies

A deep source-code comparison of three terminal coding agents across philosophy, architecture, and autonomy strategies

Who Are They

2025 was the breakout year for CLI Coding Agents. From Anthropic’s official Claude Code, to the open-source project Pì (formerly Ampcode) led by libgdx creator Mario Zechner, to OpenCode built by the SST team — all three projects are answering the same question: How do you make an LLM a real coding partner in your terminal?

But their answers couldn’t be more different.


I. Core Philosophy: Three Worldviews

Claude Code — “The Structured Workflow Engine”

Claude Code’s design philosophy is process-driven. It breaks programming tasks into strict, phased workflows. Take its feature-dev plugin as an example — developing a new feature is divided into seven stages:

Discovery → Exploration → Clarification → Architecture → Implementation → Review → Summary

Each stage has well-defined inputs and outputs, and key checkpoints (like entering Implementation) require explicit user approval before proceeding. This isn’t an on-call assistant — it’s more like a technical consultant with a methodology. It understands your codebase first, proposes a plan, and only starts coding after you give the green light.

Claude Code ships with rich multi-agent collaboration mechanisms: code-explorer scans the codebase in parallel, code-architect designs architecture proposals, and code-reviewer uses confidence scoring to filter false positives. These agents can run concurrently, with the main agent aggregating the results.

In one line: Claude Code believes structure produces quality.

Pì — “The Radically Extensible Minimal Kernel”

Pì’s philosophy is stated on the first line of its README:

“Pi is aggressively extensible so it doesn’t have to dictate your workflow.”

Pì’s core is extremely restrained. Look at what it deliberately doesn’t do:

FeaturePì’s Stance
MCP Support”Build CLI tools with READMEs, or build an extension”
Sub-agents”Spawn pi instances via tmux, or build your own with extensions”
Permission Popups”Run in a container, or build your own confirmation flow”
Plan Mode”Write plans to files, or build it with extensions”
TODOs”They confuse models. Use a TODO.md file”
Background Bash”Use tmux. Full observability, direct interaction”

This isn’t laziness — it’s a radical design choice: the core provides only primitives; all higher-level features are implemented through extensions. The kernel has just 7 built-in tools (read, bash, edit, write, grep, find, ls), but through the Extension API you can intercept 20+ events across the entire agent lifecycle, register custom tools, commands, keybindings, and even inject UI components.

In one line: Pì believes users understand their own workflow better than any framework.

OpenCode — “The Full-Stack Product Built on Open Standards”

OpenCode takes the product-oriented route. Its core keywords are: open source, multi-platform, protocol-first.

Architecturally, OpenCode uses a client/server separation: the core engine runs as a server (opencode serve), while the TUI, Web UI, and Desktop App (Tauri) are all clients. This means you can remotely connect to an OpenCode instance running on a server and operate it from your local browser.

It actively embraces standard protocols: native MCP (Model Context Protocol) support for external tool integration, ACP (Agent Client Protocol) support so it can be invoked as an agent by other systems, and out-of-the-box LSP support.

For LLM integration, OpenCode is the most open of the three — connecting to 20+ model providers through the Vercel AI SDK, including Anthropic, OpenAI, Google, Mistral, Groq, and more. It even uses Tree-sitter to parse bash commands and adapt to behavioral differences across models.

In one line: OpenCode believes open standards are the right path to production-grade tooling.


II. Core Architecture: Three Engineering Choices

Tech Stack Overview

Claude CodeOpenCode
LanguageTypeScriptTypeScriptTypeScript
RuntimeNode.jsNode.js / BunBun
Package Managernpmnpm workspacesBun
UI FrameworkInk (React)Custom pi-tuiSolidJS + OpenTUI
LLM IntegrationAnthropic APICustom pi-ai (20+ providers)Vercel AI SDK
Data PersistenceFile SystemFile System (Session JSON)SQLite (Drizzle ORM)
Extension SystemHooks + PluginsExtensions + Skills + PackagesPlugins + Custom Tools

Claude Code: Plugin Is Everything

Claude Code’s extension unit is the Plugin — a directory containing hooks, commands, agents, and skills. Its Marketplace mechanism includes 12+ official plugins:

claude-code/
├── plugins/
│   ├── feature-dev/        # 7-stage feature development workflow
│   ├── code-review/        # Multi-agent parallel code review
│   ├── ralph-wiggum/       # Self-referential loop (autonomous agent)
│   ├── hookify/            # Auto-generate hooks from conversation patterns
│   ├── security-guidance/  # Security pattern detection (9 vulnerability patterns)
│   └── plugin-dev/         # Plugin development toolchain

The Hook system is Claude Code’s security cornerstone. Hooks use Python/Shell scripts to intercept before and after tool execution, and can either warn or block:

# security_reminder_hook.py - PreToolUse interception
patterns = [
    ("command injection", r"child_process\.exec"),
    ("XSS", r"dangerouslySetInnerHTML"),
    ("eval injection", r"\beval\s*\("),
    ("pickle deserialize", r"pickle\.load"),
]
# Match found → exit code 2 → block tool execution

This “declarative security” approach is clever: you don’t need to write code to understand security rules. The Hookify plugin can even automatically extract patterns from your conversations with Claude and generate hooks.

Pì: Extension Is the Operating System

Pì’s Extension system is more like an OS-level API. An Extension is a TypeScript module that can do virtually anything through ExtensionAPI:

export default function (pi: ExtensionAPI) {
  // Intercept tool calls
  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 custom tools
  pi.registerTool({
    name: "deploy",
    description: "Deploy to production",
    parameters: Type.Object({ version: Type.String() }),
    async execute(toolCallId, params, signal, onUpdate, ctx) {
      return { content: [{ type: "text", text: "Deployed!" }], details: {} };
    },
  });

  // Register commands, keybindings, UI components...
}

Worth noting is Pì’s operations abstraction. Every built-in tool’s underlying operation (file I/O, bash execution) is a replaceable interface:

interface BashOperations {
  exec(command: string, cwd: string, options: {
    onData: (data: Buffer) => void;
    signal?: AbortSignal;
    timeout?: number;
  }): Promise<{ exitCode: number | null }>;
}

This means you can swap the bash tool’s backend to SSH remote execution, Docker container execution, or even a WebAssembly sandbox — completely transparent to the LLM.

The examples directory contains 30+ extension samples, from permission-gate, protected-paths, and git-checkpoint, to — no joke — playing Snake and Space Invaders in the terminal (snake.ts, space-invaders.ts). There’s even a doom-overlay that renders DOOM gameplay while the agent executes tools. That’s the Pì ethos: serious core, wild extensions.

OpenCode: A Frontend-Backend Separated Agent Product

OpenCode’s architecture most closely resembles a traditional software product:

opencode/packages/
├── opencode/     # Core engine (Server)
│   ├── src/
│   │   ├── session/     # Session management (SQLite)
│   │   ├── permission/  # Permission system
│   │   ├── tool/        # Tool registration
│   │   ├── agent/       # Agent definitions
│   │   ├── mcp/         # MCP integration
│   │   └── cli/cmd/tui/ # TUI (SolidJS)
├── app/          # Web UI (SolidJS)
├── desktop/      # Desktop app (Tauri)
├── plugin/       # Plugin SDK
└── sdk/js/       # JS SDK

The TUI is built with the SolidJS reactive framework paired with a custom OpenTUI rendering engine, supporting theme switching, a dialog system, and keybinding configuration. Agent switching uses the Tab key to freely toggle between build (full permissions) and plan (read-only) modes.

The choice of SQLite for persistence is worth mentioning — compared to the file system approaches of Claude Code and Pì, SQLite has natural advantages in session querying, branch management, and state rollback, at the cost of slightly higher deployment complexity.


III. The YOLO Philosophy: Three Trust Models

This is where the three projects diverge the most. “YOLO” (You Only Live Once) in the Coding Agent context means: How much human confirmation does the agent require when executing operations?

Claude Code: Secure by Default, Explicit Opt-out

Claude Code’s default stance is conservative. Every tool call has a permission classification:

// settings-strict.json
{
  "permissions": {
    "disableBypassPermissionsMode": "disable",
    "ask": ["Bash"],
    "deny": ["WebSearch", "WebFetch"]
  }
}
  • allow: Auto-execute
  • ask: Requires user confirmation
  • deny: Always reject

Want to “let it rip”? There’s an explicit flag whose name is itself a warning:

claude --dangerously-skip-permissions

The brilliance of this design is that the naming itself is a security mechanism — it’s hard to say with a straight face in a team Slack channel, “I turned on dangerously-skip-permissions and then things went wrong.” Moreover, this flag can be completely disabled in enterprise managed settings.

Additionally, Claude Code has a layered safety net:

  1. Tool-level permissions: Commands can declare which tools they need (e.g., allowed-tools: Bash(git commit:*))
  2. Hook-level interception: PreToolUse hooks can block any dangerous operation
  3. Sandbox mode: sandbox.autoAllowBashIfSandboxed auto-allows in sandboxed environments

Claude Code’s trust model: Distrust is the default; trust must be explicitly declared.

Pì: Born YOLO, Build Your Own Safety

Pì takes the most radical approach: the core has no permission system whatsoever.

There’s no --yolo flag because there doesn’t need to be — everything is allowed by default. If the LLM says execute rm -rf /, Pì executes rm -rf /. The README is candid and direct about this:

“No permission popups. Run in a container, or build your own confirmation flow with extensions inline with your environment and security requirements.”

If you need safety gates, that’s on you. The project provides a permission-gate.ts example extension as reference:

// Example, not a built-in feature
pi.on("tool_call", async (event, ctx) => {
  if (event.toolName !== "bash") return;
  const command = event.input.command as string;
  const isDangerous = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i]
    .some(p => p.test(command));
  if (isDangerous) {
    if (!ctx.hasUI)
      return { block: true, reason: "Dangerous command blocked (no UI)" };
    const choice = await ctx.ui.select("⚠️ Dangerous command. Allow?", ["Yes", "No"]);
    if (choice !== "Yes") return { block: true, reason: "Blocked by user" };
  }
});

Or you can simply restrict the tool set:

pi --tools read,grep,find,ls -p "Review this code"  # Read-only mode
pi --no-tools                                         # Disable all tools

The logic behind this design: permission models vary by person and by scenario. A CI job running in a Docker container and local development on your laptop need completely different security policies. Rather than providing a “good enough” built-in solution, let users build exactly what fits their situation.

Pì’s trust model: Trust is the default; safety must be actively built by the user.

OpenCode: Declarative Rules, Smart Defaults

OpenCode takes the middle road, but with the most refined implementation. The permission system is based on declarative rulesets with wildcard matching:

// permission/next.ts
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
  const merged = merge(...rulesets)
  const match = merged.findLast(
    (rule) => Wildcard.match(permission, rule.permission)
           && Wildcard.match(pattern, rule.pattern)
  )
  return match ?? { action: "ask", permission, pattern: "*" }
  // No matching rule → default to ask
}

The default permission config reflects a pragmatic philosophy:

const defaults = PermissionNext.fromConfig({
  "*": "allow",                    // Most operations: allow
  doom_loop: "ask",                // Doom loop detection: ask
  external_directory: { "*": "ask" }, // Directories outside project: ask
  read: {
    "*": "allow",                  // Read files: allow
    "*.env": "ask",                // But .env files: ask
    "*.env.*": "ask",              // .env.local etc.: ask
  },
})

Furthermore, OpenCode’s permissions are layered by agent. The build agent has full permissions, while the plan agent is hardcoded to deny edits:

// plan agent
edit: { "*": "deny" }   // Cannot modify files
plan_exit: "allow"       // Can exit plan mode

User confirmations have three response options:

  • once: Allow this time
  • always: Allow all similar operations going forward (within the session)
  • reject: Deny

The Bash tool also uses Tree-sitter to parse command syntax, extracting directory paths being accessed and triggering external_directory permission checks for paths outside the project. This is far more precise than simple regex matching.

OpenCode’s trust model: Smart defaults + fine-grained configurability + role-based isolation.


IV. Feature Focus: Three Product Positions

Claude Code: Enterprise-Grade Workflows

Claude Code’s feature design centers on team and enterprise scenarios:

  • Structured development workflows (feature-dev’s seven stages)
  • Multi-agent parallel review (code-review uses 6 specialized agents scanning in parallel, only reporting findings with confidence ≥ 80)
  • Enterprise governance (managed settings, marketplace allowlisting, permission tiers)
  • Security compliance (security-guidance detects 9 common vulnerability patterns)

A unique highlight is the ralph-wiggum plugin: a self-referential loop mechanism that lets Claude iterate on the same prompt until the task is complete. A Stop hook intercepts exit behavior and reinjects the prompt, with a max-iterations safety limit. Reportedly, during Y Combinator testing, it autonomously generated 6 repositories overnight.

Pì: The Developer’s Swiss Army Knife

Pì’s feature design centers on individual developer flexibility:

  • Seamless cross-provider switching (switch models mid-conversation, thinking blocks auto-converted to text)
  • Tree-structured session branching (Git-style session management with fork, navigate, and rollback)
  • Message queuing (inject guidance messages while the agent executes tools, auto-append follow-ups after completion)
  • SDK mode (embed in Node.js apps for programmatic control)
  • Pi Packages (install extension packages via npm/git, activate with one line of config)

A unique highlight is the operations abstraction — every tool’s underlying implementation is swappable, letting you extend the agent’s “hands” to SSH remote servers, Docker containers, or custom sandboxes, while the upper-layer logic remains completely unchanged.

OpenCode: UX-First Full-Stack Product

OpenCode’s feature design centers on product experience:

  • Polished TUI (SolidJS reactive rendering, theme system, dialog system)
  • Unified multi-platform (TUI + Web + Desktop sharing the same server and UI components)
  • Quick agent switching (Tab key to jump between build/plan modes)
  • Protocol-first (MCP, ACP, LSP out of the box)
  • Config priority chain (remote .well-known → global → env vars → project → managed, 7-level override)

A unique highlight is semantic understanding of bash commands — using Tree-sitter to parse command ASTs, identifying the source and dest paths in cp source dest rather than simple string matching. This enables the permission system to understand semantic-level information like “this command is accessing a directory outside the project.”


V. The Full Picture at a Glance

DimensionClaude CodeOpenCode
Core PhilosophyStructured WorkflowsMinimal Kernel + Maximum ExtensibilityFull-Stack Product on Open Standards
Target UsersEnterprise / TeamsAdvanced DevelopersBroad Developer Audience
Default SecurityHigh (requires confirmation)Low (everything allowed)Medium (smart defaults)
YOLO Mechanism--dangerously-skip-permissionsYOLO by defaultDeclarative rule configuration
Extension DepthMedium (Hooks + Plugins)Extreme (OS-level API)Medium (Plugins + Custom Tools)
Built-in AgentsMany (explorer/architect/reviewer)0 (via extensions)4 (build/plan/general/explore)
MCP SupportYesNo (via extensions)Yes (native)
Multi-platformCLI onlyCLI + SDK + RPCCLI + Web + Desktop
LLM Lock-inAnthropic-firstCompletely unlockedUnlocked (Vercel AI SDK)
Session ManagementLinearTree-structured branchingFork/Revert/Compact
Data StorageFile SystemFile SystemSQLite

Final Thoughts

The three projects represent three distinct directions in the CLI Coding Agent design space:

Claude Code is “Rails” — opinionated, structured, with best practices baked in. Ideal for teams that need predictability and security guarantees. The trade-off is flexibility constrained by framework design.

is “Arch Linux” — giving you the smallest kernel and the greatest freedom. Ideal for power users who know exactly what they want. The trade-off is that everything must be built from scratch.

OpenCode is “VS Code” — delivering a polished default experience through a product-oriented approach while maintaining sufficient openness. The trade-off is that it’s not the most extreme in any single dimension.

Which one should you pick? It depends on your answer:

  • Want out-of-the-box enterprise workflows → Claude Code
  • Want full control over every detail → Pì
  • Want a multi-platform, multi-model, protocol-first open platform → OpenCode

Or, like all good engineering decisions: try all three, then pick the one that best fits your current needs.