版本与源码地图:定位智能体的分层架构
把 Pi 的仓库结构、包清单和分层架构一次讲清,并给出记录当前源码版本、区分安装版本与仓库版本的具体办法。
版本与源码地图:定位智能体的分层架构
想读 Pi 的源码,第一步就会卡住:你读的是哪一版?
Pi 的 monorepo 迭代很快,main 分支随时在动,今天看到的目录结构和下周的可能就不一样。更麻烦的是,你机器上装的那一版和仓库 main 分支不是同一个东西——pi --version 显示的是前者,你打开源码文件看到的是后者。搞混这两个,后面所有"文档说支持 X"的判断都不可靠。
这篇解决两件事:把 Pi 的仓库结构、包清单和分层架构一次讲清,让你知道每一层的源码在哪、归哪个包发布;然后给出一套记录当前源码版本的具体办法,让你的阅读结论可复核。
读完你会有一份自己的版本地图笔记:一个提交号,一份包清单,一张分层图。
读这篇需要的前置只有一条:会用 git 的基本命令(rev-parse、describe、diff、log)。
一、仓库在哪,scope 是什么
Pi 的官方仓库是 https://github.com/earendil-works/pi。这是一个 monorepo,仓库根目录下的 packages 目录里,每个子目录是一个独立发布的 npm 包。
仓库本身经历过迁移,这一条值得先记清楚:
旧 scope 的迁移在官方 CHANGELOG 里有明确记录:
Self-update support for the npm scope migration:
pi update --selfnow supports the upcoming package rename from@mariozechner/pi-coding-agentto@earendil-works/pi-coding-agent.
这一条对你的直接影响是:看到 @mariozechner/pi-* 的教程或代码,那是很早的版本。 现在应该用的全部是 @earendil-works/*。
包清单
当前 packages 目录下的子目录与它们对应的 npm 包:
这张清单本身就回答了"该装哪个包":
二、分层架构
Pi 的主干是三层,箭头表示依赖方向:
# Pi 的分层架构,三层主干在上,可选服务层在下
┌──────────────────────────────────────────────────┐
│ @earendil-works/pi-coding-agent │
│ CLI + SDK、内置编码工具、会话树、扩展系统、资源发现 │
├──────────────────────────────────────────────────┤
│ @earendil-works/pi-agent-core │
│ 工具执行循环、事件流、队列、上下文转换 │
├──────────────────────────────────────────────────┤
│ @earendil-works/pi-ai │
│ 模型目录、provider 接入、认证解析、流式接口、用量统计 │
└──────────────────────────────────────────────────┘
↑ 叶子包,不依赖上面任何一层
┌──────────────────────────────────────────────────┐
│ pi-codemode · pi-mcp · pi-tui · pi-telemetry │
│ pi-durable · pi-env · chord · pi-evals │
└──────────────────────────────────────────────────┘下层不知道上层。pi-ai 里没有"智能体"这个概念,pi-agent-core 里没有终端也没有会话文件。
每一层的源码在哪
读源码时按这个顺序定位,能少走很多弯路:
可选服务层
服务层的包(pi-durable、pi-env、chord、pi-telemetry、pi-evals)是可选的,不在主干依赖链上。
注意 pi-telemetry 的取向:遥测上下文是显式传递的。这是为了让 pi 包之间的遥测不互相污染。
依赖关系(来自各包 package.json)
这张表解释了一个事实:装 pi-coding-agent 会自动带来 pi-agent-core、pi-ai、pi-codemode、pi-mcp、pi-tui。 你没法只要 Coding Agent 而不要 MCP 客户端。
三、怎么记录你读到的是哪一版
这是本篇最实用的一节。
记录提交号
# 进入你的 checkout 目录,命令要在仓库里跑
cd /path/to/pi
# 输出当前 HEAD 的 40 位 SHA-1,作为阅读结论的复核坐标
git rev-parse HEAD输出是一个 40 位的 SHA-1。把这个 SHA-1 写进你的笔记。 以后任何人问你"你看的是哪一版",你给一个提交号,对方就能精确复核;给一句"最新版"则什么都对不上。
想顺便知道这个提交在哪个 tag 之后:
# 看当前提交距离最近发布 tag 有多远,形如 v1.3.0-148-g8efd314fa
git describe --tags输出形如 v1.3.0-148-g8efd314fa,意思是"在 v1.3.0 之后 148 个提交,当前提交是 8efd314fa"。v1.3.0-148 表示这还没发布——它比最近的 tag 新 148 个提交。
注意这个提交号不是你写的笔记的一部分要公开传播的东西。它是你自己复核用的坐标,公开引用时用 main 分支链接即可。
锁定坐标和"最新"不是一回事
GitHub 上定位到某个提交时,URL 里的分支名会被替换成一个提交号:
https://github.com/earendil-works/pi/blob/<提交号>/<文件路径>把 URL 里的 main 换成那个提交号,链接就锁死了。但文章正文里不要这样写链接——读者拿到的永远是某个具体提交号之外的活代码,写死快照号既不准确也不能点击。写文章、做分享时,指向 main 分支的链接更合适;只有在你自己排查问题时才需要那个精确坐标。
需要对照你的记录和当前 main 的差异时:
# 拉取远端最新状态,拿到能对比的当前 HEAD
git fetch origin
# 对比你记录的提交号和本地 HEAD,--stat 先给变更文件清单
git diff <你的提交号> HEAD --stat--stat 先给你一个变更文件清单。确定要看哪几个目录后,再把目录作为路径过滤参数追加到 -- 后面,范围就限制住了。
一个容易踩的坑
本地工作树的 HEAD 未必等于你记录的提交号。 如果你 git pull 过,或者仓库被别人更新过,本地文件就比记录的版本新了。上面那条 git diff 就是用来发现这件事的。
反过来也成立:本地可能是你所记录版本的祖先,此时 git diff 会显示记录里有本地没有的内容。两种情况都要看一眼 diff 再引用。
四、安装版本和 main 分支是两件事
pi --version 输出的是你装的那一版。它来自包自己的 package.json:
// 编码智能体包的配置模块
export const VERSION: string = pkg.version || "0.0.0";而仓库 main 分支上的 package.json 里的 version 字段,可能是下一个还没发布的版本号。
这四个数不一样,是正常的。 关键是知道自己在用哪一个。
安装渠道的差别也不小
第一条尤其重要。官方安装器装的 Pi,传递依赖是锁死的,所以你 pi --version 看到的版本,它跑出来的行为就是那个版本的行为。用 npm 装的则不然——同一个 pi --version,底层依赖可能不一样。
Pi 官方仓库自己在 README 里的开发流程是:
# 官方仓库的开发流程,在 checkout 根目录按顺序执行
# 装全部依赖:Pi 正常安装不需要跑依赖的生命周期脚本,所以必须加 --ignore-scripts
npm install --ignore-scripts
# 构建所有包:先刷新模型数据再构建,这一步需要联网
npm run build
# 用现有模型数据重建,不访问网络:网络不通时用这个
npm run build:offline
# lint、格式化、类型检查三件事一起做:提交前跑一遍
npm run check
# 跑测试:没有 API key 时,依赖 LLM 的那部分会自动跳过
./test.sh
# 从源码跑 pi:想在任意目录验证改动时用它,而不是 npm 上装的那一版
./pi-test.sh./pi-test.sh 是读源码时最实用的入口——它跑的是你 checkout 里的代码,不是 npm 上装的那一版。
供应链加固
官方 README 有一节讲这件事,值得知道,因为它影响你"能不能复现":
Direct external dependencies are pinned to exact versions. Internal workspace packages remain version-ranged.
package-lock.jsonis the dependency ground truth. Pre-commit blocks accidental lockfile commits unlessPI_ALLOW_LOCKFILE_CHANGE=1is set.
package-lock.json 是依赖的权威事实来源。 如果你要精确复现某个版本的运行环境,看这个文件,不要看 package.json 里的版本范围。
五、一份可复核的阅读基线
把上面的方法落成一份笔记模板:
# Pi 阅读基线
- 仓库:https://github.com/earendil-works/pi
- 我的提交号:<填 git rev-parse HEAD 的输出>
- git describe:<填 git describe --tags 的输出>
- 仓库包版本:<填各包 package.json 里的 version>
- 我本机装的:pi --version = <填这里>
## 已核对的文件
| 文件 | 用途 |
| --- | --- |
| Codemode 官方文档 | 脚本 API 与沙箱限制 |
| MCP 官方文档 | MCP 接入与曝光策略 |
## 已知差异
- 本地 HEAD 与我记录的提交的差异:<git diff 的结果>最后一行是关键。记下差异,你就知道自己哪些结论是基于某个固定版本、哪些是基于本地代码。
六、官方已定义、源码事实与教学设计
官方已定义的行为:仓库地址 github.com/earendil-works/pi;npm scope @earendil-works/*;各包的职责与依赖方向;pi-ai 的 chat 目录只收支持工具调用的模型;pi-durable 是实验性包,API 会随版本变动;官方安装器锁定全部依赖,npm 安装不锁;package-lock.json 是依赖的权威事实来源;git describe --tags 输出中的数字表示领先最近 tag 多少个提交。
源码事实但官方文档没单独成段的:VERSION 常量取自 package.json 的 version 字段,缺失时回退 "0.0.0";pi-durable 依赖 chord、pi-ai、pi-codemode 三项;pi-env 依赖 chord 和 pi-durable;pi-evals 依赖 pi-ai 和 pi-coding-agent;chord 与 pi-telemetry 都对 pi 包零依赖。
教学设计:这张"包 → 职责 → 何时该装"表;这份阅读基线笔记模板;把 git diff 作为"差异检查"的固定动作——官方没有发布过阅读基线的标准格式。
你现在能做什么
- 用
git rev-parse HEAD和git describe --tags记录你当前读到的源码坐标,并写进一份自己的阅读基线笔记。 - 说出 Pi 的模型层、Agent Core、编码智能体和可选服务层各对应哪个目录、由哪个 npm 包发布,共 11 个包一个不漏。
- 解释为什么
pi --version的输出不能代表你正在读的源码,并画出四条"你看到的版本号各自代表什么"的对照。 - 查各包的
package.json,验证"装pi-coding-agent会连带装进哪五个包"这个结论。 - 在你的机器上跑
pi --version,并和仓库main分支上的包版本对比,回答"我装的那版落后几个版本"。 - 用
git diff <提交号> HEAD -- <目录>发现工作树是否已经比你的记录更新。 - 跑通
./pi-test.sh,确认你执行的是 checkout 里的代码而不是 npm 上装的那一版。 - 区分一段结论的来源:官方文档写明的、源码里读到的、还是你自己为了教学构造的。