第一部分 · 总览
第 2 章:pi 的血统——被包装的循环
哪些原封不动,哪些被重新接线,哪些被整个替换
一个诚实的 fork
Prime Agent 的 README.md 在致谢一节写着:"Our agent and TUI is built on top of pi." 这不是客套——仓库就是 pi-mono 的派生,四个包的名字、目录结构、甚至源码注释里残留的 "pi must not run two ipython calls in parallel" 字样,都在讲述这段血统。
对读者来说这是好消息:《Pi Agent 源码解析》覆盖过的大部分机制在这里依然成立,本书不必重讲。但 fork 不是快照——Prime 在继承的骨架上做了三类手术:原封不动的、重新接线的、整个替换的。本章逐一清点,为后文提供一张「哪些知识可以复用、哪些必须重学」的地图。
原封不动:三层基础设施
packages/ai:统一 LLM 层
Api(线缆协议)/ Provider(端点)的二层抽象、统一消息格式、EventStream、SSE 解析、惰性加载、双层重试、成本核算——全部沿用。Prime 新增的只是目录层面的东西:PrimeIntellect 自家推理服务的模型接入(core/prime-inference-models.ts 等)。模型如何被调用没有变,变的是谁在调用它、以什么节奏调用它。
packages/agent:两层嵌套循环
pi 的心脏——agent-loop.ts 的两层嵌套循环——还在原位:内层处理工具调用与 steering,外层处理 follow-up;convertToLlm / transformContext / shouldStopAfterTurn / prepareNextTurn 这些回调槽位一个不少(packages/agent/src/types.ts 全部保留)。Prime 甚至给它新增了一个槽位:getContinuationMessages——「这一轮结束后,如果没人说话,还要不要继续」的续跑钩子。它是为 goal 与 autonomous 模式准备的,第 13 章细讲。
《Pi Agent 源码解析》里那条「所有回调 must not throw」的契约纪律在这里原样有效——Prime 往槽位里塞的新实现(goal 记账、阈值压缩判定)同样遵守「失败是返回值」的规矩。循环的简洁性继续靠契约维持,而不是靠 try/catch。
packages/tui:差分渲染器
「组件即 render(width) => string[]」、逐行差分、帧调度、Kitty 键盘协议——终端 UI 库未动。Prime 在它之上添加的是新组件(IPython cell、agents-view、心跳管理器等),不是新渲染机制。第 16 章讲增量。
重新接线:AgentSession 如何驾驶 pi 的循环
Prime 的运行时是三层结构:
循环层 packages/agent Agent + agent-loop(继承) 编排层 core/agent-session.ts AgentSession(11,289 行,Prime 的心脏) 宿主层 core/agent-session-runtime.ts AgentSessionRuntime(会话替换、子代理托管、租约)
AgentSession 的文件头注释自述定位:
/** * AgentSession - Core abstraction for agent lifecycle and session management. * This class is shared between all run modes (interactive, print, rpc). * It encapsulates: * - Agent state access * - Event subscription with automatic session persistence * - Model and thinking level management * - Compaction (manual and auto) * - Bash execution * - Session switching and branching * Modes use this class and add their own I/O layer on top. */
它与下层 Agent 的耦合面精确得值得逐一列出。构造函数做三件事:
// 1. 订阅循环事件——所有持久化从这里出发
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
// 2. turn 边界钩子:循环每轮前后问一次编排层
private _installAgentTurnHook(): void {
this.agent.shouldStopBeforeTurn = () => this._shouldStopBeforeTurn();
this.agent.shouldStopAfterTurn = (context) => this._shouldStopAfterTurn(context);
}
// 3. 续跑钩子:没人说话时,编排层决定要不要继续
this.agent.getContinuationMessages = (context, signal) =>
this._getContinuationMessages(context, signal);
这三个接缝承载了 Prime 全部的「轮间治理」:
_shouldStopBeforeTurn/_shouldStopAfterTurn:goal 结算检查、auto-refine 串行屏障、阈值压缩判定(上下文超标时在这里决定先压缩再继续)、autonomous 预算检查。_getContinuationMessages:goal 续跑消息、autonomous 续跑消息的来源。pi 里这个槽位不存在——因为 pi 没有「会话自己决定继续」的概念。subscribe:pi Agent 的每个事件流入_handleAgentEvent,串行化进_agentEventQueue,在那里完成转录持久化、扩展事件分发、重试判定、goal 用量记账。
《Pi Agent 源码解析》里「循环通过回调与外界交接」的模式,在 Prime 里演化成「循环通过回调被编排层治理」。循环还是那个循环,驾驶它的东西厚了一个数量级。
队列上移:steering 的语义迁移
pi 的 steering/follow-up 队列挂在循环配置上(getSteeringMessages / getFollowUpMessages),由循环在轮间拉取。Prime 做了一个安静但重大的改动:这两个回调再也没有被接线——在整个 coding-agent 源码里找不到任何对它们的赋值。
取而代之的是会话层的动作系统:ActionStore 持有排队的会话动作,一个输入泵(_pumpSessionInputs)按准入策略把它们变成一个个「预备好的 turn」提交给循环。源码注释把这条纪律写得很硬:
/** Session-owned actions. Items are never fed into Agent.steer/followUp. */
「steer」这个词活了下来,但语义从「循环内插队」迁移成了「会话输入的一种口味」——用户消息、agent 消息、心跳、goal 上下文全部走同一条准入管道(_admitSessionInput),只在执行策略模板里区分 queued / directPrompt / injected / customTrigger 四种。第 11 章拆解这套系统;现在只需记住结论:在 Prime 里,一切输入都是会话动作,循环只负责执行。
pi 的队列语义是「用户在 REPL 里打字」:单终端、单会话、输入源单一。Prime 的输入源爆炸了——用户、其他 agent、心跳、cron、goal 续跑、autonomous 续跑、side question——每一种都有不同的准入、去重、恢复需求(崩溃后要能重放排队内容)。把这些塞进循环回调会让 AgentLoopConfig 变成杂物间;上移到会话层,每种输入源只是动作系统的一个策略实例。这是「复杂性放在哪」的又一次决策:循环继续保持干净。
整个替换:工具面
最剧烈的改动在第 4 章详述,这里只清点差异:
| pi 的机制 | Prime 的替代 | 去向 |
|---|---|---|
read / write / edit 工具 | Python 文件操作 + edit 技能 | kernel 内 / 第 10 章 |
bash 工具 | %%bash cell magic(临时子 shell) | 第 4 章 |
grep / find / ls | Python 标准库 | kernel 内 |
| 七个内置工具面 | 一个 ipython 工具 | 第 4 章 |
| TypeScript 扩展系统 | 保留(extensions 仍在),但技能主渠道变为 Python 包 | 第 10 章 |
| skills(markdown 指令) | skills 升级为可执行 Python 包(兼容 markdown 格式) | 第 10 章 |
注意第二行到第五行的方向:能力没有消失,它们下沉进了 kernel——从「宿主为模型代劳」变成「模型自己动手」。这是 RLM 哲学最直白的表达:宿主保留权威(第 5 章),但把操作让渡给解释器。
扩展系统本身(core/extensions/)也还在——extensions 仍能注册自定义工具、命令、钩子,README 明说 "Prime Agent extensions may intentionally add custom tools"。只是内置世界收缩成了一个工具。
术语对照表
读过《Pi Agent 源码解析》的读者,用这张表切换心智模型:
| pi 里的概念 | Prime 里的对应 |
|---|---|
| 工具调用 | IPython cell 里的代码(ipython 工具) |
| 工具结果 | stdout/result/traceback + 私有 MIME 旁路 |
| Task/子代理(无内置) | await rlm(...) → 准入 handle + 消息回传 |
| 会话树(JSONL + 分支) | 沿用;子代理是挂在父 artifact 目录下的 sub-* 子树 |
| 压缩(transformContext) | 沿用框架,但必须额外保全 kernel 状态(第 12 章) |
| AgentSession(pi 的产品编排器) | 同名但体积 ×N:队列、goal、RLM、refine 全部内聚(第 11 章) |
| 扩展系统 | 保留;新增「技能即 Python 包」作为能力沉淀的主渠道 |
| 无 | daemon / worker / AgentConnection(第 14、15 章) |
实践应用
- fork 的诚实是一种架构文档。承认上游、保留接口形状(回调槽位、事件流、会话树格式),让改造点清晰可数。本书能采取「继承部分快速带过」的策略,正是因为这层血统干净。
- 给继承来的框架加槽位,而不是改它的内脏。
getContinuationMessages是新增回调,不是对循环体的侵入。续跑这种 Prime 特有的治理需求,照样以契约的形式挂在边界上。 - 输入源变多时,把队列上移。当「用户打字」变成「多方投递」,循环级的拉取队列就不够用了;统一的准入管道 + 策略模板是可扩展的答案。
- 改造密度应与抽象层级成反比。越底层(ai 层)越不动,越上层(编排层)改得越狠。依赖方向保证了底层的稳定红利能被所有上层复用。
总结
Prime Agent 从 pi 继承了三层基础设施(统一 LLM 层、两层循环、差分渲染 TUI),用三个钩子(事件订阅、turn 边界、续跑)把循环重新接到自己的编排层上,把输入队列整体上移到会话的动作系统,并把整个内置工具面替换成一个持久解释器。循环没有变——变的是循环之外的几乎一切。
血统清点完毕。从下一章开始,进入 Prime 自己的领地:那个被模型当作工作台的 IPython kernel。