# Pi Agent Harness source analysis

> **A self-extensible coding-agent monorepo architecture** · TypeScript · npm workspaces · 9 core packages · 1034 TS files
>
> commit `main` · MIT · 1034 TS/TSX
>
> Every claim is marked `file:line`, traceable to concrete source.

---

## I. Project overview

### 1.1 What Pi is

Pi is not another terminal coding agent. It is **infrastructure for a self-extensible coding agent**—a monorepo that splits “Agent” into 9 core npm packages, glued by explicit contracts:

| Package | What it is | File count |
|---|---|---|
| **pi-ai** | unified multi-provider LLM API (38 providers) | 305 |
| **pi-agent-core** | Agent runtime (two-layer while loop + tools + state) | 62 |
| **pi-coding-agent** | interactive coding-agent CLI (assembly layer) | 488 |
| **pi-tui** | terminal UI library (differential rendering) | 74 |
| **pi-protocol** | binary wire protocol (CBOR + length framing) | 12 |
| **pi-server** | Unix-socket session server | 38 |
| **pi-client** | client SDK (lease model) | 18 |
| **pi-storage-sqlite-node** | SQLite event sourcing (harness `SessionStore`; **not** the CLI default path) | 12 |
| **pi-evals** | agent-behavior evaluation framework | 14 |

**Its relationship to Claude Code**: Claude Code is a monolith CLI, all capability stuffed into one process. Pi splits the Agent into protocol, server, client, storage, runtime, UI six layers, each an independent package—**protocol-driven composable agent infrastructure**.

**Its relationship to CodePilot**: CodePilot is an Electron + Next.js desktop client; Pi is a terminal CLI. CodePilot outsources three Runtimes to others; Pi wrote all of the core itself. CodePilot has no protocol layer; Pi has an explicit binary protocol.

### 1.2 The project shape in numbers

| Dimension | Number | Note |
|---|---|---|
| TS/TSX files | 1034 | excluding node_modules |
| npm packages | 8 | workspaces monorepo |
| Providers | 38 | OpenAI/Anthropic/Google/Bedrock/DeepSeek… |
| Built-in tools | 7 | read/write/edit/bash/grep/find/ls |
| TUI components | 17+ | box/text/markdown/editor/scroll-view… |
| Extension hooks | 30+ | tool_call/before_provider_request/session_before_compact… |
| Slash commands | 22 | /settings /model /fork /tree /compact /share… |
| Evals | 2 | smoke + extensions |
| License | MIT | fully open source |

### 1.3 Three most distinctive designs

1. **Self-extensible**: Extensions dynamically load TS files with `jiti`, 30+ event hooks + `registerTool/registerCommand/registerShortcut/registerFlag`. The agent can add tools, commands, shortcuts to itself at runtime.

2. **Protocol-driven process split**: `pi-protocol` (CBOR + length framing + TypeBox validation + token auth) decouples server and client. Multiple clients can attach to the same session; the server does snapshot broadcast.

3. **Differential-rendering TUI**: `pi-tui` has no React, no Virtual DOM, no flexbox—a component is `render(width): string[]`, rendering diffs line-by-line strings and only paints changed lines, with `\x1b[?2026h/l` synchronized refresh.

### 1.4 The fundamental difference from Claude Code

| Dimension | Claude Code | Pi |
|---|---|---|
| Architecture | monolith CLI | monorepo (9 core packages) |
| Protocol | none (in-process calls) | binary wire protocol (CBOR) |
| Agent loop | single-layer while | two-layer while (follow-up + tool-call) |
| Provider | locked to Anthropic | 38 providers |
| TUI | Ink (React + Yoga full redraw) | home-grown differential rendering |
| Permissions | five-gate verdict | not built in (containerization) |
| Extension | plugins + MCP | Extensions + Skills (MCP external; official No MCP) |
| Persistence | JSONL files | CLI default JSONL (`SessionManager`); also provides SQLite SessionStore |
| Sessions | one process, one session | multi session + attach/detach + fork |
| License | MIT | MIT |

---

## II. Panorama

### 2.1 Package-dependency topology

```
pi-protocol  ←  pi-server  ←  pi-client
                  ↑                  ↓
           (PiSessionBackend)  (SessionHandle)
                  ↑                  ↓
pi-storage(sqlite)           pi-coding-agent  ←  pi-evals
                                  ↑
                            pi-agent-core  ←  pi-ai
                                  ↑
                              pi-tui
```

- `pi-protocol` is the bottom-most contract, shared by server/client/storage three parties
- `pi-server` implements `PiSessionBackend`/`PiSessionRuntime` interfaces; the runtime is actually provided by coding-agent
- `pi-client` is the server’s peer SDK, mirroring protocol commands
- `pi-storage` implements `pi-agent-core`’s `SessionStore`, used by coding-agent’s `SessionManager`
- `pi-evals` stands at the top, treating coding-agent as the object under test

### 2.2 Process topology

```
┌──────────────────────────────────────────────────────┐
│  pi-server (常驻 daemon)                              │
│  ├── Unix domain socket 监听器                        │
│  ├── LiveSessionManager (多 session 并发)              │
│  ├── PiSessionBackend → AgentSession (coding-agent)   │
│  ├── JSONL SessionManager（CLI 默认）/ SQLite SessionStore（harness） │
│  └── 快照广播 (revision 化)                            │
└────────────────────┬─────────────────────────────────┘
                     │ CBOR + 长度帧 (pi-protocol)
┌────────────────────▼─────────────────────────────────┐
│  pi-client (SDK)                                      │
│  ├── Connection (握手 + 状态机)                       │
│  ├── SessionHandle (租约模型: shared/exclusive)       │
│  └── ClientState (本地快照镜像 + 监听器)              │
└────────────────────┬─────────────────────────────────┘
                     │
┌────────────────────▼─────────────────────────────────┐
│  pi-coding-agent (交互式 TUI / print / rpc)          │
│  ├── InteractiveMode (pi-tui 差分渲染)                │
│  ├── AgentSession (← pi-agent-core Agent)            │
│  ├── Extensions (jiti 动态加载, 30+ 钩子)             │
│  ├── Skills (SKILL.md + agentskills.io)              │
│  ├── Tools (read/write/edit/bash/grep/find/ls)       │
│  └── .pi/ 资源目录 (extensions/skills/prompts/themes) │
└──────────────────────────────────────────────────────┘
```

### 2.3 Data flow: from user input to AI response

```
用户输入 → pi-tui Input 组件
  → InteractiveMode.handleInput()
  → AgentSession.prompt(text)
  → AgentHarness.prepareContext()
    ├── Session.buildContext() (消息树 → 线性消息)
    ├── SystemPrompt 重建
    ├── Skills 注入 (<available_skills> XML)
    └── Compaction 检查
  → Agent.runWithLifecycle()
    → runLoop() (双层 while)
      ├── streamAssistantResponse() → pi-ai Models.streamSimple()
      │   → lazyStream → requireProvider → applyAuth → provider.streamSimple()
      │   → AssistantMessageEventStream (start → text_delta → toolcall → done)
      ├── executeToolCalls() (parallel: Promise.all)
      │   ├── prepareToolCall (validate + beforeToolCall hook)
      │   ├── execute (tool.execute)
      │   └── finalize (afterToolCall hook)
      ├── prepareNextTurn() (harness: 重建 context + compaction)
      └── shouldStopAfterTurn()
  → AgentEventSink → AgentSession → pi-tui 重绘
  → SessionManager JSONL 持久化（CLI 默认；harness 可用 SQLite SessionStore）
```

---

## III. pi-ai: unified multi-provider LLM API

### 3.1 38 providers

`packages/ai/src/providers/all.ts:87-128`’s `builtinProviders()` / `KnownProvider` export **38** providers:

amazon-bedrock, anthropic, azure-openai-responses, cerebras, cloudflare-ai-gateway, cloudflare-workers-ai, deepseek, fireworks, github-copilot, google, google-vertex, groq, huggingface, kimi-coding, minimax, mistral, moonshotai, nvidia, openai, openai-codex, opencode, openrouter, qwen-token-plan, radius, together, vercel-ai-gateway, xai, xiaomi, zai, etc.

### 3.2 Unified interface

Core abstractions in `models.ts`:

- **`Provider<TApi>`** (`models.ts:75`): `{id, name, baseUrl, auth, getModels(), stream(), streamSimple()}`
- **`Models`** (`models.ts:127`): provider container + auth resolution + unified `stream/streamSimple/complete/completeSimple`
- **`ModelsImpl.streamSimple()`** (`models.ts:512`): `lazyStream` → `requireProvider` → `applyAuth` → `provider.streamSimple()`

10 KnownApi (`types.ts:16-26`):
- `openai-completions` / `openai-responses` / `azure-openai-responses` / `openai-codex-responses`
- `anthropic-messages` / `bedrock-converse-stream`
- `google-generative-ai` / `google-vertex`
- `mistral-conversations` / `pi-messages`

Each API is implemented in `src/api/<name>.ts`, exporting `stream` and `streamSimple` two `StreamFunction`s. Provider files are extremely thin (e.g. `openai.ts` only 15 lines), only responsible for `createProvider({id, auth, models, api: openAIResponsesApi()})`.

### 3.3 lazyStream: load on demand

`api/lazy.ts:46`’s `lazyStream`:

- Synchronously returns a stream; asynchronously runs setup in the background (auth resolution, dynamic import)
- `lazyApi()` (`lazy.ts:68`) wraps dynamic `import()`—**provider implementations load on demand**
- The main bundle contains no SDK
- `anthropic-messages.ts` is as high as 1351 lines (including `@anthropic-ai/sdk` calls, cache_control, thinking), but only loads when used

### 3.4 Relationship to Vercel AI SDK

**None.** pi-ai is an independent implementation, neither depending on nor compatible with `@ai-sdk/*`. pi-ai defines its own message/tool/event protocol and implements each provider’s SDK adaptation itself.

### 3.5 models.generated.ts

Only 2 `.generated.ts`: `models.generated.ts` (118 lines) and `image-models.generated.ts` (609 lines). Generated by `scripts/generate-models.ts`, aggregating 38 `providers/<name>.models.ts` exports into a `MODELS` dictionary. Real model metadata lives in `providers/data/<name>.json`.

---

## IV. pi-agent-core: Agent runtime

### 4.1 Core files

| File | Lines | Note |
|---|---|---|
| `agent-loop.ts` | 792 | bottom-layer stateless agent loop |
| `agent.ts` | 577 | stateful Agent class (queue + lifecycle) |
| `types.ts` | 437 | core types |
| `harness/agent-harness.ts` | 1185 | upper-layer AgentHarness |
| `harness/types.ts` | 980 | harness-layer types |
| `harness/session/session.ts` | 528 | Session tree + context construction |
| `harness/compaction/compaction.ts` | 880 | context compaction |
| `proxy.ts` | 367 | LLM HTTP `streamProxy` (proxies a remote model stream; **not** MCP) |

### 4.2 Two-layer while agent loop

`agent-loop.ts:155-275`’s `runLoop()`:

```typescript
// 外层：follow-up 消息
while (true) {
  // 内层：tool-call 循环
  while (hasMoreToolCalls || pendingMessages.length > 0) {
    // 1. 流式拉取 assistant 响应
    streamAssistantResponse() // → streamFn → AssistantMessageEventStream
    
    // 2. 错误/截断检查
    if (stopReason === "error" || "aborted") break
    if (stopReason === "length") failToolCallsFromTruncatedMessage()
    
    // 3. 执行工具（parallel: Promise.all）
    executeToolCalls()
    
    // 4. prepareNextTurn 钩子（compaction、刷新 system prompt）
    prepareNextTurn?.()
    
    // 5. shouldStopAfterTurn 钩子
    if (shouldStopAfterTurn?.()) break
  }
  
  // 外层：检查 follow-up
  const followUps = getFollowUpMessages?.()
  if (!followUps?.length) break
}
```

**The essential difference from Claude Code**:

| Dimension | Claude Code | pi-agent-core |
|---|---|---|
| Loop structure | single-layer while | two-layer while (follow-up + tool-call) |
| Stream attach | call Anthropic SDK directly | inject via `StreamFn` (provider-agnostic) |
| Tool execution | serial | default parallel (`Promise.all`) |
| Control flow | hard-coded | parameterized by 4 callback hooks |
| Message model | provider-native | `AgentMessage` + `CustomAgentMessages` (declaration merging) |

### 4.3 Tool interface

`AgentTool<TParameters, TDetails>` (`types.ts:380-403`) extends pi-ai’s `Tool`:

```typescript
interface AgentTool<TParameters, TDetails> {
  label: string
  prepareArguments?(input): TransformedInput  // schema 验证前兼容垫片
  execute(toolCallId, params, signal, onUpdate): Promise<ToolResult>
  executionMode?: "sequential" | "parallel"
}
```

Execution pipeline (`agent-loop.ts:600-754`):
1. `prepareToolCall`: find the tool → `prepareArguments` → `validateToolArguments` → `beforeToolCall` hook (can block)
2. `executePreparedToolCall`: call `tool.execute`, supporting `onUpdate` streaming partial results
3. `finalizeExecutedToolCall`: `afterToolCall` hook (can override content/details/isError/usage/terminate)

**Errors do not throw**—wrapped with `createErrorToolResult`, following the contract “errors are encoded into a toolResult message.”

### 4.4 Three-layer state management

1. **`AgentContext`** (`types.ts:406`): a pure snapshot, `{systemPrompt, messages, tools}`, snapshot every turn
2. **`Agent` class** (`agent.ts:171`): holds `MutableAgentState`, `subscribe()` listens to events, `steer()`/`followUp()` go through `PendingMessageQueue`
3. **`AgentHarness`** (`agent-harness.ts:173`): on top of `Agent` stacks Session persistence, compaction, skills, prompt templates, provider hooks

### 4.5 AgentMessage: an open union type

`AgentMessage = Message | CustomAgentMessages[keyof ...]` (`types.ts:319`)—extend custom messages via declaration merging (the harness injects `bashExecution`/`custom`/`branchSummary`/`compactionSummary`).

`convertToLlm` (`messages.ts:120`) projects these custom messages into LLM-understandable `user` messages or filters them out. This is one of the largest architectural differences between pi and Claude Code—**UI/storage can use rich messages while the LLM only sees standard messages**.

---

## V. pi-coding-agent: interactive coding agent

### 5.1 Entry and modes

- **CLI entry**: `src/cli.ts` (20 lines) → `main(process.argv.slice(2))`
- **Main dispatch**: `src/main.ts` (917 lines), core function `main()` at `L521`
- **Three modes**: `interactive` (TUI) / `print` (one-shot output) / `rpc` (JSON-RPC over stdio for IDE integration)

### 5.2 Built-in tools

`src/core/tools/index.ts:83` defines 7 tools: `read | bash | edit | write | grep | find | ls`

- Factory functions: `createCodingTools` (read/bash/edit/write), `createReadOnlyTools` (read/grep/find/ls), `createAllTools`
- Each tool injects the underlying fs/exec implementation via an `Operations` interface—easy for an extension to replace (e.g. Gondolin)

### 5.3 Permission system

Pi **does not build in a permission system**. `README.md:39`:

> Pi does not include a built-in permission system for restricting filesystem, process, network, or credential access.

Three alternatives (`docs/containerization.md`):
1. **Gondolin extension**: route tools into a local Linux micro-VM (QEMU)
2. **Plain Docker**: run the whole pi process in a container
3. **OpenShell** (NVIDIA): policy sandbox

### 5.4 The .pi/ directory

`config.ts:491` defines `CONFIG_DIR_NAME = ".pi"`:

- **Global** `~/.pi/agent/`: `auth.json`, `models.json`, `settings.json`, `themes/`, `prompts/`, `sessions/`, `extensions/`, `skills/`
- **Project** `<cwd>/.pi/`: `extensions/`, `skills/`, `prompts/`, `themes/`, `AGENTS.md|CLAUDE.md`

### 5.5 Extensions system

`core/extensions/` (loader 713 lines / runner 1236 lines / types 1713 lines):

- Dynamically load TS extensions with `jiti`
- `VIRTUAL_MODULES` (`loader.ts:50`) pre-injects pi-agent-core/pi-ai/pi-tui/typebox
- `ExtensionAPI` (`types.ts:1193`) exposes 30+ event hooks:
  - `tool_call` / `before_provider_request` / `session_before_compact`
  - `registerTool` / `registerCommand` / `registerShortcut` / `registerFlag`
- Also supported under Bun compiled binaries

### 5.6 Skills system

`core/skills.ts` (487 lines). Spec follows `agentskills.io`:

- **Discover**: `loadSkillsFromDir` (`L168`) recursively scans `SKILL.md`
- **Parse**: `loadSkillFromFile` (`L277`) uses `parseFrontmatter` to extract name/description
- **Source grades**: user (`~/.pi/agent/skills`), project (`<cwd>/.pi/skills`), path (`--skills`)
- **Inject**: `formatSkillsForPrompt` (`L335`) generates an `<available_skills>` XML block, instructing the model to load the skill file with the read tool

### 5.7 AgentSession

`core/agent-session.ts` (3332 lines) is the bridge between pi-coding-agent and pi-agent-core:

- Holds `Agent` (agent-core’s looper)
- `agent.subscribe()` takes over events
- Stacks: session persistence, compaction, branch summary, retry, bash abort, extension runner, tool registry, system-prompt rebuild, steering/follow-up/asides message queues

---

## VI. pi-tui: differential-rendering terminal UI

### 6.1 Rendering engine

`src/tui.ts` (1223 lines) + `TuiMainScreen.ts` (552 lines) + `TuiAltScreen.ts` (805 lines).

**Component model**: the `Component` interface (`tui.ts:23`) is only `render(width): string[]` + `handleInput?` + `invalidate()`. Extremely minimal, no Virtual DOM.

**Render scheduling**: `requestRender()` → `setTimeout` throttle (`MIN_RENDER_INTERVAL_MS = 16`, about 60fps).

### 6.2 Differential-rendering algorithm

`TuiMainScreen.doRender()` (`TuiMainScreen.ts:146`):

1. `render(width)` renders all components as an array of string lines
2. Line-by-line diff: walk `previousLines` vs `newLines`, find `firstChanged`/`lastChanged`
3. Incremental write: wrap with `\x1b[?2026h…\x1b[?2026l` (synchronized output)
4. Only for the `firstChanged..lastChanged` interval write `\x1b[2K` + new content
5. Kitty images special-cased: incrementally delete disappeared image IDs

### 6.3 The essential difference from Ink

| Dimension | Ink (Claude Code) | pi-tui |
|---|---|---|
| Framework | React + Yoga | no framework |
| Layout | flexbox | handwritten `layout.ts` |
| Rendering | full redraw (each frame reconcile→render the whole tree) | line-by-line string diff, only paint changed lines |
| Abstraction | thick (reconciler + component tree) | thin (a component is `render(): string[]`) |
| I/O | much (full output) | little (only write changed lines) |
| Flicker | yes (needs double buffering) | none (synchronized output) |

**Essence**: Ink is “retained mode + full reconcile”; pi-tui is “immediate mode + dirty-line refresh.” pi-tui is thinner, I/O less, friendlier to slow terminals, but layout capability is weak.

### 6.4 Keyboard input

`src/keys.ts` (1401 lines):
- Supports both traditional escape sequences and the **Kitty keyboard protocol**
- `parseKey(data)` returns `KeyId`
- `KeybindingsManager`: configurable `KeyId`→action

`src/stdin-buffer.ts` (434 lines): splits stdin chunks into atomic events at ESC-sequence boundaries.

---

## VII. pi-protocol: binary wire protocol

### 7.1 Design

`packages/protocol/` (12 TS files):

- **Not JSON-RPC, not MCP**—a custom request/response + event-stream protocol
- Each message = `4-byte big-endian length prefix` + `CBOR-encoded payload` (`framing.ts:1-39`)
- `DEFAULT_MAX_FRAME_LENGTH = 16MB`
- `PROTOCOL_VERSION = 2` (`schemas.ts:3`)

### 7.2 Message kinds

**Client**: `ClientHello` (token + version), `RequestEnvelope` (id + command)

**Server**: `ServerHello` (includes `ServerSnapshot`), `ResponseEnvelope` (ok/error), `EventEnvelope`

**Commands**: `list / create / attach / detach / prompt / steer / abort / set_model / set_thinking`

**Events**: `server_snapshot / session_snapshot / session_progress / session_removed`

### 7.3 Core data structures

`TranscriptItem` (user/assistant/tool; assistant has streaming/complete/error/aborted four states), `TranscriptProgress` (item_started/assistant_delta/item_updated/item_finished, incremental streaming), `SessionSnapshot` (includes revision + transcript + queuedSteer).

### 7.4 Relationship to MCP

Officially explicit **No MCP** (`packages/coding-agent/README.md:495` / `packages/coding-agent/docs/usage.md:301`; there is no `docs/` at the repo root): no built-in MCP client; when needed, go through an external extension (e.g. the example `pi-mcp-adapter`). `pi-protocol` is a session-level protocol (create/attach/detach/abort + transcript sync), orthogonal to the MCP tool protocol—do not mix the two.

---

## VIII. pi-server: Unix-socket session server

### 8.1 Architecture

`packages/server/src/server.ts` (399 lines):

- **Not an HTTP server, not WebSocket**—a byte-stream session server based on a **Unix domain socket**
- At construct time inject `PiSessionBackend` (durable storage boundary) and `token` (sha256 + `timingSafeEqual` check)
- `accept()` → handshake timeout 5s → auth + version check → reply `ServerHello` + full `ServerSnapshot`

### 8.2 LiveSessionManager

`sessions.ts:43-353`:

- Maintains a `liveSessions` Map
- `acquire()` uses `openingSessions` to prevent concurrently opening the same session
- `maybeDispose()` unloads the runtime when there is no connection + idle + no operation
- `handleRuntimeEvent()` forwards the runtime’s progress/snapshot events to attached connections

### 8.3 Snapshot broadcast

`ServerSnapshotPublisher` does revisioned snapshot broadcast—clients discard old snapshots by monotonically increasing revision.

---

## IX. pi-client: client SDK

### 9.1 Connection model

`PiClient.connect(options)` static method does construct + connect in one step:

1. `transportFactory` creates a `ByteTransport`, connects to the Unix socket path
2. `Connection.connect()` sends `ClientHello` (version + token)
3. Receive `ServerHello` → state `disconnected → connecting → connected`

### 9.2 Session leases

- `createSession()` → exclusive lease
- `attachSession()` → shared lease
- Reference counting; `detach` is sent only when the last release happens
- `SessionHandle` exposes `prompt/steer/abort/setModel/setThinking`

---

## X. pi-storage-sqlite-node: SQLite event sourcing (optional SessionStore)

> **Clear the path first**: everyday `pi` CLI `SessionManager` writes sessions as `*_sessionId.jsonl` (supports `/fork` `/tree` `/compact`). `@earendil-works/pi-storage-sqlite-node` implements the harness’s `SessionStore`, **not** used by default by coding-agent’s `SessionManager`. What follows is the design of this optional SQLite path, not CLI default behavior.


### 10.1 Database

`packages/storage/sqlite-node/`:

- Uses Node 22+ built-in `node:sqlite`, no third-party native dependency
- `PRAGMA journal_mode=WAL; synchronous=FULL; busy_timeout=5000`

### 10.2 Event sourcing

6 tables:
- `sessions` (id, cwd, parent_session_id, active_leaf_id, metadata)
- `session_entries` (session_id, id, entry_seq, parent_id, type, timestamp, payload)— **append-only event log**
- `session_sequences` (one next_seq counter per session)
- `branch_entries` (materialized branch paths)
- `session_materialized` + `entry_materialized` (cached current-state projections)

Entry types: `message / thinking_level_change / model_change / active_tools_change / compaction / branch_summary / custom / custom_message / label / session_info / leaf`

### 10.3 Materialized views

On `appendEntry()` also update `session_materialized` (whole-session summary) and `entry_materialized` (split by type), avoiding replaying the whole tree on read.

### 10.4 Branches and fork

- A `leaf` entry triggers `materializeBranch()` to recompute branch_entries
- A `compaction` entry supports `retainedTail` + `firstKeptEntryId` for compaction + keep the tail
- `fork()` copies a selection into a new session inside a transaction

---

## XI. Design philosophy and trade-offs

### 11.1 Why a monorepo

Pi’s most core architectural decision is **splitting the Agent into orthogonal packages**. Why?

- **pi-protocol**: the protocol layer decouples server and client; multiple clients can attach the same session
- **pi-ai**: the provider layer is independent; 38 providers each load on demand
- **pi-agent-core**: the runtime layer is independent, bound to no provider
- **pi-tui**: the UI layer is independent, replaceable with a Web UI
- **pi-storage**: the storage layer is independent, replaceable with PostgreSQL

**Cost**: high engineering complexity (dependency management, version sync, build order of 9 core packages), long debug chain.

### 11.2 Why no built-in permissions

Pi explicitly chooses **not to build in a permission system**. Why?

- Permission systems are too complex (Claude Code’s five-gate verdict), and every user’s scene is different
- Containerization is safer (OS-level isolation is far more reliable than an application-level popup)
- Sink isolation to the OS/VM layer, let the user pick a fitting scheme

**Cost**: default run has no protection; the user must containerize themselves.

### 11.3 Why a home-grown TUI

- Ink’s full redraw flickers badly on slow terminals
- Differential rendering only paints changed lines, I/O extremely little
- No React dependency, smaller bundle

**Cost**: weak layout (no flexbox); components must handle wrap/truncate themselves.

### 11.4 Trade-off contrast with Claude Code

| Dimension | Claude Code | Pi | Why the trade-off |
|---|---|---|---|
| Architecture | monolith | monorepo | simple vs composable |
| Protocol | none | binary | zero cost vs many clients |
| Provider | locked to Anthropic | 38 | deep optimization vs open |
| TUI | Ink full redraw | differential rendering | strong layout vs little I/O |
| Permissions | five-gate verdict | not built in | safety vs simple |
| Extension | plugins + MCP | Extensions + Skills (MCP external) | limited vs self-extensible |
| Persistence | JSONL | CLI also JSONL; plus SQLite SessionStore | both a file tree + optional DB |
| Sessions | single process | multi session + fork | single vs flexible |

### 11.5 Three most distinctive designs

1. **Self-extensible Extensions**: 30+ event hooks + registerTool/Command/Shortcut/Flag; the agent adds capability to itself at runtime
2. **Protocol-driven process split**: CBOR + length framing + TypeBox validation + token auth; multiple clients attach the same session
3. **Differential-rendering TUI**: no React, line-by-line diff, synchronized output, extremely little terminal I/O

---

## Side-by-side: Pi’s position outside the seventeen

### Vs Claude Code (the most direct counterpart)

| Dimension | Claude Code | Pi | Essence of the difference |
|---|---|---|---|
| Architecture | monolith CLI | monorepo 9 core packages | integrated vs composable |
| Agent loop | single-layer while | two-layer while | imperative vs parameterized |
| Provider | locked to Anthropic | 38 | closed vs open |
| TUI | Ink (React + Yoga) | differential rendering | retained mode vs immediate mode |
| Permissions | five-gate verdict | not built in | application-level vs OS-level |
| Extension | plugins + MCP | Extensions + Skills (MCP external; official No MCP) | limited vs self-extensible |
| Persistence | JSONL | CLI JSONL + optional SQLite SessionStore | default both a file tree |
| Sessions | single process | multi session + attach/detach | single vs many clients |
| License | MIT | MIT | the same |

### Vs CodePilot (both multi-provider)

| Dimension | CodePilot | Pi | Essence of the difference |
|---|---|---|---|
| UI | Electron desktop GUI | terminal TUI | desktop vs terminal |
| Providers | 17+ | 38 | fewer vs more |
| Runtime | three replaceable | one (agent-core) | multi-engine vs single-engine |
| Protocol | HTTP/SSE | binary CBOR | Web vs Unix |
| Extension | MCP + Skills | Extensions + Skills (MCP external) | standard vs self-extensible |
| Persistence | SQLite | CLI JSONL; harness optional SQLite event sourcing | different product defaults |
| Permissions | three layers | not built in | has vs has not |
| Sessions | single session + rewind | multi session + fork | rollback vs branch |

### Vs open-design (both “host shapes that do not write the agent loop”)

| Dimension | open-design | Pi | Essence of the difference |
|---|---|---|---|
| Host object | 25 CLIs | 38 providers | quantity vs depth |
| Core innovation | adapter as data | self-extensible Extensions | data vs code |
| Agent loop | does not write (outsourced) | two-layer while (home-grown) | pure host vs autonomous |
| Protocol | HTTP/SSE | binary CBOR | Web vs Unix |

### Pi’s distinctive contribution

Pi brought the field of agent engineering three designs none of the previous seventeen had:

1. **Self-extensible**: Extensions add tools/commands/shortcuts to themselves at runtime. This is not “plugins” (plugins are predefined extension points); it is “the agent can change its own capability surface.”

2. **Protocol-driven process split**: split the Agent into server/client/storage/runtime four layers, glued by an explicit binary protocol. That is what makes multiple clients attaching the same session possible.

3. **Differential-rendering TUI**: no React, no Virtual DOM, line-by-line diff only paints changed lines. On a slow terminal flicker is tiny, I/O extremely little.

---

## Appendix: key-file index

| File | Lines | Note |
|---|---|---|
| `packages/agent/src/agent-loop.ts` | 792 | agent loop |
| `packages/agent/src/agent.ts` | 577 | Agent class |
| `packages/agent/src/types.ts` | 437 | core types |
| `packages/agent/src/harness/agent-harness.ts` | 1185 | AgentHarness |
| `packages/agent/src/harness/session/session.ts` | 528 | Session tree |
| `packages/agent/src/harness/compaction/compaction.ts` | 880 | context compaction |
| `packages/agent/src/proxy.ts` | 367 | LLM HTTP streamProxy (not MCP) |
| `packages/ai/src/models.ts` | — | provider container |
| `packages/ai/src/api/lazy.ts` | — | lazyStream |
| `packages/ai/src/providers/all.ts` | — | 38 providers |
| `packages/coding-agent/src/main.ts` | 917 | main dispatch |
| `packages/coding-agent/src/core/agent-session.ts` | 3332 | AgentSession |
| `packages/coding-agent/src/core/sdk.ts` | 398 | SDK bridge |
| `packages/coding-agent/src/core/skills.ts` | 487 | Skills system |
| `packages/coding-agent/src/core/extensions/loader.ts` | 713 | Extensions loader |
| `packages/coding-agent/src/core/extensions/runner.ts` | 1236 | Extensions runner |
| `packages/coding-agent/src/core/resource-loader.ts` | 1096 | resource loader |
| `packages/coding-agent/src/core/tools/index.ts` | 196 | tool aggregation |
| `packages/tui/src/tui.ts` | 1223 | TUI base class |
| `packages/tui/src/TuiMainScreen.ts` | 552 | main-screen differential rendering |
| `packages/tui/src/TuiAltScreen.ts` | 805 | Alt-screen layout |
| `packages/tui/src/keys.ts` | 1401 | keyboard input |
| `packages/protocol/src/schemas.ts` | 447 | protocol schema |
| `packages/protocol/src/framing.ts` | 165 | length framing |
| `packages/server/src/server.ts` | 399 | PiServer |
| `packages/server/src/sessions.ts` | 353 | LiveSessionManager |
| `packages/client/src/client.ts` | 433 | PiClient |
| `packages/storage/sqlite-node/src/sqlite/session-store.ts` | 275 | Session storage |

---

*This lecture is written from a file-by-file analysis of pi’s `main` branch. Every `file:line` citation has been re-checked with `grep -n` / `sed -n`.*
