动手学 Pi:把 15 个 checkpoint 当成 Git 历史来读的 Agent 课程
《动手学 Pi》:沿 15 个真实 checkpoint 从零构建 Pi-style Agent
秒懂
- 它是什么?
- 这是一个用 TypeScript 写成的中文教材项目,围绕一条离线 Agent 轨迹拆出 15 个可 checkout、可运行、可验证的 checkpoint。它的价值在于把课程代码做成真实 commit 序列,而不是伪代码演示。
- 适合谁用?
- 如果你已经会写 TypeScript,想弄清 coding agent 里的消息协议、工具配对、会话树和上下文压缩各自解决什么问题,这套课程值得按 checkpoint 顺序走一遍,动手前先确认三件事:课程分支的固定上游 commit 是否仍能 npm install 通过,你手上的 Node 版本能否跑通 ESM 测试,以及 checkpoint 05 的 Provider 适配是否要求真实 API key。如果你只想要一个能立刻接入生产的 agent 框架,或者不打算读 Git 历史,这个仓库帮不上忙,它交付的是理解路径,不是运行时依赖。
- 能商用吗?
- 可以。MIT 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
- 还在维护吗?
- 在维护。仓库最近一次提交在 55 天前。
- 用什么语言写的?
- 主要是 TypeScript(依据 GitHub 的语言统计)。
以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。
开源项目深度解析
课程代码是一条 Git 历史,不是一份可复制的伪代码
多数 Agent 教学材料给你一段删减过的示例,你把它抄进项目,然后发现缺了错误处理、缺了状态恢复、缺了上下文预算。动手学 Pi 换了个做法:15 个 checkpoint 对应一条真实的 Git 提交链,从 course(00) 到 course(14),课程源码放在 pi 仓库的 packages/pi-course/ 下。README 明确说课程代码不是伪代码演示,而是一条可以 checkout、运行和验证的 Git 历史。
这个选择决定了使用方式。你不是读一段代码然后照抄,而是切到某个 checkpoint,看这一章相对上一章改了什么。教材正文、真实 commit、聚焦测试、故障实验四部分构成每章的闭环,缺任何一块都读不完整。
另一个约束是上游固定。课程分支从固定上游 commit 8479bd84 出发,第一版课程用 pi-course-v1 和 course-v1/00 到 course-v1/14 这些 tag 钉住。钉住意味着可复现,也意味着上游 Pi 之后的重构不会自动进入课程。想跟进上游变化,得自己对比 diff。
四段式推进:先协议,再循环,后状态,最后验收
15 个 checkpoint 不是并列的知识点,而是沿同一条 Agent 执行链逐层加码。
第一部分处理模型与协议。checkpoint 01 用四个 DemoEvent 串起联合类型、运行时校验、Promise 和 ESM 测试。02 实现 EventStream,要求事件先到和消费者先等这两种时序都能交付过程项与最终结果,这是流式接口里最容易被忽略的一处竞态。03 把文本、工具调用和配对结果保存成统一消息。04 的 ScriptedModel 可以重复播放预设回合并保存请求快照。05 才接触真实 Provider,把课程消息写成 Provider 请求,再把 SSE 响应还原成统一模型事件。
第二部分是工具与循环。06 让一条 echo 调用经过 schema、Registry 和 executor,返回沿用原 call id 的工具结果。07 实现两轮 Agent Loop:模型提出 read,工具结果写回,再生成最终回答。08 把 read、write、edit、bash 四个工具限制在同一个 workspace 内。
第三部分是状态与历史。09 让 Agent 跨运行保存消息,并管理订阅、取消、运行中指令、follow-up 与重入。10 把完成消息追加为带父指针的 JSONL 记录,再从指定叶子恢复对话路径。11 按完整工具交互切分历史,在 token 预算内保留后缀,用结构化摘要补回早期事实。
第四部分是扩展与验证。12 发现项目规则、Skill 与模板并按需送入上下文。13 把 Agent、Session Store、上下文、资源和扩展接成 Runtime。14 用全新 fixture 跑 Runtime,核对活动路径与文件结果并输出分类计数。
这个顺序有它的道理:协议不确定,循环就没法测;历史不落盘,压缩就无从谈起。
跑起来要几条命令,练习模式会剥掉答案
教材本身是一个网站。README 给的本地启动方式很直接:
git clone https://github.com/hahhforest/pi-textbook.git cd pi-textbook npm install npm run dev
读课程代码是另一条路径,需要克隆 pi 的课程分支:
git clone --branch course/build-your-own-pi https://github.com/hahhforest/pi.git cd pi npm install
然后按章定位和练习:
npm run checkpoint -w @pi/course -- 05 npm run practice -w @pi/course -- 05 ../pi-practice-05
这两个脚本的分工在 README 里写得很清楚。checkpoint 定位本章的 parent、target 与聚焦测试,也就是告诉你这一章从哪个 commit 改到哪个 commit、该跑哪些测试。practice 创建一个不含答案和 Git 历史的练习目录。
练习目录里会生成 LEARNING.md。README 描述的工作流是把本章网页、命令输出和这个 LEARNING.md 一起交给陪学 Agent。这个设计把课程本身当成 Agent 的输入,而不是让 Agent 直接给答案。
需要注意的一点:README 没有说明 checkpoint 05 之后的 Provider 适配是否必须配置真实 API key,也没有列出环境变量名。这一块在动手前需要自己看章节正文确认。
ScriptedModel 让前四章不依赖网络,这是刻意的取舍
checkpoint 04 的 ScriptedModel 值得单独说。它可重复播放预设回合,保存请求快照并投影稳定事件流。有了它,01 到 04 章的测试不需要任何模型服务,断言的是事件序列和消息结构,不是模型输出质量。
代价是真实感。ScriptedModel 播放的是写死的回合,它不会告诉你模型在长上下文里开始丢工具调用,也不会暴露 SSE 分片的边界情况。这些要等到 05 接入 Provider、14 用全新 fixture 跑 Runtime 时才会碰到。
课程把不确定性推到后面,是有意的分层。前四章如果掺进真实模型,测试就会变成概率性测试,读者分不清失败是自己写错了还是模型波动。等到协议稳定了再引入真实 Provider,问题定位才干净。
如果你打算用这套课程评估某个具体模型的 Agent 能力,前四章帮不上忙。它们验证的是你的实现,不是模型的水平。
上下文压缩和历史重建是这套课程真正的难点
checkpoint 10 和 11 放在一起看,能看出作者对状态问题的判断。
10 的做法是把完成消息追加为带父指针的 JSONL 记录,再从指定叶子恢复当前对话路径。父指针意味着历史是一棵树而不是一条线,恢复对话时选的是某条路径。这个结构支持从任意分支继续,代价是每次恢复都要沿指针回溯。
11 处理的是另一件事:历史不动,上下文按预算重建。按完整工具交互切分历史,在 token 预算内保留后缀,用结构化摘要补回早期事实。注意这里切分的单位是完整工具交互,不是单条消息。工具调用和它的结果必须一起保留或一起丢弃,否则模型会看到一个没有对应结果的 call,或者一个没有对应调用的结果。
这两章合起来说明一个立场:持久化的历史是完整的事实记录,送进模型的上下文是每次按预算重新构造的投影。两者不是同一份数据。
README 没有给出压缩后的质量评估方法,也没有说明摘要本身消耗多少 token。14 章的评测核对的是活动路径与文件结果,不是压缩保真度。想知道压缩会不会丢关键事实,得自己设计实验。
四个内置工具的边界,以及它为什么不是通用 Agent 框架
checkpoint 08 提供 read、write、edit、bash 四个工具,README 的措辞是让它们在同一 workspace 内完成受控的文件与进程操作。受控这个词是关键,但 README 没有展开沙箱的具体实现,比如 bash 是否限制工作目录、是否有超时、是否拦截危险命令。这些需要读章节正文和测试才能确认。
四个工具覆盖的是编码场景的最小集合。没有网络请求工具,没有数据库连接,没有浏览器控制。想扩展,走 12 章的路径:发现项目规则、Skill 与模板,按需送入上下文,让可信扩展原子注册工具与 hooks。原子注册意味着扩展要么全部生效要么全部不生效,不会留下半注册状态。
这里就是它作为框架的边界。它教你工具契约长什么样、Registry 怎么查、executor 怎么把 call id 传回去,但不提供生产级的权限模型、审计日志、多租户隔离。把它当学习参照可以,当运行时依赖不合适。
和直接读上游 Pi 源码相比,差别在故障实验
一个自然的替代方案是直接读 Pi 上游仓库的源码。两者面对的是同一个系统,路径完全不同。
上游源码是最终形态,所有抽象都已经就位,你看到的是结果。课程分支是增量形态,每个 checkpoint 只比上一个多一层,你能看到某个抽象是为了解决什么问题才被引入的。比如 EventStream 在 02 章出现,是因为要同时处理事件先到和消费者先等两种时序;如果直接读上游,你只会看到一个已经写好的流实现,不知道它在防什么。
课程还多了一样上游没有的东西:故障实验。每章由教材正文、真实 commit、聚焦测试、故障实验四部分构成,故障实验是刻意制造失败来观察行为。上游仓库的测试是为了保证正确性,课程里的故障实验是为了让你看见错误路径。
代价是课程版本落后于上游,固定在上游 commit 8479bd84。上游之后的改动,课程不会自动同步。想学最新实现,还是得回到上游。
许可、维护成本与版本状态
许可分成三层,README 写得很明确:应用与原创代码采用 MIT License,教材正文与原创媒体采用 CC BY 4.0,Pi 上游代码沿用其原许可证和作者归属,细节在 LICENSE 与 LICENSE-CONTENT 两个文件里。这意味着你可以把课程代码用进自己的项目,但转载教材正文和图片要遵守 CC BY 4.0 的署名要求。涉及具体使用场景时请自行核对许可原文,这里不做法律判断。
项目明确声明是社区原创的非官方课程,不隶属于或代表 Pi / Earendil Works。
维护成本方面,仓库最近一次 push 是 2026-07-23,没有检索到 release。课程用 tag 钉住版本,pi-course-v1 和 course-v1/00 到 course-v1/14 固定第一版。这个做法的好处是内容稳定,坏处是上游 Pi 演进后,课程与上游的差距只会变大,不会自动收窄。
跨仓库的历史校验写在 CONTRIBUTING.md 里。如果你打算 fork 后改课程内容,这份文档是起点,因为课程代码和教材分在两个仓库,改一处很容易漏掉另一处。
Node 版本要求、依赖数量、安装耗时,README 都没有提及,需要自己跑一遍才知道。
编辑结论
如果你已经会写 TypeScript,想弄清 coding agent 里的消息协议、工具配对、会话树和上下文压缩各自解决什么问题,这套课程值得按 checkpoint 顺序走一遍,动手前先确认三件事:课程分支的固定上游 commit 是否仍能 npm install 通过,你手上的 Node 版本能否跑通 ESM 测试,以及 checkpoint 05 的 Provider 适配是否要求真实 API key。如果你只想要一个能立刻接入生产的 agent 框架,或者不打算读 Git 历史,这个仓库帮不上忙,它交付的是理解路径,不是运行时依赖。
社区笔记