Pi 主链路解析 · 交互式科普
一条消息的旅行
Pi 是一个最小、可扩展的 agent harness。你跟它说一句「帮我修个 bug」,按下回车——接下来它要过四道门、装一身零件、转若干圈、写一本账。这个网站带你跟进去看一遍,全用大白话。
基于 Pi v1.4.0 源码 · 往下滚,跟着这条消息钻进它的身体里
出发前 · 30 秒认识主角
Pi 是个什么?
你用的 AI 编程助手,聪明的是大模型;让聪明变成干活的,是大模型外面那台发动机——行话叫 agent harness。
Pi 就是一台发动机,而且是一台可以拆开重装的发动机。它的信条只有一句:最小内核,其他全部可换。
准备好,跟着你这条「帮我修个 bug」,进去走一趟。
第 1 幕 · 门
一句话进门,先过四道门
你在终端敲下 pi,输入那句话。Pi 做的第一件事不是找模型,而是判断:这句话从哪个门进来?
四个门:交互式终端、一次性打印、JSON 事件流、RPC 长连接。外面还有 SDK,把门直接开在别人的程序里。
门不一样,进门后的手续一模一样。
解析参数 → 跑配置迁移 → 判断模式 → 处理 @文件 和管道输入 → 项目信任 → 登录检查 → 选模型 → 选会话 → 交给 createAgentSession()。
四道门只是四个前台。真正干活的房间,只有一间。
这是第一个设计决定。
所有接口共享同一个 AgentSession 内核。TUI 里能做的事,RPC 和 SDK 也能做;内核修一次,四个门一起受益。
很多工具死在「CLI 一套逻辑,API 又一套」上。Pi 从门口就堵死了这条路。
第 2 幕 · 装配
开机不是初始化,是装配
进门之后,Pi 不会运行一个写死的主程序。它先摆一张工作台,把七个零件一个一个装上去。
每个零件只管一件事,装完再拧成一台 AgentSession。
七个零件,各有各的活。
- ModelRuntime 模型、认证、凭据
- SettingsManager 全局设置 + 项目设置
- ResourceLoader 扩展、skills、模板、主题、AGENTS.md
- SessionManager 那本只加不删的账
- Tools read / bash / edit / write / grep / find / ls
- Agent 心跳,也就是 agent loop
- ExtensionRunner 把所有挂钩接上
最狠的一刀:连「能力」都是插件。
MCP、codemode、tool-search 在 Pi 里都是内置扩展,而且标了 replaceable:第三方可以整块换掉。内核只留骨架,肌肉想换就换。
项目信任是装配前的门禁:不信任这个仓库,就不加载它的设置、扩展、skills、MCP。但它只管「装不装」,不是沙箱——工具还是用 Pi 进程的 OS 权限跑。
第 3 幕 · 心跳
它干活的节奏,是转圈
你修东西:看一眼 → 想一下 → 拧一下 → 再看一眼。Pi 一模一样,只是扳手换成了工具调用。
这个节奏叫 agent loop,是整台发动机的心脏。
一圈是这么走的:
消息进队列 → 从账本投影历史 → 拼系统提示、工具清单、模型设置 → 发给模型 → 流式回话 → 要工具就执行 → 结果写回账本 → 还有活就再转一圈。
一圈叫一个 turn:一次 assistant 回复 + 它发起的工具调用。
你看到的「打字机效果」,是事件流。
agent_start → turn_start → message_start / update / end → tool_execution_* → turn_end → agent_end。
模型每蹦出一个字,就有一条 message_update;界面立刻画出来,不是等半天再一次性甩给你。
运行中想插话?steering 队列在当前 turn 后插进去;follow-up 队列等它手头的活干完再追加。
第 4 幕 · 手
先安检,再并发
模型说「跑一下 npm test,顺便看看这个文件」。一句话可能带好几个工具调用。Pi 不会一个一个傻等。
顺序安检,并发干活,按源顺序入账。
先按顺序做 preflight:校验参数、跑 beforeToolCall、该拦的拦掉。能并发的再并发跑。谁先跑完,谁先发 tool_execution_end。
但写进账本的工具结果,永远按模型当初下发的顺序排。速度是速度,真相是真相。
工具和执行后端,是两件事。
每个内置工具都有一层 *Operations 接口:bash 可以是本地子进程,也可以是云端沙箱;read / write 可以是本地磁盘,也可以是远程环境。
换执行环境,工具说明书一个字不用改。同一文件的并发写还会被 file-mutation-queue 串起来,避免 edit 和 write 互相覆盖。
读文件太大?read 默认截断 2000 行或 50KB,先到先算,超了就给一句「用 bash 读剩下的」。
第 5 幕 · 账本
一本只加不删的树
你说的每句话、它的每句回答、每次工具调用和结果、每次换模型、每次压缩——全按顺序写进一个 JSONL 文件。一行一条,永不涂改。
不是列表,是树。
每条账都有 id 和 parentId。当前那条账叫 leaf;从根到 leaf 的路径就是 active branch——下一次请求的历史,只从这条路径来。
账的类型很多:message、model_change、thinking_level_change、usage、compaction、branch_summary、custom、label、session_info、custom_message、context_edit。
树能干什么?
断点续聊:翻到末尾接着写。分叉:从旧条目再长一条新枝,两个方向各聊各的。回放、导出、审计:整棵树都在,一秒不落。
账的格式写得很死:每条先声明类型;坏行会被跳过,不会硬猜成别的类型。
连「删除」都是追加:想改一条旧消息在上下文里的内容,不撕原账,追加一条 context_edit 指向它。
第 6 幕 · 投影
每一圈,现场重建模型看到的世界
Pi 不在内存里另存一份「聊天记录」。每次要问模型之前,它翻账本,沿 active branch 现场投影出这次请求的上下文。
三步:投影 → 转换 → 发车。
buildSessionProjection():取出 active branch,处理压缩和分支摘要,得到「这次该给模型看什么」。
convertToLlm():把 Pi 的内部消息翻译成模型认识的 user / assistant / toolResult。bash 记录、custom 消息、压缩摘要,都会在这里变成模型能读的文本。
prepareRequest:用投影结果替换 Agent 手里的上下文,再交给 provider。
为什么费这个劲?
因为模型会换、provider 会换、上下文会被压缩、分支会被切换——但账本不变。只要投影逻辑在,换哪颗大脑都能从同一本账接着聊。
系统提示也是每轮现拼的:基础指令 + AGENTS.md 等上下文文件 + skills 描述(正文按需加载)+ 工具定义。
账本记「发生过什么」,请求只带「这次需要什么」。中间那层投影,就是 Pi 做上下文工程的地方。
第 7 幕 · 前情提要
账太厚了?写一页前情提要
模型的上下文窗口有限。账越记越厚,总有一天装不下。Pi 的解法跟电视剧一样:开头来一段「上回说到……」。
什么时候写?
每轮请求前量一量:contextTokens > contextWindow - reserveTokens(默认预留 16,384 tokens)就压缩。provider 报 context overflow 或 stopReason: length,也会压缩再重试,最多一次。
保留最近 keepRecentTokens(默认 20,000 tokens)不摘要,前面的旧账请模型读一遍,写成一页摘要。
提要盖不住原件。
压缩会追加一条 compaction entry,记着摘要和 firstKeptEntryId——从哪条账开始原样保留。之后的投影只读「摘要 + 保留段」。
旧账一页没撕。你想翻,永远翻得到。
切换分支时也一样:branch_summary 把离开那条枝的上下文写成提要,带到新枝上。
剪的时候不能剪断因果:不会把一条工具调用和它的结果从中间撕开——撕开了模型就看不懂。
第 8 幕 · 挂钩
整条链路,处处是挂钩
前面七幕,每一幕的接缝上都有一排挂钩。Pi 的扩展系统就挂在这些钩子上——30 多个事件,覆盖从启动到收工的每一步。
挂钩有多密?
会话:session_start、session_before_compact、session_compact、session_before_tree……
请求:context、context_with_system、before_provider_request、after_provider_response……
循环:before_agent_start、turn_start、turn_end、agent_end……
工具与输入:tool_call、tool_result、input、user_bash……
挂上去能干什么?
注册工具、slash 命令、快捷键、provider、虚拟模型、MCP server、渲染器;改系统提示、改工具清单、改上下文、改压缩策略;甚至弹一个自己的 TUI。
扩展是 TypeScript 模块,用 jiti 在进程内加载,支持 /reload 热重载。写一个文件,存盘,就能试。
连 MCP、codemode、tool-search 都是内置但可替换的扩展。默认值很强,但默认值不是天花板——你可以把整条链路改成自己的样子。
终章 · 把机器拆开
现在,Pi 的主链路你懂了
回顾这趟旅行:一句话从四个门进来,装上七个零件,进心脏转圈;工具先安检再并发,结果按源顺序入账;账本是只加不删的树;每轮请求都从账本现场投影;账太厚就写前情提要,原件永存;整条链路布满挂钩,连心跳的每个节点都能插手。
Pi 的信条不是「功能多」,而是「接缝多」。最小内核,其他全部可换——所以它能变成你的,而不是你变成它的。
滚动叙事、分幕结构与卡片节奏参考了 DeepSeek Harness 原理解析 · 一条消息的旅行 ↗ 本站的文案、示意图和代码是针对 Pi 主链路的独立实现。