跳到主内容
创建时间:最后更新:作者:白川

Pi 项目概览与架构地图

Pi 是一个可被你完全拆开的终端 Agent 底盘:42 个供应商压在 10 种 API 协议之上,8 个内置工具默认启用 4 个,而它明确不替你做逐次审批。本文给出架构地图,后面每一篇都从这里出发。

大多数 Agent 工具给你的是一个产品:一套它认为合理的工作流,你在里面提需求、点确认、看结果。Pi 给的是一套底盘——一个 Agent Harness,核心小到你能在一次会话里读完它做了什么,然后按自己的方式拆开重组。

这个区别不是营销话术,它直接决定了你遇到问题时的处境:产品出 bug,你只能等它修;底盘出 bug,你读它的源码。本文先把「它是什么、它不做什么、代码怎么摆」讲清楚,后面每一篇都从这个地图出发。

1.1 Pi 是什么

官方文档对 Pi 的定义很短:一个可扩展的 Agent,你给它一个目标和一个工作目录,它能读文件、跑命令、改内容,把多步骤的任务做完。

这句话里有两个词值得停下来看。

**「工作目录」**不是装饰。Pi 的工具、资源发现、上下文文件、默认写入位置全部锚在它上面。项目里有什么文件、有什么 .pi/ 配置、有没有 AGENTS.md,都从当前目录往上找。你换目录,就是在换一个工作上下文。

**「可扩展」**是 Pi 唯一真正坚持的原则。它意味着两件事:核心行为可以被扩展接管(不是只能加东西),以及扩展之间可以互相替换(内置的也能换掉)。第二点在 内置 MCP 与 Codemode 那两篇里体现得最明显——codemode、tool-search、mcp 三个内置扩展都带 replaceable: true,你写一个同名扩展就能顶掉它们。

Pi 的仓库在 earendil-works/pi,npm scope 是 @earendil-works。本文写作时的基线是 v1.3.0-16-g8efd314fa。

1.2 它现在能做什么

先把能力摆出来,因为这一节是后面所有文章存在的理由。

  • 八个工作目录级别的工具:read bash powershell edit write grep find ls。默认只启用前四个里的 read bash edit write,grep find ls 和 powershell 要自己开。
  • 42 个供应商、10 种聊天 API 协议、1 种图像协议、4 种分类协议。认证只有两种:api_key 和 oauth。
  • MCP 内置。有 pi mcp add/remove/list/login/logout 子命令、/mcp 命令、两级配置文件和一套 OAuth 流程。
  • 四种交互界面 + 一个 SDK:交互式 TUI、print 模式、JSON 事件流、RPC 模式,以及把 Pi 当库嵌入的 TypeScript SDK。
  • 四级定制阶梯:上下文文件 → Prompt Template → Skill → Extension,以及把它们打包分发的 Pi Package。
  • 41 个扩展生命周期事件,覆盖信任判定、资源发现、会话生命周期、模型请求前后、消息流、工具执行等环节。

注意这份清单里没有的东西,比有东西更能说明 Pi 的性格,下一节专门讲。

1.3 它刻意不做什么

Pi 不内置的功能曾经被总结成一句话:「子 Agent、Plan 模式、TODO 列表、权限弹窗、后台 bash」。

这句话至今仍然成立——这些确实不是内置功能。但它需要一个重要补充,否则会误导你:这些能力官方都提供了可读的示例实现,放在 packages/coding-agent/examples/extensions/ 下:

你想要的官方示例
子 Agent
subagent/
Plan 模式
plan-mode/
TODO 列表
todo.ts
破坏性操作确认
permission-gate.ts、confirm-destructive.ts
Git 检查点与回滚
git-checkpoint.ts
交互式 shell
interactive-shell.ts
路径保护
protected-paths.ts

所以准确的表述不是「Pi 不做这些,所以你要自己从零实现」,而是:Pi 不替你决定工作流,但把实现工作流所需的每一块原语都给你,并且官方示范了怎么拼。 差别在于你读到的是官方维护的参考实现,而不是社区里质量参差的插件。

这个原则有个名字,社区里通常叫「原语而非功能」(primitives, not features)。它值得展开成一句可操作的判断:

当你发现 Pi 缺少某个能力时,先问它缺的是原语还是策略。缺原语,说明这个功能非装不可,你只能接受别人的设计;缺策略,说明你只是需要有人替你做决定——那正好是你该自己写的那部分。

子 Agent 缺的是策略(谁来拆分任务、什么时候收敛),原语(起子进程、等结果、读文件)Pi 全有。Plan 模式缺的是策略(什么时候该先规划),原语(往上下文里追加一段结构化文字)是现成的。TODO 列表同理——你要的只是「在会话里持久化一组条目」这个原语,怎么决定条目内容是你的事。

反过来,真正的原语级缺失就很少见了。这也解释了为什么 Pi 能长期维持「东西少但能用」的状态:它拒绝的不是能力,是替用户做决定的那部分代码。

其中「权限弹窗」这一条最容易被误读,下一节单独澄清。

1.3.1 「没有权限弹窗」到底是什么意思

这是最容易被断章取义的一条,需要说清楚。

Pi 的官方安全文档里有一句毫不含糊的表述:Pi 能以启动它的账户的权限读、改、执行文件,并且它不会在每次工具调用前征求批准。

这句话经常被拿去做文章标题,语气还很重。但要理解它得连着看两件事:

第一,project trust 确实存在,但它管的不是这个。 Pi 在启动时如果发现工作目录下有受保护的资源(.pi/settings.json、.pi/mcp.json、.pi/extensions、.pi/skills 等),会要求一次信任决定。这个决定存在 ~/.pi/agent/trust.json 里,按规范化目录路径索引,最近的祖先目录优先。官方文档自己写了它的边界:信任只决定这些项目资源要不要加载,不限制工具调用能访问或影响什么。

第二,官方甚至记录了 project trust 的一个已知缺口:项目的 sessionDir 设置是在信任判定之前被读取的,即使你拒绝信任,也回退不了这一次读取。

所以完整的图景是:Pi 有一个启动期的资源加载门禁,但没有运行期的逐次调用审批。 把这两件事混成一句「Pi 什么都不管」或反过来「Pi 有安全机制」,都是不准确的。

如果你确实需要运行期审批,路径是现成的:tool_call 事件可以被扩展拦截并返回 { block: true },官方示例是 permission-gate.ts。细节在 工具系统与信任边界。

真正能兜住风险的是运行环境边界——容器、虚拟机或策略沙箱,让 Pi 的进程压根碰不到你的主机资源。这条线在 隔离与约束 里展开,官方给了四种形态。

1.4 架构地图:11 个包,两棵互不相干的树

packages/ 下有 11 个包,但它们不是一棵依赖树,而是两棵互不相干的树。搞清楚这一点,后面看源码就不会迷路。

第一棵是 CLI 树,也就是你装 pi 时真正拿到的东西:

text
                    coding-agent (pi)
                            │
        ┌───────────┬───────┼────────┬──────────────┐
     agent-core   pi-ai   pi-mcp  pi-codemode    pi-tui
        │          │                              │
        └──────────┤                              │
                   └──────► pi-telemetry ◄────────┘
                            (仅类型依赖)

依赖关系是可验证的,不是猜的:

  • pi-coding-agent 依赖 pi-agent-core pi-ai pi-codemode pi-mcp pi-tui 五个 workspace 包;
  • pi-agent-core 只依赖 pi-ai;
  • pi-ai 依赖 pi-telemetry,而且只是一个 import type——运行时零耦合;
  • pi-mcp pi-codemode pi-tui 是叶子包,不依赖任何 Pi 包。

第二棵树是 Durable 树,跟 CLI 没有任何关系:

text
    chord ──► pi-durable ──► pi-env

chord(热重载宿主运行时)、pi-durable(实验性的可恢复 Agent harness)、pi-env(SSH 远程执行,带一个 Rust 守护进程)三个包自成一体,pi-coding-agent 不依赖它们中的任何一个。

还有第四个包 pi-evals,它是 private: true,从不发布,是 Pi 团队自己用的评测工具(用 Docker 做「有文档 vs 无文档」的 A/B 对照)。

本文系列不覆盖 Durable 树和 pi-evals。 理由是可验证的:它们不在 CLI 的依赖路径上,文章里的任何一句关于它们的话,你都无法通过运行 pi 来验证。这不是它们不重要,而是它们属于另一条叙事线——如果你关心的是「搭自己的 Agent 服务」,那是 pi-durable 的 README 该讲的事。

顺带一提,pi-tui 不依赖 pi-ai。终端 UI 库对大模型这个概念一无所知,它只负责把字符串画到屏幕上。这个边界在 架构(三)里会展开。

1.5 四种界面,一套机制

Pi 的交互方式不止一种,但它们共享同一套 agent 和 session 机制——官方文档里这句话是明确的。

界面入口适合
交互式 TUI
pi
日常编码
Print 模式
pi -p "…"
一次性问答、脚本
JSON 事件流
pi --mode json
消费结构化事件
RPC 模式
pi --mode rpc
控制一个常驻进程
TypeScript SDK
createAgentSession()
把 Pi 嵌进你的程序

不管从哪个入口进去,一轮 agent 循环都是同一套流程:

  1. 提交的消息被追加到当前分支;
  2. Pi 用「系统提示 + 当前分支 + 可用工具 + 模型设置」组装一次模型请求;
  3. 供应商流式返回响应,里面可能混着文本和工具调用;
  4. Pi 记录响应,逐个执行工具调用,记录结果;
  5. 这一轮结束。如果工具结果或队列里还有消息,就再来一轮;否则运行终止。

这个循环在 pi-agent-core 里是纯函数——不持有状态,状态全在你传进去的 context 里。具体拆解见 架构(二)。

三件事顺着这个流程自动发生,值得先知道:

  • steering 消息在当前 assistant 轮次结束后进入;follow-up 消息要等 Agent 把手头的事干完才进入。这两种队列可以分别配置投递模式。
  • 中止会停止当前运行,并把队列里的消息退回编辑器。
  • 会话是一棵树,不是一条线。从更早的条目继续,会在同一个文件里长出另一个分支。

1.6 三条阅读路径

全集 19 篇,按下面的顺序读效率最高,但三条路径都是完整的。

路径一:只想把 Pi 用起来(6 篇)

01 → 02 安装与运行模式 → 03 交互式会话 → 04 模型与供应商 → 05 配置 → 06 工具与信任边界

路径二:想把 Pi 改造成自己的东西(11 篇)

前 6 篇 → 07 定制阶梯 → 08 MCP → 09 Codemode → 10 扩展开发 → 11 Pi 包

路径三:想读源码(9 篇,含全部三篇架构)

01 → 06 工具 → 17 pi-ai → 18 pi-agent-core → 19 pi-tui → 14 会话格式 → 15 上下文与压缩

1.7 事实来源

本文所有数字都可在仓库里复算,不是估计值:

断言来源
8 个内置工具,4 个默认启用
packages/coding-agent/src/core/tools/index.ts、src/core/settings-manager.ts:85
42 个供应商、10/1/4 种 API 协议
packages/ai/src/types.ts 的 KnownProvider / KnownApi / KnownImageApi / KnownClassifierApi
认证只有 api_key 与 oauth
packages/ai/src/auth/types.ts
4 个内置扩展,后三个可替换
src/extensions/index.ts:8-13
41 个扩展事件
src/core/extensions/types.ts 全部 on(event: …) 重载
11 个包与 CLI 依赖树
packages/*/package.json 的 dependencies
Agent 循环与界面语义
docs/how-pi-works.md

完整事实表(含每条断言的推导命令)见专栏仓库的 research/pi-agent/FACTS.md。