第一部分 · 总览

第 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 在继承的骨架上做了三类手术:原封不动的重新接线的整个替换的。本章逐一清点,为后文提供一张「哪些知识可以复用、哪些必须重学」的地图。

原封不动:三层基础设施

图 1:继承与改造的分布。越往下越稳定——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 对照

《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 / lsPython 标准库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 章)

实践应用

  1. fork 的诚实是一种架构文档。承认上游、保留接口形状(回调槽位、事件流、会话树格式),让改造点清晰可数。本书能采取「继承部分快速带过」的策略,正是因为这层血统干净。
  2. 给继承来的框架加槽位,而不是改它的内脏。getContinuationMessages 是新增回调,不是对循环体的侵入。续跑这种 Prime 特有的治理需求,照样以契约的形式挂在边界上。
  3. 输入源变多时,把队列上移。当「用户打字」变成「多方投递」,循环级的拉取队列就不够用了;统一的准入管道 + 策略模板是可扩展的答案。
  4. 改造密度应与抽象层级成反比。越底层(ai 层)越不动,越上层(编排层)改得越狠。依赖方向保证了底层的稳定红利能被所有上层复用。

总结

Prime Agent 从 pi 继承了三层基础设施(统一 LLM 层、两层循环、差分渲染 TUI),用三个钩子(事件订阅、turn 边界、续跑)把循环重新接到自己的编排层上,把输入队列整体上移到会话的动作系统,并把整个内置工具面替换成一个持久解释器。循环没有变——变的是循环之外的几乎一切。

血统清点完毕。从下一章开始,进入 Prime 自己的领地:那个被模型当作工作台的 IPython kernel。