开源编程 Agent pi 是什么:三个独立包拆出来的终端编码工具
本文基于 pi v0.82.1(仓库 commit 027a584,2026-07-28)梳理,该项目仍在快速迭代,具体行为以官方文档 https://pi.dev/docs/latest 与仓库最新代码为准。
pi 值得你花时间看的地方,不是它又是一个能在终端里改代码的 Agent,而是它把「跟模型说话」「跑 Agent 循环」「画终端界面」这三件事拆成了三个可以分别 npm install 的包 —— 所以你既可以把它当命令行工具用,也可以只捞走中间某一层塞进自己的系统里。 这个拆分不是文档里的一句宣传语,而是仓库目录和 npm 包名一一对应的事实,你 clone 下来就能逐个核对。
先把身份说清楚:仓库在 https://github.com/earendil-works/pi ,MIT 许可证,截至 2026 年 7 月 GitHub 上约 8 万 star(这个数字随时在变,看当天页面为准)。文档站是 https://pi.dev/docs/latest 。官方文档首页对它的自我定义是 “a minimal terminal coding harness” —— 核心保持小,靠 TypeScript 扩展、skills、prompt templates、themes 和 pi packages 往外长。
站内已经有两篇讲通用方法论的文章:Agent 框架横向对比讲的是选型时该看哪些维度,多 Agent 框架怎么选讲的是编排层的判断标准。这篇不重复那套框架,它换个方向 —— 拿一个你现在就能 clone 下来逐行核对的真实项目,看这些维度在代码里到底长什么样。方法论告诉你该问什么问题,这篇给你一份具体答案。
一、它把哪三块拆开了
README 的 All Packages 表列了四个包。三个是主线,一个是它们共用的界面层:
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
@earendil-works/pi-ai | 统一的多厂商 LLM 接口:provider 与模型目录、auth 解析、流式事件、token 与成本统计 | packages/ai | 你只想要一个「换模型不改代码」的调用层,不需要 Agent 循环时 |
@earendil-works/pi-agent-core | Agent 运行时:工具调用、状态管理、事件流、steering 与 follow-up 队列 | packages/agent | 你要在自己的应用里跑一个带工具的 Agent 循环 |
@earendil-works/pi-coding-agent | 交互式编码 Agent 的 CLI:会话、扩展、上下文文件、命令行入口 | packages/coding-agent | 你只是想在项目目录里敲 pi 干活 |
@earendil-works/pi-tui | 终端 UI 库,差分渲染、同步输出、内置组件 | packages/tui | 你给 pi 写扩展要画自定义界面,或者自己做 CLI 工具 |
| 文档全集 | 从 quickstart 到 session 文件格式的完整说明 | packages/coding-agent/docs/ | 任何时候 —— 这是本文的主要出处 |
| 项目规则 | 提交、测试、依赖、发布的硬性约定 | AGENTS.md | 你打算给它提 PR 时 |
packages/ 下还有 evals、server、storage 三个工作区目录,README 的 All Packages 表没有把它们列进来,但它们的性质并不一样:packages/evals 的 package.json 里标了 private: true,是纯内部工作区;packages/server 和 packages/storage/sqlite-node 则是正常命名的包,后者对应 @earendil-works/pi-storage-sqlite-node。packages/agent/README.md 专门解释了这个拆分的原因 —— SQLite 会话后端和 node:sqlite 适配器被挪进独立包,是为了让 agent 核心包默认不拖进运行时内建模块和原生 SQLite 依赖,后端本身接受一个运行时相关的 SQLite 工厂,将来别的存储后端可以各发各的包。所以读这张表的正确姿势是:它列的是「主线四件套」,不是「全部可安装的东西」。
这个拆法的直接后果是:依赖方向是单向的。packages/agent/README.md 开头写得很直白 —— “Stateful agent with tool execution and event streaming. Built on @earendil-works/pi-ai.”。Agent 运行时建在模型接口之上,CLI 建在 Agent 运行时之上,界面层被 CLI 用但不反过来知道 Agent 的存在。你从任何一层切进去,下面的层都是完整可用的,上面的层你可以整个不要。
对你的意义很实际:如果你的诉求是「给内部系统加一个能调工具的 Agent」,你装 pi-agent-core 就够了,不会顺带背上一整套终端界面代码。这跟「SDK 还是框架」那道老题目是同一个问题的两面,站内那篇Agent SDK 与框架的取舍讲的判断逻辑在这里直接适用 —— 区别是 pi 不逼你二选一,它把选择权放在你 install 哪个包上。
二、一个请求从回车到模型返回,经过哪几层
这是理解 pi 最有价值的一段。你在终端里敲完一句话按下回车,往下依次发生这些事。
第一层:CLI 把输入变成一次 prompt 调用。 pi 启动时会加载上下文文件 —— 按 quickstart 的说法是 ~/.pi/agent/AGENTS.md 作为全局指令,加上当前目录及其父目录里的 AGENTS.md 或 CLAUDE.md。你输入的内容里如果有 @ 引用的文件、粘贴的图片,也在这一层被解析进去。
第二层:Agent 运行时整理上下文。 packages/agent/README.md 给了一张消息流图:
AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM
(optional) (required)
AgentMessage 是个比 LLM 消息更宽的类型,除了 user、assistant、toolResult,还能通过 declaration merging 塞进应用自己的消息类型。LLM 只认前三种,所以 convertToLlm 负责过滤掉那些纯 UI 用途的消息、把自定义类型翻成 LLM 认识的格式。transformContext 是可选的一步,用来裁剪旧消息、注入外部上下文 —— 上下文压缩这类活就挂在这里。这一层怎么设计直接决定你的成本曲线,可以对着上下文管理的实操方法一起看。
第三层:模型接口解析 auth 并发请求。 packages/ai/README.md 里说,provider 是运行时单位,它自己拥有模型目录、auth 解析和流式行为;Models 集合持有一堆 provider,把每个请求路由给拥有该模型的那个 provider。请求头的合并顺序文档写得很明确:
provider auth headers -> model.headers -> explicit options.headers -> transformHeaders -> Provider.stream*()
再往下是 API 实现层,也就是真正的线上协议。文档的说法是 Anthropic 系模型走 anthropic-messages,OpenAI 走 openai-responses,而 xAI、Groq、Cerebras、OpenRouter 等大多数走 openai-completions;GitHub Copilot、OpenCode Zen 这类混合 API 的 provider 按模型逐个分发。
第四层:流式事件往回冒泡。 模型这端吐出的是 text_delta、thinking_delta、toolcall_delta 这些细粒度事件;Agent 运行时把它们包成自己的事件序列往上抛。README 给的带工具调用的完整序列长这样:
prompt("Read config.json")
├─ agent_start
├─ turn_start
├─ message_start/end { userMessage }
├─ message_start { assistantMessage with toolCall }
├─ message_update...
├─ message_end { assistantMessage }
├─ tool_execution_start { toolCallId, toolName, args }
├─ tool_execution_update { partialResult }
├─ tool_execution_end { toolCallId, result }
├─ message_start/end { toolResultMessage }
├─ turn_end { message, toolResults: [toolResult] }
│
├─ turn_start
├─ message_start { assistantMessage }
├─ message_update...
├─ message_end
├─ turn_end
└─ agent_end
一个 turn 等于「一次 LLM 调用 + 随之而来的工具执行」。工具执行完,结果作为 toolResult 消息回到上下文里,下一个 turn 开始 —— 循环就这么转下去,直到模型不再要求调工具。
第五层:TUI 把事件画成你看到的东西。 差分渲染只重画变化的部分,这是终端里做流式输出不闪屏的基本要求。
把这五层连起来看,每一层的边界都是一个可以订阅、可以替换、可以单独测试的接口。落到排障上有个具体好处:docs/development.md 里记着一个隐藏命令 /debug,它会把渲染出的带 ANSI 码的 TUI 行、以及最后一批发给 LLM 的消息写进 ~/.pi/agent/pi-debug.log。这两样东西正好对应链条的两端 —— 你看到的画面,和模型真正收到的输入。当 Agent 给出的答案明显不对时,先看这个文件能省掉大量猜测:多数时候问题不在模型,而在某个上下文文件没被加载、或者 convertToLlm 把你以为会传过去的消息过滤掉了。
三、它默认给模型多大的手
quickstart 里说得很清楚:默认给模型四个工具 —— read 读文件、write 创建或覆盖文件、edit 打补丁、bash 跑 shell 命令。另外还有几个只读工具 grep、find、ls,通过 tool options 开启。pi 在你的当前工作目录里跑,能改那里的文件;文档直接建议你用 git 或别的 checkpoint 机制来做回滚。
工具执行模式可以配。toolExecution 默认是 parallel:先按顺序做 preflight,然后并发执行被放行的工具,每个工具敲定就立刻发 tool_execution_end。另一种是 sequential,一个一个来。这里有个容易被忽略的细节 —— 并行模式下事件按完成顺序走,但落到会话里的 toolResult 消息仍然按 assistant 给出调用的顺序排。也就是说你看到的顺序和模型看到的顺序不是一回事,做日志分析时别搞混。
两个钩子决定了你能插手到什么程度:beforeToolCall 在 tool_execution_start 之后、参数校验完成之后跑,可以直接拦下这次执行;afterToolCall 在工具跑完、最终事件发出之前跑,可以改结果。这对钩子是 Agent 运行时那一层的名字;CLI 这一层把 beforeToolCall 包成了扩展能订阅的 tool_call 事件(packages/coding-agent/src/core/agent-session.ts 里就是把它挂上去的),处理函数返回 { block: true, reason } 就等于拦下这次调用。扩展文档 Quick Start 里给的示例正是拿它做权限门 —— 检测到 bash 命令里含 rm -rf 就弹一个确认框,用户不点头就返回 block。所以你要插手工具执行,选哪一层取决于你是在写扩展还是在直接用 pi-agent-core,两边是同一个拦截点的两个入口。
还有一对队列值得知道:steer() 让你在工具正在跑的时候插话,等当前 assistant 消息的所有工具调用结束后注入,下一个 turn 模型就看到了;followUp() 是排在 Agent 本来要停下来之后的活。这两个东西是交互式 Agent 的刚需 —— 你看着它跑偏了想拦,靠的就是前者。
四、边界与代价:它明确不管什么
这一节比前面都重要,因为它决定了 pi 适不适合你。
它没有权限系统。 README 写得毫不含糊:pi 不包含用于限制文件系统、进程、网络或凭据访问的内置权限系统,默认就以启动它的那个用户和进程的权限运行。docs/security.md 进一步解释了为什么这是有意为之 —— 一个半吊子的进程内沙箱容易被误当成安全边界,而它实际上仍然依赖宿主的 shell、文件系统、包管理器、凭据和扩展代码;真正的隔离必须来自操作系统或虚拟化/容器边界。
项目信任不是沙箱。 .pi/settings.json、.pi/extensions、.pi/skills 这类项目本地资源需要你先信任这个目录才会加载,决定存在 ~/.pi/agent/trust.json。但文档明确说了:这只是一道「输入加载」的闸门,防的是一个仓库在你点头之前偷偷改掉 pi 的设置或扩展,它不能让不可信的代码、不可信的提示词或不可信的模型输出变得安全。来自仓库文件、注释、文档、上下文文件或构建输出的提示词注入,被它归类为本地 Agent 的预期风险,明说无法可靠防住。
要边界就自己上容器。 文档给了三种模式:Gondolin 扩展(pi 和 provider 认证留在宿主机,把内置工具和 ! 命令路由进本地 Linux 微虚机)、纯 Docker(整个 pi 进程跑在容器里)、OpenShell(整个进程跑在策略受控的沙箱里)。security.md 还额外提醒:别把宿主的 ~/.pi/agent 挂进容器,除非你真的想让容器碰到宿主的会话、设置和凭据;bind-mount 成读写的话,容器里的写入照样能改宿主文件。这套取舍值得对照最小权限设计那篇一起想 —— pi 的立场是把权限边界整个外包给操作系统。
它只收支持工具调用的模型。 packages/ai/README.md 开头有一条注记:这个库只收录支持 tool calling(function calling)的模型,因为这是 agentic 工作流的前提。所以你想拿它跑纯文本补全类的老模型,这个目录里根本不会有。
它不承诺向后兼容。 AGENTS.md 的 Code Quality 一节里有一句 “Do not preserve backward compatibility unless the user asks for it.”。发布策略也印证了这点:所有包 lockstep 版本号一起走,patch 是修复加新增,minor 是破坏性变更,没有 major 版本。翻译成人话 —— 一个 minor 号的跳动就可能要你改代码,你要是把它嵌进生产系统,锁版本是必须动作。
什么场景不适合。 三类:一是你需要一个能对着审计人员交差的权限矩阵,pi 本身给不了,你得在外面套一层;二是你要在多租户环境里跑不可信的用户任务,那必须是容器加策略,pi 只是里面的一个进程;三是你团队完全不碰终端、只要 IDE 里的补全体验,那这个项目的形态跟你的需求不在一个方向上。
五、上手与避坑清单
装的时候带上 --ignore-scripts。 官方给的命令是 npm install -g --ignore-scripts @earendil-works/pi-coding-agent。为什么会踩:大多数人装全局 CLI 就是 npm i -g 一把梭,习惯性地把这个参数吃掉了。怎么避:记住文档写明 pi 正常 npm 安装并不需要 lifecycle scripts,这个参数不是可选装饰而是供应链约束的一部分 —— 同一套思路在仓库里是成体系的(.npmrc 设了 save-exact=true 和 min-release-age=2,发布的 CLI 包还带一份 npm-shrinkwrap.json 来锁传递依赖)。Linux 和 macOS 上也可以用 https://pi.dev/install.sh 这个安装脚本,它底层仍然走 npm 全局安装。
非交互模式不会弹信任提示。 为什么会踩:你在本地交互跑得好好的,扩展、项目设置全生效;一放进 CI 用 -p 或者 --mode json、--mode rpc,行为突然变了,扩展像没装一样。原因是这三种模式不显示信任提示,在没有可用的已保存决定时,defaultProjectTrust 的 ask(默认值)和 never 都会直接忽略这些项目资源。怎么避:给这一次运行显式加 --approve(或 -a)来覆盖项目信任,或者在全局设置里把 defaultProjectTrust 明确设成你要的值 —— 别指望默认值替你做决定。
改了上下文文件要重新加载。 为什么会踩:你在 AGENTS.md 里加了一条「改完代码跑 npm run check」,模型完全无视。因为 pi 是在启动时加载上下文文件的。怎么避:改完之后重启 pi,或者在会话里跑 /reload。扩展也一样 —— 文档特意提醒,放在 ~/.pi/agent/extensions/ 或 .pi/extensions/ 这两个自动发现的位置才能被 /reload 热重载,用 pi -e ./path.ts 只适合临时测试。
别用低层 agentLoop 去做需要屏障的事。 为什么会踩:你直接调 agentLoop() 订阅事件,在 message_end 里做异步落库,然后发现 beforeToolCall 跑起来的时候你的状态还没写完。文档对这个说得很明确:这些低层流是观察式的,保证事件顺序,但不会等你的异步处理 settle 就继续推进后面的生产阶段。怎么避:需要「消息处理必须先完成、再开始工具 preflight」这种屏障语义时,用 Agent 类而不是裸的 agentLoop() / agentLoopContinue()。
并行工具里混进一个串行工具,整批都变串行。 为什么会踩:你给某个危险工具单独标了 executionMode: "sequential",以为只影响它自己,结果整批工具的执行时间全线拉长。文档的规则是:一批工具调用中只要有任何一个的目标工具标了 sequential,不管全局设置是什么,整批都串行执行。怎么避:把这类工具的触发时机跟高频并行工具错开,或者接受这个代价并在设计工具集时就想清楚。
卸载不等于清干净。 为什么会踩:你以为 npm uninstall -g 之后机器上就没痕迹了,实际上设置、凭据、会话和已安装的 pi packages 都还留在 ~/.pi/agent/ 里。怎么避:换机器或者交接设备时,把这个目录单独处理一遍 —— 尤其是里面的 auth.json。
关于模型来源的一句实话。 pi-ai 支持的 provider 列表很长,涵盖 OpenAI、Anthropic、Google、Mistral、Groq 等一批海外服务,也包括 DeepSeek、Moonshot AI、MiniMax、ZAI、Xiaomi MiMo 等提供了中国区独立入口的服务。需要说清楚的是:Anthropic、OpenAI 这类海外服务商官方对中国大陆存在区域限制,不支持直连;市面上存在第三方中转,本文不背书也不给具体渠道。各家的计费方式、可用区域和接入规则不同而且会调整,以官方最新说明为准。真要在国内团队里落地,从 provider 列表里挑一个官方在国内可用的入口是更省事的路径。
六、接下来该读哪个文件
如果你只是想用,路径是:packages/coding-agent/docs/quickstart.md 装好跑通第一个会话 → docs/usage.md 摸清交互模式和 CLI 参数 → docs/settings.md 把默认模型、主题、信任策略配成你要的样子。
如果你想把它嵌进自己的系统,路径不一样:先读 packages/agent/README.md 的 Message Flow 和 Event Flow 两节,把消息怎么变成 LLM 输入、事件怎么冒上来这两件事吃透 —— 这是整个项目认知成本最高也最值钱的一段;再读 packages/ai/README.md 的 Auth 和 Providers 两节,搞清楚凭据从哪来、请求头按什么顺序合并;最后按你的集成形态挑一条 —— 嵌 Node 应用看 docs/sdk.md,跨进程集成看 docs/rpc.md,只要结构化输出看 docs/json.md。
如果你想改它或者提 PR,先读 AGENTS.md。这份文件把约束写成了可执行的清单,而不是一堆倡议:不许用内联 import(await import()、import("pkg").Type 这类一律禁,只能顶层导入);只能用可擦除的 TypeScript 语法,也就是 Node 的 strip-only 模式认得的那部分,参数属性、enum、namespace、import =、export = 全不能用,要写显式字段加构造函数赋值;不许直接改 packages/ai/src/models.generated.ts,要改就去改 packages/ai/scripts/generate-models.ts 再重新生成;提交只能 stage 具体路径,git add -A 和 git add . 明令禁止,git reset --hard、git checkout .、git clean -fd、git stash、git commit --no-verify 也在禁用清单里。README 里说这份文件是同时写给人和 Agent 看的,读下来也确实是这个味道 —— 规则被写成机器能照做的判定条件,而不是「请注意保持代码整洁」这类只有人能领会的提示。这个细节本身也值得借鉴:如果你打算让 Agent 在自己的仓库里干活,约定就得写成这个形状。README 顶部还有一条:新贡献者的 issue 和 PR 默认自动关闭,维护者每天回看被自动关闭的那些。
最后给一份自检:你能说清 transformContext 和 convertToLlm 各自解决什么问题吗?你知道自己那台机器上的 ~/.pi/agent/ 里都有什么吗?你的使用场景里,「没有内置权限系统」这句话是可以接受的前提,还是必须在外面补一层的缺口?这三个问题都有答案,你就算真的看懂了这个项目要你付什么代价。
这个系列的其余文章
这篇是总览。想往下挖,按下面两条线走:先把它用起来,或者直接读源码。
上手与使用
- 开源编程 Agent pi 接入国产模型
- 开源编程 Agent pi 上手实录
- 开源编程 Agent pi 没有内置权限系统
- 开源编程 Agent pi 的上下文压缩
- 开源编程 Agent pi 的扩展、技能、提示词模板与包该怎么选
- 把开源编程 Agent pi 嵌进自己的程序
- 开源编程 Agent pi 的会话怎么存、怎么续、怎么翻回去看
- 把开源编程 Agent pi 调顺手
- 开源编程 Agent pi 的本地模型接入
- 开源编程 Agent pi 的终端界面为什么不闪
- 终端编程 Agent 选型五条线
源码与设计
- 开源编程 Agent pi 的仓库结构导读
- 开源编程 Agent pi 的统一模型接口
- 开源编程 Agent pi 的服务商层
- 逐段读开源编程 Agent pi 的主循环
- 开源编程 Agent pi 的基础工具层
- 开源编程 Agent pi 的文件编辑
- 开源编程 Agent pi 的会话存储
- 开源编程 Agent pi 的上下文压缩
- 开源编程 Agent pi 的系统提示词是怎么拼出来的
- 开源编程 Agent pi 的技能机制
- 开源编程 Agent pi 的扩展加载器
- 开源编程 Agent pi 的安全边界
- 开源编程 Agent pi 的钩子与可观测性
- 开源编程 Agent pi 的模型解析链路
- 开源编程 Agent pi 的可持久化运行
- 开源编程 Agent pi 的服务端包
- 给开源编程 Agent pi 接入自定义模型服务
- 开源编程 Agent pi 的评测包