如果你一直在关注我前两天的博客——Grok Build 的 Rust 巨构和 OpenCode 的 Effect TS 体系——今天的主角是 Pi Agent(@earendil-works/pi)。
Pi 是一个 TypeScript 多包单体仓库项目,由 5 个核心包组成,共计 859 个 TypeScript 源文件。它的定位和 OpenCode 类似(都是 TypeScript 编码 Agent),但在架构设计上走了完全不同的路。
TL;DR:Pi 在「用户交互能力」上远超同类。它的 Steering + Follow-up 双队列设计,是 Grok Build 和 OpenCode 都没有的工程创新。
一、五层包的依赖关系
Pi 的包结构比 OpenCode 的 20+ 包更精简,但每层的抽象更严格:
@earendil-works/pi-orchestrator → RPC 子代理编排
↑
@earendil-works/pi-coding-agent → 编码 Agent CLI
↑
@earendil-works/pi-agent-core → 无状态 Agent Loop 引擎
↑
@earendil-works/pi-ai → 30+ LLM Provider 统一接口
↑
@earendil-works/pi-tui → 终端 UI 库
对比一下前两篇的架构:
| 项目 | 包数量 | 语言 | Agent 数量 | 会话存储 |
|---|---|---|---|---|
| Grok Build | 50+ crate | Rust | 1 (MvpAgent) | ACP 协议 + 磁盘序列化 |
| OpenCode | 20+ 包 | TypeScript/Effect | 2 (build/plan) | Drizzle SQLite |
| Pi Agent | 5 个包 | TypeScript | N/A (用户配置) | JSONL 树形文件 |
Pi 的包最少,但它的 Agent Loop 设计 反而是三者中最复杂的。
二、Agent Loop:Steering vs Follow-up 双队列
这是 Pi 最值得关注的架构创新。读 packages/agent/src/agent-loop.ts,它的核心循环是一个双层结构:
// 外层循环:处理 follow-up 消息
while (true) {
let hasMoreToolCalls = true;
// 内层循环:处理工具调用和 steering
while (hasMoreToolCalls || pendingMessages.length > 0) {
// step 1: 处理 pending steering 消息
// step 2: 调用 LLM
// step 3: 执行工具(并行或串行)
// step 4: shouldStopAfterTurn 检查
// step 5: 获取新的 steering
}
// 检查 follow-up 队列
// 若有 → 继续外层循环
// 若无 → break
}
Steering 和 Follow-up 有什么区别?
- Steering:在当前 turn 内注入的消息。用户可以在 Agent 思考时发一条"等一下,换个思路",它被排到 pending 队列,在当前 LLM 调用结束后立即处理。这是「打断并纠正」机制。
- Follow-up:在当前 turn 结束后注入的消息。Agent 即将停止时,如果外部有新的请求(比如子 Agent 返回结果),它被注入到下一轮。这是「追加任务」机制。
对比 Grok Build:Grok Build 没有 Steering 机制。用户一旦提交 prompt,Agent 会一直跑到工具调用结束或达到最大迭代次数。用户在中途发送的消息会被排到新 turn 中,而不是注入到当前 turn。
这种差异的本质原因是:Architecture influences interaction design.
- Grok Build 用 ACP 协议 + 网关模式:用户通过
session/prompt提交请求,Agent 执行完毕后通过PromptResponse返回。协议层面就是「一问一答」。 - Pi Agent 在进程内运行:Agent Loop 和 Steering 队列在同一个进程空间,天然支持 mid-turn injection。
三、30+ LLM Provider 的抽象层
Pi 的 @earendil-works/pi-ai(packages/ai/src/)拥有我在开源编码 Agent 中见过的最完整的 Provider 抽象层。
在 types.ts 中定义了核心类型:
interface Model {
id: string;
provider: string;
description?: string;
// 认证方式
authType?: "api-key" | "oauth" | "cert" | "none";
// 是否支持流式、工具调用、并行工具等
capabilities?: ModelCapabilities;
}
注册机制在 providers/all.ts 中,每个 Provider 一个文件:
providers/
├── anthropic.ts # Claude 系列
├── openai-codex.ts # Codex 系列
├── openai.ts # OpenAI 系列
├── deepseek.ts # DeepSeek
├── google-vertex.ts # Google Vertex AI
├── fireworks.ts # Fireworks
├── openrouter.ts # OpenRouter
├── cerebras.ts # Cerebras
├── mistral.models.ts # Mistral
├── minimax.ts # MiniMax
├── moonshotai-cn.models.ts # 月之暗面
└── ... (30+ providers)
对比 Grok Build:Grok Build 的 Provider 层在 xai-grok-sampler crate 中,核心是 Retry + Backoff + AuthScheme 三层。它不需要 30+ Provider 是因为 Grok Build 通过 ACP 协议的 backend_search 机制复用 xAI 服务器端的 model catalog。
对比 OpenCode:OpenCode 的四轴 LLM 路由体系(Protocol/Endpoint/Auth/Framing)比 Pi 更抽象,但 Pi 在 Provider 覆盖面上更广——尤其是对中文厂商的支持(MiniMax、月之暗面、小米、零一万物等),这是 Pi 的一个明显差异化。
四、工具系统:定义优先 + 双层注册
Pi 的工具系统在 packages/agent/src/types.ts 中定义了核心类型体系:
ToolDefinition → AgentTool → Agent
↓ ↓
schema (JSON) runtime implementation
ToolDefinition:工具的定义(名称、描述、参数 Schema),由 LLM 调用方消费。 AgentTool:工具的实现(validate → execute),由 Agent 执行方消费。
两层注册流程:
1. 注册 ToolDefinition(定义)→ 注册到 Agent
2. 注册 AgentTool(实现)→ 注册到 Agent
3. Agent Loop 中:LLM 选择工具 → Agent 匹配 ToolDefinition → Agent 调用对应的 AgentTool
Pi 支持两种工具执行模式:
- 串行执行:一个工具调用返回后再下一个
- 并行执行:多个工具调用同时执行(在 LLM 支持并行工具调用的情况下)
对比 Grok Build:Grok Build 的工具系统使用 ToolBridge + FinalizedToolset,通过 xai-tool-runtime crate 实现 类型安全的 Resource 注入。每个工具声明它需要的资源(Terminal、Cwd、SessionEnv 等),由系统注入。Pi 没有这一层——工具直接通过 input 参数获取依赖。
五、会话管理:JSONL 树形文件
Pi 的会话存储在 packages/coding-agent/src/agent-session.ts(3283 行!)中。它的存储格式是 JSONL 树形文件:
~/.pi/sessions/
├── {session_id}/
│ ├── config.json # 会话配置
│ ├── turns.jsonl # 核心数据:每个 turn 一行 JSON
│ └── branches/
│ └── {branch_id}/
│ ├── config.json
│ └── turns.jsonl
树形分支:Pi 支持从历史中的任何 turn 创建分支,这个分支可以独立发展。这是一个被严重低估的功能——在复杂的调试场景中,你想回到 10 分钟前的某个决策点重新探索,而不丢失那之后的分析结果。
对比 Grok Build:Grok Build 的会话通过 ACP 协议管理,5 个生命周期状态(Working → IdleResident → Dormant → Completed → DeadFailed),不支持分支。
对比 OpenCode:OpenCode 用 Drizzle SQLite 做持久化,支持 V2 Session 的 durable prompt admission——先持久化再执行。也不支持分支。
JSONL 树形 + 分支回滚,Pi 在会话管理的灵活度上独树一帜。
六、子代理:RPC 进程级编排
Pi 的子代理通过 packages/orchestrator/ 实现,和其他编码 Agent 最大的不同在于:它启动的是独立的 Pi 进程,通过 RPC 协议通信。
主 Pi 进程 → 启动子 Pi 进程(独立 Node/Bun 进程)
│ RPC 协议(JSON-RPC over stdio/Unix socket)
▼
子 Pi 进程 → 拥有自己的 Agent Loop、工具、会话
这种方式和 Grok Build 的 SubagentCoordinator(进程内 spawn_local)完全不同:
| 维度 | Pi (RPC 进程) | Grok Build (进程内) | OpenCode (???) |——|————-|——————-| | 隔离性 | ✅ 完全隔离,崩溃不影响主进程 | ❌ 同一进程,panic 可能级联 | | 资源开销 | ❌ 每个子 Agent 一个完整进程 | ✅ 共享进程资源 | | 通信方式 | JSON-RPC over stdio/socket | ACP 消息通道 | | 嵌套深度 | 无限制 | 1 层 | | 工具继承 | 从零启动,需重新注册 | 继承父进程的 ToolBridge |
Pi 的 RPC 模式更安全但更重。Grok Build 的进程内模式更高效但隔离性差——这和两者语言的选择一脉相承:Rust 的类型系统让进程内并发安全得多,而 TypeScript 需要用进程隔离来弥补。
七、扩展系统 vs 插件系统
Pi 的扩展系统比 Grok Build 和 OpenCode 的插件系统更轻量,但覆盖了完整的 Agent 生命周期事件。
在 packages/coding-agent/src/extensions/types.ts(1682 行)定义了所有扩展点:
onBeforeTurn/onAfterTurn:Turn 前后钩子onToolCall/onToolResult:工具调用钩子onUserMessage/onAssistantMessage:消息钩子onConfigLoad/onConfigSave:配置钩子onSessionCreate/onSessionDestroy:会话生命周期钩子
扩展可以:
- 注册新工具(通过
extensions.registerTool()) - 注册新命令(如
/help、/search) - 修改系统提示词
- 拦截和修改 LLM 响应
- 添加自定义 UI 组件(TUI 模式下)
对比 Grok Build:Grok Build 的 Hooks 系统在 xai-grok-agent 中,支持 before_turn 和 after_turn,通过 TurnHook trait 实现。Pi 的扩展系统更丰富,而 Grok Build 的 Hooks 更专注于安全和合规。
八、安全问题:没有内置权限系统
Pi 的 README 明确说明:
Pi does not include a built-in permission system for restricting filesystem, process, network, or credential access.
它给出的替代方案是外部容器化:
- Gondolin 扩展:在 Linux micro-VM 中运行工具
- Plain Docker:整个 Pi 进程跑在 Docker 容器中
- OpenShell:基于策略的沙箱
对比 Grok Build:Grok Build 有完整的 xai-grok-workspace 权限系统 + xai-grok-sandbox 沙箱 + permission::manager(6295 行的大模块)。它是三者中唯一有原生权限模型的。
对比 OpenCode:OpenCode 的 PermissionV1.Ruleset 在 Agent 定义中指定,支持细粒度的 allow/deny。
Pi 在安全性上最弱,但这也反映了它的目标用户群不同——Pi 更面向「个人开发者个人使用」,而 Grok Build 设计之初就考虑了企业级安全。
九、和 Grok Build 的深层对比
从代码层面,两者在四个关键维度上有本质差异:
9.1 并发模型
- Grok Build:Tokio Actor 模型 + LocalSet,
!Send类型安全 - Pi Agent:EventEmitter + async/await,无共享状态
9.2 工具系统
- Grok Build:
ToolBridge+FinalizedToolset,类型安全的 Resource 注入,支持 MCP 工具和本地工具统一注册 - Pi Agent:
ToolDefinition+AgentTool双层注册,定义优先,不支持 MCP
9.3 协议
- Grok Build:ACP 协议,支持 5 种部署模式(TUI/Headless/Stdio/Server/Leader)
- Pi Agent:纯 CLI 工具,无标准化协议层
9.4 验证系统
- Grok Build:Skeptic 验证 + Goal 系统 + Doom Loop 检测(最完善)
- Pi Agent:无验证系统
写在最后
Pi Agent 是一个定位清晰的个人开发工具。它在架构设计上有两个真正的创新:
- Steering + Follow-up 双队列:让用户可以在 Agent 思考过程中介入,这是其他编码 Agent 没有做到的。
- JSONL 树形会话存储 + 分支:为探索式编程提供了天然的「反悔」机制。
但它也有明显的短板:没有权限模型、没有标准化协议、没有验证系统。如果你把它和 Grok Build 对比,它们解决的问题不同——Grok Build 是「企业级 AI 编码基础设施」,Pi 是「个人开发者的编码助手」。
如果你想深入了解,Pi 的 @earendil-works/pi-ai Provider 抽象层是三者中最值得学习的——30+ Provider 的统一接口设计,对于任何需要支持多模型的项目都有参考价值。
相关阅读: