小岛AIAI 航行日志

小岛 / 岛刊社 / 文章

Claude Code 内建的四样,这个开源 agent 一样都没放

小岛AI

子 agent、MCP、权限弹窗、规划模式,pi 一样都没放进内核,却能用三个回调把它们拼出来。Pi系列收官篇,拆它的整体骨架和不做清单,附五个问题,帮你判断自己的 agent 该不该这么做。

Agent 工程Claude Code开源

是谁来自山川湖海,却囿于昼夜厨房与爱。

万能青年旅店这句词,我这阵子老想起来,因为拆 pi 拆到最后,发现它的内核就是反过来的。内核只肯囿于三件事,调模型、跑循环、管状态,长在它外面的东西,倒是能去山川湖海。

这是「Pi系列」的最后一篇。前面七篇,我们一块块拆了它的零件,这一篇退后两步,看看整台机器长什么样,再看看它故意没装的那几样东西。

上百个 AI 编程工具,底下那层地基还没个共识

一颗四层的洋葱

先看骨架。pi 的结构像一颗洋葱,从里往外四层。

最里层叫 pi-ai,把二十多家模型的调法统一成一种,第六篇拆过。往外一层是 agent-core,就是第一篇那台只管转的发动机。再往外是 coding-agent,会话、压缩、提示词、扩展,一个编程 agent 产品该有的本事都在这层。最外面是各种壳,终端界面、Slack 机器人、网页组件。

四层洋葱,依赖只往里指

规矩只有一条,每一层只认识里面那层,依赖只往里指。

我去翻了 v0.66.0 各个包的 package.json,这条规矩是真的执行了。pi-ai 不依赖任何自家包,agent-core 只依赖 pi-ai,coding-agent 依赖 agent-core、pi-ai 和终端界面库,Slack 机器人 mom 再往外依赖 coding-agent。没有哪个里层的包去引用外层。

落到代码上,是七个 npm 包

packages/          # 摘自 pi v0.66.0 仓库,行数为 src 目录 .ts 文件合计
├── ai             # pi-ai,统一的模型接入层       26875 行
├── agent          # pi-agent-core,循环引擎        1859 行
├── coding-agent   # pi-coding-agent,产品层      42058 行
├── tui            # pi-tui,终端界面库            10764 行
├── web-ui         # pi-web-ui,网页组件           14623 行
├── mom            # pi-mom,Slack 机器人           4046 行
└── pods           # pi-pods,在 GPU 机器上部署模型   1773 行

七个包,用的人不一样才拆开

几个数字挺有意思。agent-core 整个包就 5 个文件,1859 行,第一篇那 78 行循环就住在这里。产品层 coding-agent 最胖,四万两千多行,一个包占了全仓库源码的四成。终端界面库 pi-tui 干脆一个自家包都不依赖,跟 AI 没关系,别的项目拿去画终端界面也行。七个包还共用一个版本号,全是 0.66.0,一起升,一起发。

拆包的标准很朴素,用的人不一样,才拆开。只想统一调模型的,装 pi-ai 就够。只想要一台发动机自己造产品的,装 agent-core。包就是硬边界,底层想偷偷引用上层,得先把上层写进自己的依赖清单,一眼就能看出来。

如果你想自己读 pi 的源码,我的建议是从内核读起。agent-core 的类型定义,pi-ai 里那个 98 行的 api-registry.ts,再加第一篇的循环。界面组件和各家模型的具体实现放到最后,跟着类型走,用搜索代替翻目录。

故意没装的四样

骨架看完,再看它没装什么。

子 agent,MCP,权限弹窗,先规划再执行。这四样在 Claude Code 里都有,很多编程 agent 也都内建了。pi 一样都没放进内核。

不是没想到。coding-agent 的 README 里专门有一节叫 Philosophy,把这件事写得明明白白

Pi is aggressively extensible so it doesn't have to dictate your workflow.

No MCP. Build CLI tools with READMEs (see Skills), or build an extension that adds MCP support.
No sub-agents. There's many ways to do this. Spawn pi instances via tmux, or build your own with extensions.
No permission popups. Run in a container, or build your own confirmation flow with extensions.
No plan mode. Write plans to files, or build it with extensions, or install a package.

(摘自 pi v0.66.0 packages/coding-agent/README.md,有删减)

我第一次读到这段笑了一下。别家的 README 都在列自己有什么,它在列自己没什么,每一条后面还跟着一句,你想要的话可以这样自己搭。

有点子个性。

这份清单其实还更长。README 接着写了不内建待办清单,理由就一句 They confuse models,会把模型搞糊涂,想要的话用一个 TODO.md 文件就行。也不内建后台运行的 bash,让你直接用 tmux,看得见,随时能接手。每一条都是同一个路数,能用更底层、更通用的东西拼出来的,就不往内核里放。

靠什么拼出来

那这四样真要用的时候怎么办?答案是三个回调,全在 agent-core 的类型定义里。

第一个是工具的 execute,决定 agent 能做什么。第二个是 beforeToolCall,每次工具执行前调用,返回 { block: true } 就能拦下这次调用,决定它能不能做。第三个是 transformContext,每次问模型前改一改它看到的上下文,第一篇讲过,决定它看到什么。

做什么、能不能做、看到什么,三个回调

做什么,能不能做,看到什么,合起来就是对一个 agent 的完全控制。

拿这三个回调去拼那四样,就不那么玄了。

子 agent,就是写一个工具,在它的 execute 里再开一个 agent 跑一圈。循环不靠全局状态,每次都带着自己的上下文,里面那个失败了,对外面来说就是一次普通的工具调用失败。只是想让十个子 agent 并行协作的话,编排得自己在上层搭。

MCP,大多数扩展只是告诉模型某件事怎么做,一篇 Skill 文档就够了,第七篇拆过。真要跑代码,写一个扩展,比另起一个服务进程轻得多。

权限弹窗,就是 beforeToolCall 这一个钩子。在终端里,它可以弹一个确认框。在聊天软件里,它可以发一条消息等你回复。在自动化流水线里,它可以按规则直接放行。同一个钩子,各个壳各用各的,第三篇讲的那几道关里,就有这一道。

规划模式,就是在不同阶段用 transformContext 往上下文里塞不同的指令,规划阶段告诉它只许读不许改,执行阶段再放开。

什么该放进内核

pi 判断一样东西该不该进内核,大致是三条标准。跟产品形态有关的,放外面。有好几种合理做法的,放外面。必须全系统一致的,比如事件的格式,才放进内核。

三根柱子,外置、外置、内建

拿这三条回头看,子 agent 有好几种合理做法,权限弹窗跟产品形态强相关,规划模式有人喜欢有人嫌烦,确实都该放外面。

我觉得这里有个很公平的对比。Claude Code 只为一个产品优化,它可以把更多功能直接内建,用户上手就有,这是它的优势。pi 要给好几种壳当底座,终端、Slack、网页,同一个功能在不同的壳里长得不一样,只好把更多东西推到外面。两边都没错,服务的对象不一样。

代价也要说清楚。上手门槛高,想用好得一层层读懂。几个壳之间有重复劳动,同一个确认流程,终端写一遍,Slack 再写一遍。出了问题,排查路径也更长。

所以它不适合所有人。如果你在考虑自己的 agent 要不要照这个路子搭,可以先问自己五个问题。要跑在几种形态里,接几家模型,团队愿不愿意读源码,安全靠什么兜底,多快要上线。

只想快速上线一个问答机器人,或者只用一家模型、一个界面,直接用厂商的 SDK 更省事。要跑在好几种形态里、接好几家模型、还打算长期维护,这套架构才开始回本。

八篇走下来

回头看这个系列。

第一篇,心脏是一个 78 行的循环,两层 while,里圈干活,外圈守门。第二篇,上下文满了不扔旧账,按六栏写摘要,越压越清楚。第三篇,工具调用前后有几道关,手里有 bash 还单独给六个工具。第四篇,会话是一棵树,随时分叉,随时回到过去。第五篇,系统提示词是一层层拼出来的。第六篇,一个接口插遍二十多家模型,中途换模型也不丢记忆。第七篇,扩展和 Skill 让产品长出新器官。第八篇,就是今天,不做什么。

十三集动画挂成一面墙

这些零件背后,其实是同一个判断。内核越小,能长出来的东西越多。

还记得第一篇那两层 while 吗?78 行,不存会话,不重试,不压缩,所有难题都往外推。当时看像是偷懒,拆完八篇再回头看,那就是整座房子的地基。地基只做一件事,所以上面能盖各种各样的房子。

下次你搭自己的 agent,想往主循环里再塞一个功能的时候,也许可以先停一下,问一句。

这个功能,真的要放进内核吗?

你手上的 agent 项目里,有哪个功能你现在回头看,其实不该放进内核?


这 8 篇的内容,我都做成了一套彩铅动画,13 集,每集两三分钟,一集讲透一个设计决定。今天这篇对应第 1 集和第 13 集,洋葱四层和不做清单,动起来看会更直观。

课程在 B 站课堂,预告和前两集可以免费看。

本文基于 pi v0.66.0 源码,新版本如有变化,以源码为准。讲解脉络参考了开源书《pi 的设计艺术》,想系统深入的推荐直接读原书。