共计 5691 个字符,预计需要花费 15 分钟才能阅读完成。
最近在翻 Armin Ronacher 的博客时,看到了他写的一篇关于 Pi 的文章。Armin Ronacher 这个名字可能有些人不太熟悉,但提到 Flask 和 Sentry,大部分开发者应该都有印象。他在文章里说,Pi 已经成为他几乎唯一使用的编码代理。这让我挺意外的,毕竟现在市面上的 AI 编码工具已经多到让人选不过来了,Claude Code、Cursor、Windsurf、Codex 各有各的拥趸,为什么一个做过这么多项目的开发者反而选了一个听起来没什么名气的工具?读完之后我发现,Pi 的故事比我预想的有意思得多,它让我重新想了想"编码代理到底该做多少事"这个问题。
从 OpenClaw 的爆红说起
2026 年 1 月底,一个叫 OpenClaw 的开源项目几乎在一夜之间席卷了整个开发者社区。这个项目最初由奥地利开发者 Peter Steinberger 在 2025 年 11 月以 Clawdbot 的名字发布,后来因为 Anthropic 的商标投诉改名为 Moltbot,又在四天后改为 OpenClaw。名字虽然换了三次,但丝毫没有影响它的热度,一周之内 GitHub 星标数突破了 145000,到 2 月初已经超过 180000 颗星。OpenClaw 做的事情说起来也不复杂:它是一个本地运行的开源 AI 代理,可以连接到你常用的聊天平台(Slack、Discord、iMessage、WhatsApp),通过大语言模型来帮你执行各种任务,文件读写、Shell 命令执行、浏览器自动化、第三方服务集成这些都有。完全开源,免费,自带 API Key 就能跑。
但大多数人可能不知道的是,OpenClaw 底下真正干活的是一个叫 Pi 的编码代理。Pi 由 Mario Zechner 开发,做过 libGDX 游戏框架的那位。Pi 和 OpenClaw 的关系有点像引擎和汽车,Pi 有自己的设计思路和架构,OpenClaw 只是用 Pi 的组件搭出来的应用之一。搞清楚这层关系之后再回头看 Armin 的文章,就能理解他推荐的不是一个爆红项目,而是项目底下那个引擎。
Pi 的设计哲学:少即是多
读完 Mario Zechner 在他个人博客上的介绍后,我第一反应是:这个人对"少"有种执念。Pi 的核心只有四个工具,Read、Write、Edit 和 Bash。没有搜索工具,没有浏览器工具,没有文件树浏览器,没有代码分析器。就这四个,没了。系统提示词不到 1000 个 token,对比一下,市面上大多数编码代理的系统提示词动辄 10000 个 token 以上。Mario 的逻辑倒也直接:现在的前沿模型经过大量强化学习训练,已经足够理解编码代理的工作方式了,你不需要用几千个 token 去教模型怎么写代码,它本来就会。给它最基本的工具,让它自己发挥就行。
工具少只是表象,Pi 有意砍掉了很多其他编码代理视为标配的功能。没有 Plan Mode,Mario 觉得那种临时性的规划缺乏持久性,不如直接让模型写一个 PLAN.md 文件,规划可以跨会话保留,也可以和代码一起版本控制。没有内置的 Todo List,他觉得在上下文中注入任务列表往往让模型更困惑,用一个带复选框的 TODO.md 文件反而更清晰。没有子代理(Sub-agents),需要并行处理的时候直接在 Bash 里启动另一个 Pi 实例就好了,每个代理的行为都摆在台面上,不像有些工具的子代理在黑箱里运作。Pi 甚至不支持后台 Bash 进程,理由是 tmux 已经足够好用了,"Bash is all you need"。
仔细想想这个逻辑也说得通:这些"高级功能"本质上都可以通过四个基础工具来实现。搜索代码?Bash 跑 grep 或 ripgrep。浏览网页?Bash 跑 curl 或者调你自己写的脚本。规划任务?Write 写到文件里。与其在代理核心里硬编码一堆功能,不如保持核心干净,让用户按需扩展。这个思路很 Unix,每个工具只做一件事,做好一件事,然后通过组合来完成复杂任务。
为什么不支持 MCP
Pi 完全不支持 MCP(Model Context Protocol)。现在 MCP 生态这么火,这个选择乍一看有点反直觉。但 Mario 给出的理由我觉得挺有说服力的。
问题出在上下文窗口的成本上。拿 Playwright MCP 来说,21 个工具,光是工具描述就占了 13700 个 token。Chrome DevTools MCP 更夸张,18000 个 token。这些描述在每次会话开始时就会被塞进上下文里,占掉 7% 到 9% 的窗口,而且不管你这次用不用得到。你可能整个会话就是在写代码改 Bug,完全不碰浏览器,但那一万多个 token 就在那里干占着位置。
Pi 的替代思路是把功能封装成 CLI 工具,附上一个 README 文件。代理需要某个工具时,先读 README 了解用法,再通过 Bash 调用工具本身。token 成本只在实际使用时才产生,用不到就不加载。而且 CLI 工具天然可以用管道串起来,比 MCP 那种封闭的工具调用灵活不少。Armin 在文章里提到他就是这样干的,自己写了一个浏览器自动化的 CLI 替掉 Playwright MCP,还做了一个拦截层把 pip 调用自动重定向到 uv。
Pi 的技术架构
设计哲学虽然极简,技术实现却不糙。Pi 的代码组织在一个叫 pi-mono 的 monorepo 里,目前在 GitHub 上有超过一万 颗星,96.5% 的代码是 TypeScript,MIT 协议。整个仓库用 npm workspaces 管理,拆成了七个包,各司其职。底层是两个基础包:pi-ai 负责把 Anthropic、OpenAI、Google、xAI、Groq、Mistral 等不同 LLM 提供商的 API 统一成一套流式接口,让上层代码不用关心底下接的是哪家模型;pi-tui 是一个终端 UI 库,用差量渲染的方式刷新屏幕,只重绘变化的行,避免全屏 TUI 常见的闪烁问题,同时保留原生终端的滚动回看功能。中间层是 pi-agent-core,负责代理运行时的状态管理、工具调用和消息队列,所有代理状态都放在一个 AgentState 对象里,方便持久化和检查。最上层才是我们一直在聊的 pi-coding-agent,也就是日常用的编码代理 CLI。除此之外还有几个应用层的包:pi-mom 是一个 Slack 机器人,把编码代理嵌入 Slack 对话中;pi-web-ui 提供浏览器端的聊天组件;pi-pods 用来管理 vLLM 在 GPU Pod 上的部署;pi-proxy 是给浏览器客户端用的 CORS/认证代理。
这种分层结构意味着你不一定要用 pi-coding-agent 这个完整的 CLI,完全可以只拿 pi-ai 和 pi-agent-core 来搭自己的代理应用。OpenClaw 就是这么干的,它用 Pi 的底层组件拼出了自己的产品形态。Mario 自己用 pi-mom 搭了一个 Telegram 机器人,Armin 也基于这些组件搭了自己的小工具。这种"拆开来每一块都能单独用"的设计,让 Pi 不只是一个编码代理,更像是一个造编码代理的工具箱。
回到 pi-coding-agent 本身,有几个设计我觉得值得单独说说。会话树(Session Trees)允许你在任意节点分支出新的上下文,在不同分支之间切换而不丢失之前的工作。比如你在尝试两种重构方案,可以分别在两个分支里试,最后选效果好的那个。热重载(Hot Reloading)是另一个实用的功能,修改扩展代码之后 Pi 可以在不丢失当前 AI 上下文的情况下重新加载,你可以一边和 AI 对话一边迭代扩展。多模型支持前面提到了,pi-ai 这个抽象层干的就是这件事,不同会话可以用不同模型。Armin 提到他移植 MiniJinja 到 Go 语言时,先用 Opus 4.5 做初始开发,后来切到 GPT-5.2 Codex 处理测试修复。
扩展系统也值得说说。扩展就是标准的 TypeScript 模块,通过 jiti 运行时加载,不需要预编译。放在 ~/.pi/agent/extensions/ 或者项目的 .pi/extensions/ 目录下就行。扩展可以订阅生命周期事件、注册自定义工具供 LLM 调用、添加命令和快捷键。比较有意思的一点是,扩展可以通过自定义消息类型在会话中持久化状态,相当于在标准 AI 消息之外多了一层存储,扩展因此可以跨轮次保持自己的上下文。
Armin 在文章里提到了几个他常用的扩展:/answer 把代理的提问重新格式化为结构化的选择对话框,/todos 做本地任务管理,/review 是一套带 diff 查看的分支代码审查流程,/control 让多个代理之间可以通信而不需要编排层,/files 用来跟踪和预览文件变更。这些都不在 Pi 核心里,全靠扩展实现。Armin 说 Pi 的理念是"如果你想让代理做某件它还不会做的事,不要去下载别人的扩展,让代理自己扩展自己"。说白了就是让 AI 帮你写扩展代码,然后热重载进去,下次就有这个功能了。这个思路我觉得挺有意思的。
Pi vs Claude Code:两种不同的哲学
我自己日常用 Claude Code 比较多,读 Mario 对它的批评时确实有些地方觉得说到点子上了。Claude Code 会在后台注入一些用户看不到的内容到上下文中,你想精细地控制上下文就很难。子代理跑起来像个黑箱,不知道它在干什么、用了多少 token。系统提示词和工具集随版本更新频繁变动,有时候刚适应了一种工作流,下次更新就变了。Mario 的原话是 Claude Code "变成了一艘太空船,80% 的功能我根本用不上"。
不过话说回来,两者面向的人群不一样。Claude Code 想做的是开箱即用的通用编码助手,内置功能多是因为它的用户可能不想自己折腾扩展。Pi 更像是给那些清楚自己要什么、愿意花时间定制工作流的人准备的。Pi 跑在"YOLO 模式"下,没有权限检查,没有安全围栏,不限制文件系统访问。Mario 的理由是,模型已经可以写代码和执行代码了,假装存在安全边界是自欺欺人。对老手来说这很爽,对新手来说可能就是灾难。
Terminal-Bench 2.0 的测试结果也挺有意思,Pi 跑出来的分数和 Codex、Cursor、Windsurf 这些主流工具差不多,甚至 Terminal-Bench 自己的 Terminus 2,一个只提供原始终端交互的工具,也取得了相近的成绩。这说明什么呢?在当前模型能力的水平下,各家代理的核心能力差距可能没有宣传的那么大,真正有区别的是工作流合不合手,以及上下文管理做得好不好。
不得不聊的安全问题
聊编码代理就绕不开安全,OpenClaw 的爆红把这个问题放大了不少。2026 年 2 月,安全研究人员披露了 CVE-2026-25253,CVSS 评分 8.8,一键远程代码执行。SecurityScorecard 发现超过 135000 个 OpenClaw 实例直接暴露在公网上,原因很简单,软件默认监听所有网络接口,大多数用户从来没改过这个设置。围绕 OpenClaw 的生态还长出了一些让人意想不到的东西:Moltbook,一个 770000 个自主 AI 代理组成的"社交网络";还有一起 800 万美元的加密货币骗局。
Tildes 上关于 Armin 那篇文章的讨论里,有人直接点出了问题:你的编码代理只有 Read、Write、Edit 和 Bash 四个工具,任何来自互联网的内容都可能变成任意代码执行的入口。这当然不是 Pi 独有的问题,所有能执行代码的 AI 代理都面临同样的风险,只是 Pi 的"YOLO 模式"没有给你任何缓冲。Armin 的建议是在虚拟机里跑编码代理,确保它们碰不到不该碰的东西。OpenClaw 官方也在应对,和 VirusTotal 合作扫描 ClawHub 技能市场的上传内容,良性的自动通过,可疑的加警告,恶意的直接拦截,所有活跃技能每天重新扫描一次。
Mario 说"一旦模型可以写入和执行代码,安全就已经 game over 了",听起来极端,但我越想越觉得他说的有一定道理。那些权限确认对话框,你点了几次"允许"之后基本就是条件反射地一路同意了,并不能真正拦住什么。比起在代理层面做权限检查,在容器或虚拟机中运行代理、限制网络访问、只挂载必要的目录,这些环境层面的隔离可能才是真正管用的办法。
动手体验 Pi
想试试 Pi 的话,上手不复杂。npm 包在 @mariozechner/pi-coding-agent,源码在 badlogic/pi-mono。Pi 有四种运行模式:交互模式日常用,Print/JSON 模式接脚本,RPC 模式做进程通信,SDK 模式嵌入到自己的应用里。
用下来有几个体会:先从简单任务开始,感受一下四个核心工具的边界。Read + Write + Edit + Bash 的组合能力比你以为的要强。然后逐步建自己的扩展库,发现某个操作经常重复就是写扩展的好时机,反正是 TypeScript 模块,放到目录下就能自动加载。会话树也建议多用,探索性的工作分个支出来试试,成本很低,但能帮你保留不同方案的上下文。如果之前习惯用 MCP,可以试试 CLI + README 的方式,把常用功能封装成命令行工具,按需加载省 token 的同时还更灵活。
另外推荐一下 Syntax 播客第 976 期,Armin 和 Mario 一起聊了 Pi 的设计思路,包括为什么他们觉得 Bash 就够了、代理的安全风险怎么看、工作流怎么适配这些话题。听播客比读文章更能感受到两个人对这些问题的态度。
最后
Pi 让我想得最多的一件事是:我们现在给编码代理塞了太多东西了吗?四个工具、不到 1000 token 的系统提示、不支持 MCP、没有子代理,结果 benchmark 上跑出来和那些功能丰富的工具差不多。这说明很多我们以为必不可少的功能,模型自己通过基础工具就能搞定。也许对于足够聪明的模型来说,一个干净的工作台比一堆预置功能更有用。
当然 Pi 不适合所有人。"YOLO 模式"和零安全围栏意味着你得知道自己在干什么,OpenClaw 暴露出的那些安全问题也说明,这种"信任用户"的假设在工具走向大众时是站不住脚的。但如果你平时用编码代理比较多,而且时不时会觉得这些工具做的事情太多、塞的东西太杂,Pi 值得花点时间看看。不一定要切过去用,但它对"编码代理到底需要什么"这个问题给出了一个很不一样的回答。

