小岛AI
| ONLINE |

posts/pi-agent-harness-source-guide.md

Pi 不是 Claude Code 平替,它是一套 Agent 底座

小岛AI 2026 / 08 / 12

最近有组测试挺抓眼球。

同一个本地 DeepSeek V4 Flash,分别塞进 Pi、OpenCode 和 Claude Code,做八个真实 bug 修复。结果里,Pi 平均 2.1 分钟完成,Claude Code 平均 8 分钟。前者每轮固定开销约 1340 tokens,后者约 23132 tokens。

四倍左右的时间差,十几倍的固定上下文差距。放在标题里,当然很容易写成「DeepSeek 加 Pi 跑赢 Claude Code」。

但我把原始数据和 Pi 源码拉下来读完,结论反而没那么热血。

这组测试没有证明 Pi 写代码更好。Pi、OpenCode、Claude Code 以及后来补测的 Nanocoder,质量置信区间彼此重叠,统计上分不出明显高下。它真正说明的是另一件事。

当模型固定以后,包在模型外面的那层软件,会显著改变它完成任务的路径、耗时和 token 开销。

模型像发动机,agent harness 像变速箱、方向盘和仪表盘。发动机没换,传动结构不同,车照样会开出完全不同的感觉。

这也是 Pi 真正值得看的地方。它不只是一个更轻的 Claude Code,更像一套可以拆开、改造、重新组装的 Agent 底座。

别急着宣布 Pi 赢了

先把那组测试的边界说清楚。

测试者固定了 DeepSeek V4 Flash,挑了八个范围明确的逻辑 bug,每种 harness 跑 24 次。Pi 平均质量分是 2.34,Claude Code 是 2.42,OpenCode 是 2.07。只看小数点,Claude Code 甚至还高一点。

问题在于每次运行的波动很大,三个区间完全重叠。作者自己的判断也很克制,少于大约十次重复测试,很可能只是在测天气。

真正拉开的是效率。Pi 只有 4 个默认工具,平均输出约 14775 tokens,耗时 2.1 分钟。Claude Code 带着 27 个工具,平均输出约 58370 tokens,耗时 8 分钟。它读得更多,探索得更久,第一刀也下得更晚。

同一 DeepSeek 模型下,不同 harness 的平均耗时

这里还有两个数字容易混在一起。固定开销,是每轮请求都要带给模型的系统提示词与工具说明。输出 tokens,则是模型在分析、调用工具和回答过程中真正生成的内容。前者会吃上下文,也会影响 prompt cache。后者直接决定解码时间,在按量计费的 API 上还会进入账单。

Pi 两头都轻,原因也不同。固定开销小,是因为默认提示词与工具少。输出更少,则说明同一个模型在这套动作空间里更早收敛。不能简单理解成删掉二十几个工具就能获得同样结果,工具描述、系统提示词、工具返回格式与模型训练偏好是一起起作用的。

我更关心后面这个变量。团队做 Agent 评测时,至少应该同时记录任务成功率、运行时间、输入输出 tokens、工具调用数和首次修改位置。只报成功率,重型 harness 可能看起来很稳。只报速度,极简 harness 又可能在复杂任务里少做了必要检查。把几条曲线放在一起,才知道省掉的是浪费还是保障。

这不等于工具越少越好。

八个聚焦型 bug,不需要长时间规划、浏览网页、多人协作和复杂审批。换成长链路迁移、跨仓库重构或高风险生产操作,Claude Code 那些额外能力可能就不是负担,而是护栏。

所以,Pi 跑得快的准确说法应该是,在这组模型和任务里,极简 harness 用更短路径拿到了相近结果。

这个结论没那么炸,却更有用。因为它提醒我们,模型评测不能只报模型名。系统提示词多长,默认塞了多少工具,什么时候压缩上下文,工具结果怎样回填,是否会派子 Agent,都会进入最终账单。

以后再看到某个 Agent 神器「能力暴涨」,先别只问底下用了哪个模型。还要问,它让模型绕了多少路。

为什么我更愿意叫它 harness

ZenML 那篇 Building Pi 在标题里用了 framework。Mario 自己早期介绍 Pi 时,也偶尔混用 framework 和 harness。

可如果看设计取舍,我觉得 harness 更准确。

Framework 往往替你规定正确姿势。生命周期怎么走,状态放哪里,插件怎样注册,任务如何编排,开发者进入它划好的轨道里填空。

Harness 更像套在模型外面的工作装具。它负责把提示词、工具、上下文、会话和终端接起来,让模型能够行动,但尽量不替你决定工作流应该长什么样。

Armin Ronacher 写 Pi时,抓住了两个点。一个是极小的核心,另一个是能把状态写进会话的扩展系统。Pi 默认只有 read、write、edit、bash 四把工具,没有内置 MCP、子 Agent、计划模式、待办列表和后台 bash。

好家伙,别人忙着给 Agent 加军火库,Pi 先把柜子搬空了。

这不是作者来不及做。Mario 在最初的设计文章里把这些都列成了主动舍弃的能力。要计划,就写一个 Markdown 文件。要后台任务,就用 tmux。要 MCP,可以通过 CLI、扩展或外部工具接。要子 Agent,也不必先在核心里造一套复杂编排。

关键不在于 Pi 反对这些功能,而在于它不愿意让所有用户每一轮都为这些功能付上下文税。

工具定义不是免费的。模型每次请求都要先读一遍工具名称、参数和说明。工具越多,可选动作越多,模型也越容易先搜索、再规划、再确认,最后才开始改那几行代码。

Pi 的思路是,常用能力留在核心,不常用能力留在上下文之外。需要时再通过 skill、CLI 或扩展拿进来。

Armin 举的例子更大胆。Pi 的扩展可以把自定义状态持久化进会话,还能热重载。Agent 可以自己写一个扩展,重新加载,测试,再继续修改。会话本身又是一棵树,可以临时分叉去修工具,修好后回到主线,只带回摘要,不把整段岔路全塞进主上下文。

会话树看着只是一个交互功能,其实是在处理 Agent 最贵的资产,注意力。线性聊天一旦走错路,要么带着整段错误探索继续跑,要么压缩时把有用细节一起揉掉。树结构允许主线保留干净状态,把查文档、修扩展、代码评审这些旁路放到分支上。结束后带回结论,不带回所有脚印。

同样的取舍也出现在 MCP 上。MCP 很方便,但几十个远程工具如果在会话启动时全部进入工具列表,模型每轮都得重新面对这张菜单。Pi 更偏爱让 Agent 先通过 skill 知道工具存在,再在需要时调用 CLI。它牺牲了一点统一发现体验,换来更可控的上下文体积。

这才是「harness 而非 framework」的精髓。

它给模型一副够用的骨架,也给开发者留下换关节的地方。不是功能少就叫极简,而是核心只保留那些每种工作流都绕不过去的机制。

顺着依赖读三层,Pi 就不神秘了

我拉取的是现在的 earendil-works/pi 主仓库。项目在 2026 年 5 月从 badlogic 组织迁到了 Earendil Works,旧地址目前会跳转,新安装包也换成了 @earendil-works

源码按依赖方向读最省力。

Pi 从模型适配到编码产品的三层结构

第一层是 pi-ai,它像翻译官。

不同模型厂商表面都在做聊天,细节却到处不一样。Anthropic、OpenAI Responses、OpenAI Completions、Gemini、Bedrock 各有消息格式、流式事件、思考内容和工具调用约定。pi-ai 先定义统一的 Model、Message、Tool、Context 和事件流,再把不同 API 翻译到这套公共语言里。

这层不只是把字段改个名字。工具调用 ID 在不同厂商那里有不同限制,思考内容未必允许跨模型回传,流式响应也可能把文字、推理、工具参数和结束原因拆成不同事件。Pi 把这些差异尽量压在边界上,让上层只消费统一事件。会话中途换 provider 时,再根据目标模型能力做消息转换,而不是要求 Agent loop 认识每家 API 的脾气。

DeepSeek 的实现尤其能说明问题。它的 provider 文件很短,主要声明接口地址、环境变量、模型目录,然后复用 OpenAI Completions 适配器。

Pi 并没有为 DeepSeek 重新写一套 Agent。它只是把 DeepSeek 映射进已有协议层。以后模型换成 Claude 或 Gemini,上面的循环不需要跟着重做。

这和我在生产里做的 DeepSeek 直连适配,边界不太一样。

我们的直连层同样复用 OpenAI 兼容客户端,但更关心线上运营。不同任务该选哪个模型和温度,reasoning_content 怎样在多轮工具调用里原样回传,429 后如何退避,直连什么时候恢复健康,prompt cache 是否命中,输出上限有没有被兼容层悄悄改名。

一个追求横向可迁移,一个追求纵向可运营。

如果产品只维护少数经过验证的模型,直连适配更容易做深,也更容易给每个厂商补特殊行为。如果产品允许用户随时切换二三十家 provider,Pi 这种 provider 与 API 分层就更划算。两者没有谁高级,只是边界选得不同。

第二层是 pi-agent-core,它像心跳。

当前仓库已经成长了不少,整个 agent 包并不只有几千行。不过最关键的 agent-loop.ts仍只有约 800 行。把 loop、状态、事件、会话几个核心文件加起来,才是大家说的几千行精髓。

循环本身很朴素。把会话消息转换成当前 provider 能吃的格式,调用模型,接收流式输出。模型发出工具调用,就校验参数、执行工具、把结果追加回消息,再问模型下一步。直到模型不再调用工具,或者外部要求停止。

工具执行还有一个经常被教程略过的细节。模型给出的参数不是可信输入。core 会先按工具 schema 校验,再决定顺序执行还是并行执行,期间允许 before 与 after hook 介入。执行失败也要形成结构化结果回到会话,不能只在终端打一句报错,否则模型下一轮根本不知道刚才发生了什么。

真正漂亮的是事件被当成一等公民。一次运行从 agent_start 开始,中间有 turn_start、message_update、tool_execution_start、tool_execution_end,最后到 agent_end。终端界面、日志、成本统计和外部系统都可以订阅同一条事件流,不必钻进循环内部打补丁。

状态管理也没有和某个 UI 焊死。steering message 可以在运行中改变方向,follow-up queue 可以排下一条用户消息,prepareNextTurn 甚至能在下一轮切模型、思考等级和上下文。

这套事件流也是 Pi 能从 CLI 长成 SDK 的关键。同一套 core 可以被终端界面消费,也可以用 JSON 流接进机器人或后台服务。外层产品只需要订阅消息增量、工具开始、工具更新和工具结束,不必复制一份 Agent 循环。对于要做成本统计、审计记录或中途取消的生产系统,这种分层比系统提示词写得多漂亮更重要。

怎么说呢,Agent loop 的秘密看完有点朴素。它不是一张玄学提示词,而是一台不断做「模型输出、工具执行、结果回填」的小机器。难点全在边界条件,流被打断怎么办,工具失败怎么办,状态怎样恢复,事件怎样保证顺序。

第三层是 pi-coding-agent,它才是完整产品。

这一层把 core 组装成我们在终端里看到的 Pi。CLI 参数、鉴权、模型选择、系统提示词、会话文件、AGENTS.md、skills、扩展、主题、交互界面、JSON 模式都在这里接上。

也就是说,pi-ai 负责让不同发动机接上同一套线束,pi-agent-core 负责点火和循环,pi-coding-agent 才装上车壳、方向盘和仪表盘。

读到这里,Pi 最值得借鉴的架构判断就出来了。

模型适配、Agent 循环和最终产品必须能分开演化。

模型 API 天天变,不该逼着产品界面重写。终端体验要加功能,也不该污染最底层的消息协议。想把 core 嵌进机器人、IDE 或后台服务时,更不需要背着整个 TUI 走。

五分钟跑起来,先别急着放权

Pi 当前的安装很简单。

npm install -g --ignore-scripts @earendil-works/pi-coding-agent

也可以使用官网脚本。

curl -fsSL https://pi.dev/install.sh | sh

准备 DeepSeek API Key 后,把它放进环境变量。

export DEEPSEEK_API_KEY=你的密钥
pi

进入 Pi 后,用 /login 选择 DeepSeek 并完成配置,再用 /model 选模型。最新安装方式和 provider 说明最好以 Pi 官方文档为准,因为包名和组织今年已经迁移过一次,很多旧教程还停在 @mariozechner

第一次别拿公司主仓库试。

新建一个小项目,提交一次干净的 git 基线,目录里不要放 .env、云凭证和生产数据。先给它这种任务。

先阅读项目结构和测试,不修改任何文件。
告诉我这个错误最可能经过哪条调用链,并列出证据。

它定位得靠谱,再给第二步。

只做最小修改,修复这个问题并运行相关测试。
不要顺手重构无关代码。

最后让它自己收口。

检查 git diff,指出潜在回归、未覆盖分支和可以撤销的改动。

这三步不是所谓万能提示词,只是在你还不熟悉 Pi 时,把观察、修改和复盘拆开。等你看懂事件流与工具行为,再把固定流程做成 skill 或扩展。

想继续读源码,也不用一上来通读整个 monorepo。先在 pi-ai 找一个你熟悉的 provider,看模型配置怎样落到共用 API。再给 agent-loop.ts 画一遍从 prompt 到 tool result 的事件顺序。最后回到 coding agent,追踪 CLI 如何创建 session、加载资源并注册四个工具。

这条路线的好处是,每走一层都能做个小实验。给 DeepSeek 加一个自定义 base URL,验证适配边界。写一个只返回当前 git 状态的工具,观察完整事件流。再写一个很小的扩展,把工具结果渲染成自己的终端组件。三次实验做完,Pi 对你来说就不再是一串产品功能,而是一套可以动手改的零件。

这里必须提醒一句,Pi 默认没有内置权限弹窗。提示它「先不要修改」只是行为约束,不是安全隔离。模型真要执行命令,权限就是当前系统用户的权限。

因此,涉及陌生仓库、第三方脚本、部署凭证和生产环境时,真正的护栏应该是容器、虚拟机、低权限账号和可丢弃工作区。审批框点多了会麻木,系统权限不会因为你很认真地写了提示词就自动变小。

如果你想要开箱即用的审批、计划模式、MCP、子 Agent 和成熟团队协作,Claude Code 仍然更省事。Pi 适合另一类人。你在意模型选择、上下文开销和每一次工具调用,也愿意用 TypeScript 把工具改成自己的形状。

Pi 不是便宜版 Claude Code,也不是所有开发者的新标准答案。

它更像一台拆掉外壳的透明机器。你能看到模型在哪里思考,工具在哪里执行,状态在哪里流动,也能决定下一块能力到底该装进核心,还是只在需要时从外面拿进来。

那组 DeepSeek 测试最值得记住的,也不是四倍快。

模型越来越像公共发动机以后,真正拉开产品差距的,会是发动机外面那层看似不起眼的装配方式。

Pi 只是把这层装配,摊开给你看了。