← 返回写作
10 分钟阅读

Pi Agent 是什么:一个生产级 Agent 运行时的架构拆解

拆解 Pi Agent 的核心架构——从 Agent Loop 双层设计、原子化工具系统、消息与事件分离到 Session Tree 持久化——理解一个"生产级 Agent 运行时"应该怎么设计。

Pi Agent 是什么:一个生产级 Agent 运行时的架构拆解

如果你只用过 Claude Code 或 Cursor,你可能从未想过一个问题:这些 AI 编码助手的”发动机”长什么样?Pi Agent 把这台发动机拆开来给你看——而且它的设计决策,每一个都有清晰的理由。


2025 年到 2026 年,AI 编码助手已经多到让人脸盲。Claude Code、Cursor、Cline、Codex、Devin——名字列出来能占满手机屏幕。但有一个问题很少被认真讨论:如果你想自己造一个,你该从哪开始?

大多数人对这个问题的第一反应是:调 API 嘛,一个 while 循环加上 tool calling,半天就能跑起来。

这个直觉不算错——一个能跑的最简 Agent 确实只需要几十行代码。但”能跑”和”能上线”之间,隔着一整套工程问题:工具调用失败了怎么恢复?对话历史怎么压缩?用户中途要打断怎么办?怎么支持模型热切换?怎么让前端 UI 实时看到 Agent 的内部状态?

这些问题,每个造过 Agent 系统的团队都遇到过。而且大多数团队的回答是:自己写。因为很长一段时间里,市面上没有一个开源的、生产级的、设计清晰的 Agent 运行时参考实现。

Pi Agent 的出现改变了这件事。

Pi Agent 是什么

Pi Agent 是一个 MIT 协议开源(GitHub: badlogic/pi-mono)的 Agent SDK / 运行时框架,由开发者 Mario Zechner(GitHub 用户名 badlogic,也是 libGDX 游戏框架的作者)创建。

它和 Claude Code 的关系,不是”竞品”,而是”工厂和产品”——Claude Code 是给你一个 AI 助手,Pi 是给你造 AI 助手的工厂。Claude Code、Cursor、Cline 等产品的内部架构,几乎都能在 Pi 里找到对应模块。

pi-mono 是一个 monorepo,包含 7 个包:

包名 定位 职责
pi-ai LLM 抽象层 统一 20+ 提供商的 API,处理上下文序列化、跨提供商切换
pi-agent-core Agent 运行时 通用 Agent 循环、状态机、工具系统、事件系统、会话管理
pi-coding-agent 编码 Agent CLI 面向编码场景的交互式 Agent,含会话管理、扩展系统
pi-tui 终端 UI 库 保留模式渲染、差异更新、图片显示、自动补全
pi-web-ui Web UI 组件 聊天面板、沙箱 iframe、Artifact 渲染
pi-mom Slack 机器人 将 Agent 接入 Slack 工作空间
pi-pods GPU Pod 管理 远程 vLLM 部署和管理

这 7 个包的分层非常严格:pi-ai 不知道 pi-agent-core 的存在,pi-agent-core 不依赖 pi-coding-agent。每一层都可以独立测试、独立替换。这种分层纪律,是 Pi 区别于许多”一锅炖”式 Agent 框架的第一个信号。

如果你只想理解最核心的部分,盯住 pi-agent-core 就够了。它是整个 Pi 体系的”发动机”,下面我们就拆开来看。

pi-mono 架构分层

Agent Loop:一个循环,两层设计

Agent 的本质是一个循环:接收输入 → 调用 LLM → 如果 LLM 返回工具调用就执行工具 → 把工具结果喂回 LLM → 重复直到 LLM 认为任务完成。这个循环看似简单,但细节决定了一个 Agent 运行时的质量。

Pi 对这个循环做了两层设计:

底层:agentLoop 纯函数

1
agentLoop(context, config) → Observable<AgentEvent>

这是一个无状态的函数。你给它当前的上下文和配置,它返回一个事件流。它不持有任何可变状态,不管理任何队列,不负责持久化。这意味着你可以把它嵌入任何现有的系统——React 组件、Express 服务器、CLI 工具——而不需要引入 Pi 的任何其他部分。

这个函数内部是一个双层嵌套循环

  • 外层循环(follow-up loop):处理 Agent 停止后追加的消息(比如”继续”、”再检查一下”)。Agent 完成任务 → 用户追加新消息 → Agent 重新启动,这个循环保证了追加消息能被正确处理。
  • 内层循环(turn loop):这是标准的一轮 Agent 执行——调用 LLM → 检查是否有工具调用 → 执行工具 → 把结果反馈给 LLM → 检查是否应该停止。

上层:Agent 有状态类

agentLoop 纯函数之上,Agent 类封装了运行时需要的所有可变状态:

  • 消息历史(transcript):完整的对话记录
  • 工具注册表(tool registry):当前可用的工具
  • 配置快照(model、systemPrompt、thinkingLevel):可以在运行时动态修改,下一次 turn 自动生效
  • 两个消息队列
    • steering 队列:用于中途干预(用户按了”停止”按钮)
    • followUp 队列:用于 Agent 停止后追加新指令
  • AbortController:单次运行锁 + 中断控制

这个双层设计的好处是清晰的:底层无状态 = 可嵌入、可测试;上层有状态 = 可独立运行、可持久化。你用哪个层级取决于你的场景,而不是框架替你做决定。

再往上:AgentHarness 编排层

AgentHarnessAgent 之上增加了会话持久化(JSONL Session)、资源管理(Skills/Prompts)、执行环境抽象(跨 Node/Termux/Browser 的 FS/Shell 接口)和操作锁。这是”生产级”和”demo 级”的分界线——demo 只需要 Agent,生产需要 Harness。

Agent Loop 双层设计

工具系统:四个原子工具和一个五步管道

如果你看过一些 Agent 框架的工具列表,你可能会被上百个工具吓到——文件操作、网络请求、数据库查询、图片处理……每个场景一个工具,工具越多越”强大”。

Pi 走了完全相反的路:只有 4 个核心工具

1
2
3
4
read   — 读取文件内容
write — 创建或覆盖文件
edit — 基于字符串匹配的精准修改
bash — 执行 Shell 命令

这 4 个工具是”原子操作”,Agent 像程序员一样组合使用它们:改代码 = read + edit;创建项目 = bash(mkdir)+ write(多个文件);调试 = bash(运行测试)+ read(看错误日志)。

这不是极简主义审美,而是工程上的刻意选择:工具越少,Agent 越不会在选择工具上犯错;工具越原子,Agent 的行为越可预测

五步管道

工具从定义到执行结果回收,经过五个阶段:

1
2
3
4
5
定义(defineTool + TypeBox schema)
→ 注册(registerTool,加入 Agent 的工具集)
→ 拦截(beforeToolCall hook,可修改参数或阻止执行)
→ 执行(并行或串行,取决于配置)
→ 回收(afterToolCall hook,可修改结果或标记错误)

其中每一步都有明确的扩展点。beforeToolCallafterToolCall 是 hook,你可以在这里注入权限检查、日志记录、结果缓存等逻辑,而不需要修改工具本身的代码。

为什么不用 MCP?

Pi 做了一个很有争议的选择:明确拒绝 MCP(Model Context Protocol)

MCP 的问题是启动成本。一个典型的 MCP Server 会在连接时把所有工具的定义一次性发送给 Agent——对于一个有 50 个工具的 MCP Server,这可能意味着 13000+ token 的系统 prompt 开销,而且大部分工具在本次对话中根本用不到。

Pi 的替代方案是 CLI 工具 + README 渐进式加载:工具的可执行文件放在磁盘上,README 描述工具的用途和参数,Agent 在需要时通过 read 工具按需加载工具描述。结果就是——Pi 的核心系统 Prompt 只有 约 800 token,而同类框架通常在 1000-2000 token。

这是一个典型的”Pi 式权衡”:放弃生态互操作性(MCP 有很多现成的 Server),换取更低的 token 开销和更精确的上下文控制。

工具系统:五步管道与 MCP vs Pi 对比

消息与事件:内外分离,事件驱动

消息系统的内外分离

Pi 的消息系统有一个精巧的设计:内部表示和外部通信使用不同的消息类型

  • 内部AgentMessage 是一个联合类型,包含 7 种消息——除了标准的 user/assistant/tool 消息外,还有系统通知、配置变更、分支标记等”元消息”。而且 TypeScript 的声明合并(declaration merging)允许应用层扩展这个消息类型,添加自定义消息而无需修改框架代码。
  • 外部:通过 convertToLlm() 函数将 AgentMessage[] 转换为 LLM 能理解的 3 种标准 Message(user/assistant/tool)。UI 专用的消息、尚未完成的流式消息、元数据消息——这些都只存在于内部,不会浪费对外通信的 token。

在这两层之间还有一个 transformContext() 函数,负责上下文压缩和裁剪——把旧消息替换为摘要、删除不再需要的工具结果。这三个组件构成了一条清晰的流水线:

1
AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM

事件驱动的可观测性

Agent 内部的所有关键行为都以事件的形式暴露出来:

事件 含义 谁在监听
agent_start 一次 Agent 运行开始 UI 显示”运行中”状态
turn_start 一轮 LLM 调用开始 日志系统记录轮次
message_update 助手消息的流式增量 前端逐字渲染回复
tool_execution_start 工具开始执行 UI 显示”正在执行 xyz…”
tool_execution_end 工具执行完成 日志记录执行结果和耗时
turn_end 一轮 LLM 调用结束 统计本轮 token 消耗
agent_end Agent 运行结束 UI 恢复为”就绪”状态

通过 agent.subscribe() 注册的监听器会按注册顺序依次被调用(await)。这意味着你可以把自定义逻辑——日志、监控、权限拦截、UI 更新——以插件的形式挂载到 Agent 上,而不需要修改核心循环的代码。

事件系统加上前面提到的工具 hook,构成了 Pi 的”神经系统”:你几乎可以在 Agent 执行的任何节点插入自己的逻辑。

消息转换与事件生命周期

会话管理:Session Tree

大多数 Agent 系统把对话历史存成线性日志——一条消息接一条消息,像聊天记录一样。

Pi 的做法不同:它把会话存成一棵树

每个会话是一个 JSONL 文件(追加写,一行一个条目)。每个条目都有 idparentIdtimestamp。通过 parentId 链接,会话可以分叉:

1
2
3
4
5
         [根消息]
/ \
[分支A] [分支B]
/ \ \
[A.1] [A.2] [B.1]

这支持的三种核心操作:

  • **branch()**:从当前节点创建一个新分支,在新分支上继续工作。比如你想试一种重构方案,但不确定是否可行——开一个分支,失败了不影响主线。
  • **checkout()**:切换到另一个分支。回到之前保存的主线继续工作。
  • **rewind(n)**:回退 N 条消息,从那个位置重新开始。Agent 走偏了?回退几步换个思路。

这个设计让 Agent 会话具备了类似 Git 的探索能力——每条路径都完整保留,你可以大胆尝试,出了错随时回到分叉点。公开的 pi-session-traces 数据集 展示了真实的 Pi 会话分支结构,是一个很好的参考。

除了消息之外,Session Tree 还支持多种条目类型:compaction(上下文压缩标记)、model_change(模型切换记录)、branch_summary(分支摘要)。重建上下文时,buildSessionContext() 从当前叶子节点沿 parentId 链一路走回根节点,沿途收集所有有效消息。

Session Tree 会话树

设计哲学与边界

Pi 并不是一个”功能最全”的 Agent 框架。它的很多选择——比如拒绝 MCP、只提供 4 个核心工具、坚持显式上下文控制——在便利性上做出了明确的牺牲。但正是这些牺牲,让它成为理解”Agent SDK 应该怎么设计”的最佳参考之一。

Pi 的设计哲学可以归纳为三条:

  1. 显式优于隐式。上下文完全可控、完全可序列化(完整 JSON 序列化支持存储和恢复)。不替你管理 prompt,不替你压缩历史,一切都暴露给你——代价是你需要自己做更多工作。

  2. 原子优于庞杂。4 个工具打天下,组合而非枚举。Agent 的行为更可预测,system prompt 更短——但某些场景下(比如需要调用第三方 API),你需要自己写工具。

  3. 库优于框架。你可以只用 agentLoop 函数嵌入现有系统,不引入 Agent 类和 Harness。框架不强加架构——代价是开箱即用的体验不如 Claude Code 这样的产品。

Pi 当前的一些边界和限制也值得注意:它没有内置的权限系统(设计文档建议通过容器化和沙箱来处理安全问题);中文文档和社区相对薄弱;版本迭代快速(目前 0.81.x),API 稳定性承诺尚不明确。

什么时候应该关注 Pi Agent

读完这篇文章,你不一定需要立刻把 Pi 用到项目里。但你应该在以下场景想起它:

  • 你要自己造一个 Agent 系统——无论是对内的编码助手、客服 Agent、还是数据分析流水线。Pi 的架构设计是经过实战检验的参考范本,即使你不用它的代码,也应该理解它的设计决策。
  • 你在评估 Agent 框架——Pi 提供了一个很好的”检查清单”:循环怎么跑?工具怎么管?消息怎么传?会话怎么存?用这几个维度去审视任何 Agent 框架,你都能更快看出它的设计质量。
  • 你想深入理解 Agent 的内部机制——Pi 的源码开放、架构文档丰富(DeepWiki 上有详尽的模块拆解),中文社区也有多份学习笔记(learning-pi-agenthow-pi-agent-works)。这是目前学习 Agent 系统设计的最佳路线之一。

如果你只是想要一个开箱即用的 AI 编码助手,Claude Code 或 Cursor 仍然是更好的选择。Pi Agent 不是来取代它们的——它是来帮你看清它们内部长什么样的。


本文所有技术细节基于 pi-mono 公开仓库和文档,访问日期 2026-08-07。架构描述对应 pi-agent-core 0.81.x 版本,后续版本可能有变更。


参考资料

  1. Mario Zechner, pi-mono — AI agent toolkit monorepo
  2. DeepWiki, pi-mono: Monorepo Structure
  3. DeepWiki, pi-agent-core: Agent Framework
  4. DeepWiki, earendil-works/pi: Core Architecture
  5. DeepWiki, earendil-works/pi: Overview
  6. yamsfeer, learning-pi-agent — Pi Agent 学习笔记
  7. cellinlab, how-pi-agent-works — 从零实现 AI Agent
  8. buchidonggua, dg-ai-notes — Pi Agent 源码设计拆解
  9. larsderidder, framework-analysis: Pi Agent tier-2 analysis
  10. grfwings, pi-session-traces — 真实 Pi 会话公开数据集
  11. korya, nu-duo — pi-mono 的 Python 移植版
  12. can1357, oh-my-pi — Pi 配套增强工具

Ask Leslie

从本站公开文章中寻找答案。当前版本在浏览器本地检索,不上传问题,也不会编造不存在的经历。

输入一个问题,我会把你带到 Leslie 写过的相关内容。

微信联系

Leslie Zhang 的微信二维码

扫码添加 Leslie,建议备注你的名字与来意。