Pi 源码解析(一):整体架构

Pi Agent Core 源码系列(1/8)|下一篇:Agent Loop:生命周期、事件与控制流

Pi 是一个包含多个 package 的 monorepo:既要适配不同 LLM provider,也要处理 agent loop、会话、工具、终端 UI 和扩展。直接顺着 import 追代码,很快就会在模块之间来回跳。

先按职责把它分成几层会比较清楚:pi-ai 统一模型接口,pi-agent-core 运行 agent,pi-coding-agent 加入工具、会话和扩展,pi-tui 负责终端交互。

Package 之间的关系

flowchart TD O[pi-orchestrator<br/>实验性任务编排] --> C[pi-coding-agent<br/>CLI、会话、工具、扩展] C --> A[pi-agent-core<br/>Agent Runtime] C --> T[pi-tui<br/>终端交互与渲染] C --> L[pi-ai<br/>统一 LLM API] A --> L

关系很直接:pi-aipi-tui 提供基础功能,pi-agent-core 实现 agent 的执行流程,pi-coding-agent 把它们组装成 CLI 产品,pi-orchestrator 用来管理多个 coding-agent 实例。

五个主要 package

1. pi-ai:把不同模型变成同一种接口

packages/ai 解决的是多 provider 差异问题。OpenAI、Anthropic、Google、Bedrock 等服务拥有不同的鉴权方式、消息协议、流式事件和模型能力;如果这些差异直接泄漏到 agent 层,上层代码会充满 provider 分支。

Pi 用一组公共类型和 API 隔开这些差异:

  • types.ts 定义消息、模型、工具调用、流式响应等核心类型;
  • index.ts 暴露稳定的公共 API;
  • providers/all.ts 注册内置 provider 和模型目录;
  • api/ 下的实现负责适配 Anthropic Messages、OpenAI Responses、OpenAI Completions 等具体协议。

上层 agent 只依赖这些公共消息和事件。更换模型时,agent loop、工具执行和会话状态不需要跟着重写。

2. pi-agent-core:定义 agent 如何运行

packages/agent 是整个系统的执行内核。它不关心终端长什么样,也不负责具体的编辑器体验;它关心的是一次 agent 运行必须解决的通用问题:如何接收用户消息、调用模型、处理工具调用、更新上下文并继续下一轮推理。

几个主要模块分工如下:

  • agent-loop.ts 描述模型响应与工具结果之间的循环;
  • agent-harness.ts 将执行循环、工具和环境组合为可复用 harness;
  • session.ts 维护消息、上下文与会话状态;
  • compaction.ts 在长会话中压缩历史,控制上下文增长;
  • env/nodejs.ts 把文件和进程操作落到 Node.js 宿主环境。

推理循环和执行环境在这里是分开的。同一套 agent 流程可以接入不同 transport、状态存储或受限环境,不会和某一个 CLI 实现绑定。

3. pi-tui:终端 UI

packages/tui 提供终端组件、编辑器、Markdown 渲染、自动补全、按键识别和图片显示。渲染器使用 differential rendering,只更新发生变化的终端区域,并通过 synchronized output 减少闪烁。

packages/tui/src/index.ts 是公共入口。理解这一层时,不必先读完所有组件;先掌握三个概念即可:

  1. 组件根据终端宽度渲染为文本行;
  2. 输入事件被分发给当前交互组件;
  3. 渲染器比较前后帧,只刷新变化部分。

这解释了为什么 Coding Agent 能在纯终端中同时展示流式输出、编辑器、工具执行状态、选择器和主题,而不必依赖浏览器 UI。

4. pi-coding-agent:组装 CLI 产品

packages/coding-agent 是用户运行 pi 时真正接触到的产品层,也是仓库中最大的 package。它依赖前面的 pi-aipi-agent-corepi-tui,并补上 coding agent 所需的产品能力:

pi-coding-agent 不再实现一套 agent loop。它负责选择模型、加载配置和扩展、建立会话、提供工具,再把底层事件转成终端中的交互反馈。

5. pi-orchestrator:从单个 agent 走向多实例任务

packages/orchestrator 仍处于实验阶段。它依赖 pi-coding-agent,尝试把单个 CLI 进程提升为可管理的任务执行单元。

supervisor.ts 负责实例生命周期,ipc/protocol.tsipc/server.ts 定义本地通信边界。由于这一层的 API 和行为尚未稳定,学习时应把它放在最后,而不是用它反推整个项目的稳定架构。

一次用户请求如何穿过系统

把这些 package 串起来,可以得到一条简化的运行链路:

  1. 用户在终端输入请求,interactive-mode 接收输入并更新界面状态;
  2. pi-coding-agentAgentSession 将请求、配置、模型和工具组合起来;
  3. pi-agent-core 的 harness 启动 agent loop,并把当前上下文交给模型;
  4. pi-ai 选择对应 provider,完成鉴权和协议转换,再把响应转成统一流式事件;
  5. 如果模型产生 tool call,runtime 暂停模型循环,调用 Coding Agent 提供的 read、edit、bash 等工具;
  6. 工具结果写回会话上下文,agent loop 继续请求模型,直到得到最终结果;
  7. 会话事件持续推送给 TUI,最终状态由 session manager 保存。

模型接入、agent loop、工具、执行环境和 UI 分属不同模块。因此每个部分都可以单独测试、替换或扩展。

几个实现选择

统一事件流,而不是只返回一个字符串

Agent 需要处理文本增量、thinking、tool call、结束原因、错误和 token/cost 信息。pi-ai 将这些过程建模为流式事件,使 TUI 可以实时渲染,runtime 也能在工具调用出现时及时接管控制权。

扩展优先,而不是把所有能力写进核心

Coding Agent 的工具和扩展都有明确契约。扩展 loader 负责发现和加载,自定义行为不必直接修改核心循环。这与 Pi “self extensible coding agent”的定位一致。

长会话需要单独处理

Session 和 compaction 并非附属功能。Coding agent 会持续读取文件、执行命令并积累工具结果,如果没有结构化会话和上下文压缩,长任务很快会受到上下文窗口与成本限制。

权限边界由运行环境决定

Pi 本身不会自动提供完整的文件系统、进程、网络或凭据沙箱。工具最终通过执行环境访问宿主资源,因此在不可信任务中,应结合容器或其他 sandbox 机制限制能力范围。理解 security.mdcontainerization.md,与理解 agent loop 同样重要。

推荐的源码阅读顺序

如果目标是学习并继续写系列文章,可以按下面的顺序推进:

  1. 建立边界:根 README.md 与各 package 的 package.json
  2. 理解模型契约packages/ai/src/types.tsindex.ts、一个具体 provider 和对应 API adapter;
  3. 理解执行循环packages/agent/src/agent-loop.tsagent-harness.tssession.ts
  4. 理解产品组装packages/coding-agent/src/main.tsagent-session.ts、工具入口;
  5. 理解扩展与交互:extension loader、interactive-mode.ts、TUI 公共入口;
  6. 最后阅读外围系统:compaction、安全隔离、CI/CD、发布脚本和 orchestrator。

每一阶段只跟踪一个问题。例如阅读 pi-ai 时,只看不同 provider 如何接入公共接口;阅读 agent runtime 时,只看工具结果如何让模型继续下一轮。这样比同时展开所有 import 更容易跟住代码。

总结

Pi 把模型、agent 运行时、终端和产品逻辑拆成了几个 package:

  • pi-ai 统一模型与 provider;
  • pi-agent-core 定义 agent 的运行语义;
  • pi-tui 提供高效终端交互;
  • pi-coding-agent 把这些能力组装成可用产品;
  • pi-orchestrator 探索更高层的多实例任务编排。

后面阅读某个实现时,先确认它属于哪一层,再继续跟踪调用关系。后续文章会依次展开 agent loop、工具执行、Session 和上下文压缩。