# Pi Agent Harness 源码分析

> **自扩展编程 Agent 的 monorepo 架构** · TypeScript · npm workspaces · 9 个核心包 · 1034 个 TS 文件
>
> commit `main` · MIT · 1034 TS/TSX
>
> 全部标注 `文件:行号`，可追溯到具体源码。

---

## 一、项目概览

### 1.1 Pi 是什么

Pi 不是又一个终端编程 Agent。它是**自扩展编程 Agent 的基础设施**——一个 monorepo，把"Agent"这件事拆成了 9 个核心 npm 包，靠显式契约粘合：

| 包 | 说明 | 文件数 |
|---|---|---|
| **pi-ai** | 统一多 Provider LLM API（38 个 Provider） | 305 |
| **pi-agent-core** | Agent runtime（双层 while 循环 + 工具 + 状态） | 62 |
| **pi-coding-agent** | 交互式编程 Agent CLI（组装层） | 488 |
| **pi-tui** | 终端 UI 库（差分渲染） | 74 |
| **pi-protocol** | 二进制线协议（CBOR + 长度帧） | 12 |
| **pi-server** | Unix socket 会话服务器 | 38 |
| **pi-client** | 客户端 SDK（租约模型） | 18 |
| **pi-storage-sqlite-node** | SQLite 事件溯源（harness `SessionStore`；**非** CLI 默认路径） | 12 |
| **pi-evals** | Agent 行为评测框架 | 14 |

**它和 Claude Code 的关系**：Claude Code 是单体 CLI，所有能力塞进一个进程。Pi 把 Agent 拆成协议、服务器、客户端、存储、运行时、UI 六层，每层独立成包——**协议驱动的可组合 Agent 基础设施**。

**它和 CodePilot 的关系**：CodePilot 是 Electron + Next.js 桌面客户端，Pi 是终端 CLI。CodePilot 三条 Runtime 外包给别人，Pi 自己写了全部核心。CodePilot 没有协议层，Pi 有显式二进制协议。

### 1.2 数字化的项目形状

| 维度 | 数字 | 说明 |
|---|---|---|
| TS/TSX 文件 | 1034 | 不含 node_modules |
| npm 包 | 8 | workspaces monorepo |
| Provider | 38 | OpenAI/Anthropic/Google/Bedrock/DeepSeek… |
| 内置工具 | 7 | read/write/edit/bash/grep/find/ls |
| TUI 组件 | 17+ | box/text/markdown/editor/scroll-view… |
| 扩展钩子 | 30+ | tool_call/before_provider_request/session_before_compact… |
| Slash 命令 | 22 | /settings /model /fork /tree /compact /share… |
| 评测 | 2 | smoke + extensions |
| 许可证 | MIT | 完全开源 |

### 1.3 三个最独特设计

1. **自扩展（Self-extensible）**：Extensions 用 `jiti` 动态加载 TS 文件，30+ 事件钩子 + `registerTool/registerCommand/registerShortcut/registerFlag`。Agent 可以在运行时给自己加工具、加命令、加快捷键。

2. **协议驱动的进程分离**：`pi-protocol`（CBOR + 长度帧 + TypeBox 校验 + token 鉴权）把 server 和 client 解耦。多客户端可以 attach 到同一个 session，server 做快照广播。

3. **差分渲染 TUI**：`pi-tui` 无 React、无 Virtual DOM、无 flexbox——组件就是 `render(width): string[]`，渲染靠逐行字符串 diff 只刷变化行，配 `\x1b[?2026h/l` 同步刷新。

### 1.4 与 Claude Code 的根本区别

| 维度 | Claude Code | Pi |
|---|---|---|
| 架构 | 单体 CLI | monorepo（9 核心包） |
| 协议 | 无（进程内调用） | 二进制线协议（CBOR） |
| 主循环 | 单层 while | 双层 while（follow-up + tool-call） |
| Provider | 锁 Anthropic | 38 个 Provider |
| TUI | Ink（React + Yoga 全量重绘） | 自研差分渲染 |
| 权限 | 五关裁决 | 不内建（容器化方案） |
| 扩展 | 插件 + MCP | Extensions + Skills（MCP 外置，官方 No MCP） |
| 持久化 | JSONL 文件 | CLI 默认 JSONL（`SessionManager`）；另提供 SQLite SessionStore |
| 会话 | 单进程单会话 | 多 session + attach/detach + fork |
| 模型 | MIT | MIT |

---

## 二、全景架构

### 2.1 包依赖拓扑

```
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` 是最底层契约，被 server/client/storage 三方共享
- `pi-server` 实现 `PiSessionBackend`/`PiSessionRuntime` 接口，runtime 实际由 coding-agent 提供
- `pi-client` 是 server 的对端 SDK，镜像了协议命令
- `pi-storage` 实现 `pi-agent-core` 的 `SessionStore`，被 coding-agent 的 `SessionManager` 使用
- `pi-evals` 站在最顶层，把 coding-agent 当被测对象

### 2.2 进程拓扑

```
┌──────────────────────────────────────────────────────┐
│  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 数据流：从用户输入到 AI 响应

```
用户输入 → 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）
```

---

## 三、pi-ai：统一多 Provider LLM API

### 3.1 38 个 Provider

`packages/ai/src/providers/all.ts:87-128` 的 `builtinProviders()` / `KnownProvider` 导出 **38** 个 Provider：

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 等。

### 3.2 统一接口

核心抽象在 `models.ts`：

- **`Provider<TApi>`**（`models.ts:75`）：`{id, name, baseUrl, auth, getModels(), stream(), streamSimple()}`
- **`Models`**（`models.ts:127`）：provider 容器 + auth 解析 + 统一 `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`

每个 API 在 `src/api/<name>.ts` 实现，导出 `stream` 和 `streamSimple` 两个 `StreamFunction`。Provider 文件极薄（如 `openai.ts` 仅 15 行），只负责 `createProvider({id, auth, models, api: openAIResponsesApi()})`。

### 3.3 lazyStream：按需加载

`api/lazy.ts:46` 的 `lazyStream`：

- 同步返回 stream，异步在背后跑 setup（auth 解析、动态 import）
- `lazyApi()`（`lazy.ts:68`）包装动态 `import()`——**provider 实现按需加载**
- 主 bundle 不含任何 SDK
- `anthropic-messages.ts` 高达 1351 行（含 `@anthropic-ai/sdk` 调用、cache_control、thinking），但只有用到时才加载

### 3.4 与 Vercel AI SDK 的关系

**无关系**。pi-ai 是独立实现，不依赖也不兼容 `@ai-sdk/*`。pi-ai 自己定义消息/工具/事件协议、自己实现每个 provider 的 SDK 适配。

### 3.5 models.generated.ts

只有 2 个 `.generated.ts`：`models.generated.ts`（118 行）和 `image-models.generated.ts`（609 行）。由 `scripts/generate-models.ts` 生成，聚合 38 个 `providers/<name>.models.ts` 的导出为 `MODELS` 字典。真正的模型元数据在 `providers/data/<name>.json` 里。

---

## 四、pi-agent-core：Agent runtime

### 4.1 核心文件

| 文件 | 行数 | 说明 |
|---|---|---|
| `agent-loop.ts` | 792 | 底层无状态 Agent 主循环 |
| `agent.ts` | 577 | 有状态 Agent 类（队列+生命周期） |
| `types.ts` | 437 | 核心类型 |
| `harness/agent-harness.ts` | 1185 | 上层 AgentHarness |
| `harness/types.ts` | 980 | harness 层类型 |
| `harness/session/session.ts` | 528 | Session 树+context 构造 |
| `harness/compaction/compaction.ts` | 880 | 上下文压缩 |
| `proxy.ts` | 367 | LLM HTTP `streamProxy`（代理远端模型流；**不是** MCP） |

### 4.2 双层 while 主循环

`agent-loop.ts:155-275` 的 `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
}
```

**与 Claude Code 的本质区别**：

| 维度 | Claude Code | pi-agent-core |
|---|---|---|
| 循环结构 | 单层 while | 双层 while（follow-up + tool-call） |
| 流式接入 | 直接调 Anthropic SDK | 通过 `StreamFn` 注入（provider-agnostic） |
| 工具执行 | 串行 | 默认 parallel（`Promise.all`） |
| 控制流 | 硬编码 | 4 个回调钩子参数化 |
| 消息模型 | provider 原生 | `AgentMessage` + `CustomAgentMessages`（declaration merging） |

### 4.3 工具接口

`AgentTool<TParameters, TDetails>`（`types.ts:380-403`）扩展 pi-ai 的 `Tool`：

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

执行管线（`agent-loop.ts:600-754`）：
1. `prepareToolCall`：找工具 → `prepareArguments` → `validateToolArguments` → `beforeToolCall` 钩子（可 block）
2. `executePreparedToolCall`：调 `tool.execute`，支持 `onUpdate` 流式部分结果
3. `finalizeExecutedToolCall`：`afterToolCall` 钩子（可覆盖 content/details/isError/usage/terminate）

**错误不抛**——用 `createErrorToolResult` 包装，遵循"错误编码进 toolResult 消息"的契约。

### 4.4 三层状态管理

1. **`AgentContext`**（`types.ts:406`）：纯快照，`{systemPrompt, messages, tools}`，每个 turn 都 snapshot
2. **`Agent` 类**（`agent.ts:171`）：持有 `MutableAgentState`，`subscribe()` 监听事件，`steer()`/`followUp()` 走 `PendingMessageQueue`
3. **`AgentHarness`**（`agent-harness.ts:173`）：在 `Agent` 之上叠加 Session 持久化、compaction、skills、prompt templates、provider hooks

### 4.5 AgentMessage：开放联合类型

`AgentMessage = Message | CustomAgentMessages[keyof ...]`（`types.ts:319`）——通过 declaration merging 扩展自定义消息（harness 注入了 `bashExecution`/`custom`/`branchSummary`/`compactionSummary`）。

`convertToLlm`（`messages.ts:120`）把这些自定义消息投影成 LLM 可懂的 `user` 消息或过滤掉。这是 pi 与 Claude Code 最大的架构差异之一——**UI/存储能用富消息而 LLM 只看标准消息**。

---

## 五、pi-coding-agent：交互式编程 Agent

### 5.1 入口与模式

- **CLI 入口**：`src/cli.ts`（20 行）→ `main(process.argv.slice(2))`
- **主调度**：`src/main.ts`（917 行），核心函数 `main()` 在 `L521`
- **三种模式**：`interactive`（TUI）/ `print`（单次输出）/ `rpc`（JSON-RPC over stdio 供 IDE 集成）

### 5.2 内置工具

`src/core/tools/index.ts:83` 定义 7 个工具：`read | bash | edit | write | grep | find | ls`

- 工厂函数：`createCodingTools`（read/bash/edit/write）、`createReadOnlyTools`（read/grep/find/ls）、`createAllTools`
- 每个工具用 `Operations` 接口注入底层 fs/exec 实现——便于扩展替换（如 Gondolin）

### 5.3 权限系统

Pi **不内建权限系统**。`README.md:39`：

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

替代方案三种（`docs/containerization.md`）：
1. **Gondolin 扩展**：工具路由进本地 Linux micro-VM（QEMU）
2. **Plain Docker**：整个 pi 进程跑进容器
3. **OpenShell**（NVIDIA）：策略化沙箱

### 5.4 .pi/ 目录

`config.ts:491` 定义 `CONFIG_DIR_NAME = ".pi"`：

- **全局** `~/.pi/agent/`：`auth.json`、`models.json`、`settings.json`、`themes/`、`prompts/`、`sessions/`、`extensions/`、`skills/`
- **项目** `<cwd>/.pi/`：`extensions/`、`skills/`、`prompts/`、`themes/`、`AGENTS.md|CLAUDE.md`

### 5.5 Extensions 系统

`core/extensions/`（loader 713 行 / runner 1236 行 / types 1713 行）：

- 用 `jiti` 动态加载 TS 扩展
- `VIRTUAL_MODULES`（`loader.ts:50`）预注入 pi-agent-core/pi-ai/pi-tui/typebox
- `ExtensionAPI`（`types.ts:1193`）暴露 30+ 事件钩子：
  - `tool_call` / `before_provider_request` / `session_before_compact`
  - `registerTool` / `registerCommand` / `registerShortcut` / `registerFlag`
- Bun 编译二进制也支持

### 5.6 Skills 系统

`core/skills.ts`（487 行）。规范遵循 `agentskills.io`：

- **发现**：`loadSkillsFromDir`（`L168`）递归扫描 `SKILL.md`
- **解析**：`loadSkillFromFile`（`L277`）用 `parseFrontmatter` 提取 name/description
- **来源分级**：user（`~/.pi/agent/skills`）、project（`<cwd>/.pi/skills`）、path（`--skills`）
- **注入**：`formatSkillsForPrompt`（`L335`）生成 `<available_skills>` XML 块，指示模型用 read 工具加载 skill 文件

### 5.7 AgentSession

`core/agent-session.ts`（3332 行）是 pi-coding-agent 与 pi-agent-core 的桥梁：

- 持有 `Agent`（agent-core 的循环器）
- `agent.subscribe()` 接管事件
- 叠加：session 持久化、compaction、branch summary、retry、bash abort、extension runner、tool registry、system prompt 重建、steering/follow-up/asides 消息队列

---

## 六、pi-tui：差分渲染终端 UI

### 6.1 渲染引擎

`src/tui.ts`（1223 行）+ `TuiMainScreen.ts`（552 行）+ `TuiAltScreen.ts`（805 行）。

**组件模型**：`Component` 接口（`tui.ts:23`）只有 `render(width): string[]` + `handleInput?` + `invalidate()`。极简，无 Virtual DOM。

**渲染调度**：`requestRender()` → `setTimeout` 节流（`MIN_RENDER_INTERVAL_MS = 16`，约 60fps）。

### 6.2 差分渲染算法

`TuiMainScreen.doRender()`（`TuiMainScreen.ts:146`）：

1. `render(width)` 渲染全部组件为字符串行数组
2. 逐行 diff：遍历 `previousLines` vs `newLines`，找 `firstChanged`/`lastChanged`
3. 增量写：用 `\x1b[?2026h…\x1b[?2026l`（synchronized output）包裹
4. 只对 `firstChanged..lastChanged` 区间行写 `\x1b[2K` + 新内容
5. Kitty 图片特殊处理：增量删除已消失图片 ID

### 6.3 与 Ink 的本质区别

| 维度 | Ink（Claude Code） | pi-tui |
|---|---|---|
| 框架 | React + Yoga | 无框架 |
| 布局 | flexbox | 手写 `layout.ts` |
| 渲染 | 全量重绘（每帧 reconcile→render 整棵树） | 逐行字符串 diff 只刷变化行 |
| 抽象层 | 厚（reconciler + component tree） | 薄（组件即 `render(): string[]`） |
| I/O | 多（全量输出） | 少（只写变化行） |
| 闪烁 | 有（需 double buffering） | 无（synchronized output） |

**本质**：Ink 是"保留模式 + 全量 reconcile"；pi-tui 是"立即模式 + 脏行刷新"。pi-tui 更薄、I/O 更少、对慢终端更友好，但布局能力弱。

### 6.4 键盘输入

`src/keys.ts`（1401 行）：
- 同时支持传统转义序列与 **Kitty keyboard protocol**
- `parseKey(data)` 返回 `KeyId`
- `KeybindingsManager`：可配置 `KeyId`→动作

`src/stdin-buffer.ts`（434 行）：把 stdin chunk 按 ESC 序列边界拆分成原子事件。

---

## 七、pi-protocol：二进制线协议

### 7.1 设计

`packages/protocol/`（12 个 TS 文件）：

- **不是 JSON-RPC，也不是 MCP**——自定义的请求/响应 + 事件流协议
- 每条消息 = `4 字节大端长度前缀` + `CBOR 编码的 payload`（`framing.ts:1-39`）
- `DEFAULT_MAX_FRAME_LENGTH = 16MB`
- `PROTOCOL_VERSION = 2`（`schemas.ts:3`）

### 7.2 消息种类

**客户端**：`ClientHello`（token + version）、`RequestEnvelope`（id + command）

**服务端**：`ServerHello`（含 `ServerSnapshot`）、`ResponseEnvelope`（ok/error）、`EventEnvelope`

**命令**：`list / create / attach / detach / prompt / steer / abort / set_model / set_thinking`

**事件**：`server_snapshot / session_snapshot / session_progress / session_removed`

### 7.3 核心数据结构

`TranscriptItem`（user/assistant/tool，assistant 有 streaming/complete/error/aborted 四态）、`TranscriptProgress`（item_started/assistant_delta/item_updated/item_finished，增量流式）、`SessionSnapshot`（含 revision + transcript + queuedSteer）。

### 7.4 与 MCP 的关系

官方明确 **No MCP**（`packages/coding-agent/README.md:495` / `packages/coding-agent/docs/usage.md:301`；仓库根目录没有 `docs/`）：内置不提供 MCP 客户端；需要时走外置扩展（如示例 `pi-mcp-adapter`）。`pi-protocol` 是会话级协议（create/attach/detach/abort + transcript 同步），与 MCP 工具协议正交——二者不要混为一谈。

---

## 八、pi-server：Unix socket 会话服务器

### 8.1 架构

`packages/server/src/server.ts`（399 行）：

- **不是 HTTP 服务器，也不是 WebSocket**——基于 **Unix domain socket** 的字节流会话服务器
- 构造时注入 `PiSessionBackend`（durable 存储边界）和 `token`（sha256 + `timingSafeEqual` 校验）
- `accept()` → 握手超时 5s → 鉴权 + 版本检查 → 回 `ServerHello` + 全量 `ServerSnapshot`

### 8.2 LiveSessionManager

`sessions.ts:43-353`：

- 维护 `liveSessions` Map
- `acquire()` 用 `openingSessions` 防并发打开同一 session
- `maybeDispose()` 在无连接 + idle + 无操作时卸载 runtime
- `handleRuntimeEvent()` 把 runtime 的 progress/snapshot 事件转发给 attach 的连接

### 8.3 快照广播

`ServerSnapshotPublisher` 做 revision 化的快照广播——客户端按 revision 单调递增丢弃旧快照。

---

## 九、pi-client：客户端 SDK

### 9.1 连接模型

`PiClient.connect(options)` 静态方法一步完成构造 + 连接：

1. `transportFactory` 创建 `ByteTransport`，连 Unix socket path
2. `Connection.connect()` 发 `ClientHello`（version + token）
3. 收到 `ServerHello` → 状态 `disconnected → connecting → connected`

### 9.2 Session 租约

- `createSession()` → 独占租约（exclusive）
- `attachSession()` → 共享租约（shared）
- 引用计数，最后一个 release 时才发 `detach`
- `SessionHandle` 暴露 `prompt/steer/abort/setModel/setThinking`

---

## 十、pi-storage-sqlite-node：SQLite 事件溯源（可选 SessionStore）

> **路径先说清**：日常 `pi` CLI 的 `SessionManager` 把会话写成 `*_sessionId.jsonl`（支持 `/fork` `/tree` `/compact`）。`@earendil-works/pi-storage-sqlite-node` 实现的是 harness 用的 `SessionStore`，**不被** coding-agent 的 `SessionManager` 默认使用。下文讲的是这条可选 SQLite 路径的设计，不是 CLI 默认行为。


### 10.1 数据库

`packages/storage/sqlite-node/`：

- 用 Node 22+ 内置的 `node:sqlite`，无第三方原生依赖
- `PRAGMA journal_mode=WAL; synchronous=FULL; busy_timeout=5000`

### 10.2 事件溯源

6 张表：
- `sessions`（id, cwd, parent_session_id, active_leaf_id, metadata）
- `session_entries`（session_id, id, entry_seq, parent_id, type, timestamp, payload）— **append-only 事件日志**
- `session_sequences`（每 session 一个 next_seq 计数器）
- `branch_entries`（物化分支路径）
- `session_materialized` + `entry_materialized`（缓存当前状态投影）

Entry type 有：`message / thinking_level_change / model_change / active_tools_change / compaction / branch_summary / custom / custom_message / label / session_info / leaf`

### 10.3 物化视图

`appendEntry()` 时同时更新 `session_materialized`（整体摘要）和 `entry_materialized`（按 type 分行），避免读时重放整树。

### 10.4 分支与 fork

- `leaf` entry 触发 `materializeBranch()` 重算 branch_entries
- `compaction` entry 支持 `retainedTail` + `firstKeptEntryId` 做压缩 + 保留尾部
- `fork()` 在事务内复制选区到新 session

---

## 十一、设计哲学与取舍

### 11.1 为什么做 monorepo

Pi 最核心的架构决策是**把 Agent 拆成正交的包**。为什么？

- **pi-protocol**：协议层让 server 和 client 解耦，多客户端可以 attach 同一个 session
- **pi-ai**：Provider 层独立，38 个 Provider 各自按需加载
- **pi-agent-core**：运行时层独立，不绑定任何 Provider
- **pi-tui**：UI 层独立，可以替换成 Web UI
- **pi-storage**：存储层独立，可以替换成 PostgreSQL

**代价**：工程复杂度高（9 个核心包的依赖管理、版本同步、构建顺序）、调试链路长。

### 11.2 为什么不内建权限

Pi 明确选择**不内建权限系统**。为什么？

- 权限系统太复杂（Claude Code 五关裁决），且每个用户场景不同
- 容器化方案更安全（OS 级隔离比应用级弹窗可靠得多）
- 把隔离下沉到 OS/VM 层，让用户选择适合的方案

**代价**：默认运行没有保护，用户必须自己容器化。

### 11.3 为什么自研 TUI

- Ink 的全量重绘在慢终端上闪烁严重
- 差分渲染只刷变化行，I/O 极少
- 无 React 依赖，bundle 更小

**代价**：布局能力弱（无 flexbox），组件需自己处理换行/截断。

### 11.4 与 Claude Code 的取舍对比

| 维度 | Claude Code | Pi | 取舍理由 |
|---|---|---|---|
| 架构 | 单体 | monorepo | 简单 vs 可组合 |
| 协议 | 无 | 二进制 | 零成本 vs 多客户端 |
| Provider | 锁 Anthropic | 38 个 | 深度优化 vs 开放 |
| TUI | Ink 全量重绘 | 差分渲染 | 布局强 vs I/O 少 |
| 权限 | 五关裁决 | 不内建 | 安全 vs 简单 |
| 扩展 | 插件 + MCP | Extensions + Skills（MCP 外置） | 有限 vs 自扩展 |
| 持久化 | JSONL | CLI 亦为 JSONL；另有 SQLite SessionStore | 同为文件树 + 可选 DB |
| 会话 | 单进程 | 多 session + fork | 单一 vs 灵活 |

### 11.5 三个最独特设计

1. **自扩展 Extensions**：30+ 事件钩子 + registerTool/Command/Shortcut/Flag，Agent 运行时给自己加能力
2. **协议驱动进程分离**：CBOR + 长度帧 + TypeBox 校验 + token 鉴权，多客户端 attach 同一 session
3. **差分渲染 TUI**：无 React、逐行 diff、synchronized output，终端 I/O 极少

---

## 横向对比：Pi 在十七家之外的位置

### 与 Claude Code 的对比（最直接的对标）

| 维度 | Claude Code | Pi | 差异本质 |
|---|---|---|---|
| 架构 | 单体 CLI | monorepo 9 核心包 | 一体化 vs 可组合 |
| 主循环 | 单层 while | 双层 while | 命令式 vs 参数化 |
| Provider | 锁 Anthropic | 38 个 | 封闭 vs 开放 |
| TUI | Ink（React + Yoga） | 差分渲染 | 保留模式 vs 立即模式 |
| 权限 | 五关裁决 | 不内建 | 应用级 vs OS 级 |
| 扩展 | 插件 + MCP | Extensions + Skills（MCP 外置，官方 No MCP） | 有限 vs 自扩展 |
| 持久化 | JSONL | CLI JSONL + 可选 SQLite SessionStore | 默认同为文件树 |
| 会话 | 单进程 | 多 session + attach/detach | 单一 vs 多客户端 |
| 模型 | MIT | MIT | 相同 |

### 与 CodePilot 的对比（同为多 Provider）

| 维度 | CodePilot | Pi | 差异本质 |
|---|---|---|---|
| 界面 | Electron 桌面 GUI | 终端 TUI | 桌面 vs 终端 |
| Provider | 17+ | 38 | 少 vs 多 |
| Runtime | 三条可替换 | 一条（agent-core） | 多引擎 vs 单引擎 |
| 协议 | HTTP/SSE | 二进制 CBOR | Web vs Unix |
| 扩展 | MCP + Skills | Extensions + Skills（MCP 外置） | 标准 vs 自扩展 |
| 持久化 | SQLite | CLI JSONL；harness 可选 SQLite 事件溯源 | 产品默认不同 |
| 权限 | 三层 | 不内建 | 有 vs 无 |
| 会话 | 单会话 + rewind | 多 session + fork | 回滚 vs 分支 |

### 与 open-design 的对比（同为"不写主循环"的宿主形态）

| 维度 | open-design | Pi | 差异本质 |
|---|---|---|---|
| 宿主对象 | 25 个 CLI | 38 个 Provider | 数量 vs 深度 |
| 核心创新 | 适配器即数据 | 自扩展 Extensions | 数据 vs 代码 |
| 主循环 | 不写（外包） | 双层 while（自研） | 纯宿主 vs 自主 |
| 协议 | HTTP/SSE | 二进制 CBOR | Web vs Unix |

### Pi 的独特贡献

Pi 给 Agent 工程带来了三个前面十七家都没有的设计：

1. **自扩展**：Extensions 在运行时给自己加工具/命令/快捷键。这不是"插件"（插件是预定义的扩展点），是"Agent 可以改变自己的能力面"。

2. **协议驱动进程分离**：把 Agent 拆成 server/client/storage/runtime 四层，靠显式二进制协议粘合。这让多客户端 attach 同一 session 成为可能。

3. **差分渲染 TUI**：无 React、无 Virtual DOM，逐行 diff 只刷变化行。在慢终端上闪烁极小，I/O 极少。

---

## 附录：关键文件索引

| 文件 | 行数 | 说明 |
|---|---|---|
| `packages/agent/src/agent-loop.ts` | 792 | Agent 主循环 |
| `packages/agent/src/agent.ts` | 577 | Agent 类 |
| `packages/agent/src/types.ts` | 437 | 核心类型 |
| `packages/agent/src/harness/agent-harness.ts` | 1185 | AgentHarness |
| `packages/agent/src/harness/session/session.ts` | 528 | Session 树 |
| `packages/agent/src/harness/compaction/compaction.ts` | 880 | 上下文压缩 |
| `packages/agent/src/proxy.ts` | 367 | LLM HTTP streamProxy（非 MCP） |
| `packages/ai/src/models.ts` | — | Provider 容器 |
| `packages/ai/src/api/lazy.ts` | — | lazyStream |
| `packages/ai/src/providers/all.ts` | — | 38 个 Provider |
| `packages/coding-agent/src/main.ts` | 917 | 主调度 |
| `packages/coding-agent/src/core/agent-session.ts` | 3332 | AgentSession |
| `packages/coding-agent/src/core/sdk.ts` | 398 | SDK 桥梁 |
| `packages/coding-agent/src/core/skills.ts` | 487 | Skills 系统 |
| `packages/coding-agent/src/core/extensions/loader.ts` | 713 | Extensions 加载器 |
| `packages/coding-agent/src/core/extensions/runner.ts` | 1236 | Extensions 运行器 |
| `packages/coding-agent/src/core/resource-loader.ts` | 1096 | 资源加载器 |
| `packages/coding-agent/src/core/tools/index.ts` | 196 | 工具聚合 |
| `packages/tui/src/tui.ts` | 1223 | TUI 基类 |
| `packages/tui/src/TuiMainScreen.ts` | 552 | 主屏差分渲染 |
| `packages/tui/src/TuiAltScreen.ts` | 805 | Alt 屏布局 |
| `packages/tui/src/keys.ts` | 1401 | 键盘输入 |
| `packages/protocol/src/schemas.ts` | 447 | 协议 schema |
| `packages/protocol/src/framing.ts` | 165 | 长度帧 |
| `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 存储 |

---

*本文基于 pi `main` 分支源码逐文件分析写成。所有 `文件:行号` 引用已用 `grep -n` / `sed -n` 回验。*
