A self-extensible coding-agent monorepo architecture · TypeScript · npm workspaces · 9 core packages · 1034 TS files
commit
main· MIT · 1034 TS/TSXEvery 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
-
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. -
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. -
Differential-rendering TUI:
pi-tuihas no React, no Virtual DOM, no flexbox—a component isrender(width): string[], rendering diffs line-by-line strings and only paints changed lines, with\x1b[?2026h/lsynchronized 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-protocolis the bottom-most contract, shared by server/client/storage three partiespi-serverimplementsPiSessionBackend/PiSessionRuntimeinterfaces; the runtime is actually provided by coding-agentpi-clientis the server’s peer SDK, mirroring protocol commandspi-storageimplementspi-agent-core’sSessionStore, used by coding-agent’sSessionManagerpi-evalsstands 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 + unifiedstream/streamSimple/complete/completeSimpleModelsImpl.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 StreamFunctions. 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 dynamicimport()—provider implementations load on demand- The main bundle contains no SDK
anthropic-messages.tsis as high as 1351 lines (including@anthropic-ai/sdkcalls, 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():
// 外层: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:
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
AgentContext(types.ts:406): a pure snapshot,{systemPrompt, messages, tools}, snapshot every turnAgentclass (agent.ts:171): holdsMutableAgentState,subscribe()listens to events,steer()/followUp()go throughPendingMessageQueueAgentHarness(agent-harness.ts:173): on top ofAgentstacks 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 functionmain()atL521 - 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
Operationsinterface—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/typeboxExtensionAPI(types.ts:1193) exposes 30+ event hooks:tool_call/before_provider_request/session_before_compactregisterTool/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 scansSKILL.md - Parse:
loadSkillFromFile(L277) usesparseFrontmatterto 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):
render(width)renders all components as an array of string lines- Line-by-line diff: walk
previousLinesvsnewLines, findfirstChanged/lastChanged - Incremental write: wrap with
\x1b[?2026h…\x1b[?2026l(synchronized output) - Only for the
firstChanged..lastChangedinterval write\x1b[2K+ new content - 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 = 16MBPROTOCOL_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) andtoken(sha256 +timingSafeEqualcheck) accept()→ handshake timeout 5s → auth + version check → replyServerHello+ fullServerSnapshot
8.2 LiveSessionManager
sessions.ts:43-353:
- Maintains a
liveSessionsMap acquire()usesopeningSessionsto prevent concurrently opening the same sessionmaybeDispose()unloads the runtime when there is no connection + idle + no operationhandleRuntimeEvent()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:
transportFactorycreates aByteTransport, connects to the Unix socket pathConnection.connect()sendsClientHello(version + token)- Receive
ServerHello→ statedisconnected → connecting → connected
9.2 Session leases
createSession()→ exclusive leaseattachSession()→ shared lease- Reference counting;
detachis sent only when the last release happens SessionHandleexposesprompt/steer/abort/setModel/setThinking
X. pi-storage-sqlite-node: SQLite event sourcing (optional SessionStore)
Clear the path first: everyday
piCLISessionManagerwrites sessions as*_sessionId.jsonl(supports/fork/tree/compact).@earendil-works/pi-storage-sqlite-nodeimplements the harness’sSessionStore, not used by default by coding-agent’sSessionManager. 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
leafentry triggersmaterializeBranch()to recompute branch_entries - A
compactionentry supportsretainedTail+firstKeptEntryIdfor 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
- Self-extensible Extensions: 30+ event hooks + registerTool/Command/Shortcut/Flag; the agent adds capability to itself at runtime
- Protocol-driven process split: CBOR + length framing + TypeBox validation + token auth; multiple clients attach the same session
- 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:
-
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.”
-
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.
-
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.