<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>AI on 仕麟的博客</title><link>https://edward40.com/zh-cn/categories/ai/</link><description>Recent content in AI on 仕麟的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Tue, 21 Jul 2026 00:00:00 -0700</lastBuildDate><atom:link href="https://edward40.com/zh-cn/categories/ai/index.xml" rel="self" type="application/rss+xml"/><item><title>深入 Pi Agent</title><link>https://edward40.com/zh-cn/p/pi-agent-internal/</link><pubDate>Tue, 21 Jul 2026 00:00:00 -0700</pubDate><guid>https://edward40.com/zh-cn/p/pi-agent-internal/</guid><description>&lt;p&gt;Pi 是一个极简的编程 Agent，但“极简”并不意味着简单。它默认只提供少量工具，背后却包含了一套很值得研究的架构，可以帮助我们理解一个生产级终端 Agent 是如何工作的。&lt;/p&gt;
&lt;p&gt;本文将从内到外拆解 Pi：核心循环、发送给模型的上下文、会话持久化、工具执行、扩展、提示词组装、终端界面、上下文压缩以及 Skills。目的不仅是理解 Pi，也是提炼出能够复用到其他 Agent 中的设计模式。&lt;/p&gt;
&lt;h2 id="pi-的两个主要层次"&gt;Pi 的两个主要层次
&lt;/h2&gt;&lt;p&gt;理解 Pi 最简单的方法，是先把它划分为两个概念层。&lt;/p&gt;
&lt;p&gt;第一层是 &lt;strong&gt;Agent Core&lt;/strong&gt;。它负责面向模型的运行时，包括对话状态、模型流式输出、工具调用、工具结果、取消操作，以及持续执行直到模型完成任务的循环。&lt;/p&gt;
&lt;p&gt;第二层是 &lt;strong&gt;Pi Interactive&lt;/strong&gt;，也就是构建在核心之上的用户侧编程环境。它增加了终端 UI、持久化会话、上下文压缩、项目指令、扩展、Skills、命令以及多种运行模式。&lt;/p&gt;
&lt;p&gt;在这两个概念层之下，Pi 的 TypeScript Monorepo 又通过四个包划分职责：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;包&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pi-ai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;为多个 LLM Provider 提供统一的流式接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pi-agent-core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;有状态的 Agent 与工具调用循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pi-tui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;终端组件与差量渲染&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pi-coding-agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;会话、提示词、Skills、扩展、工具与用户侧运行模式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这种拆分很重要，因为 TUI 并不等于 Agent。相同的核心可以嵌入其他程序、通过 RPC 暴露，或者配合完全不同的界面使用。Provider 特有的行为也被限制在循环下层，因此切换模型不需要重写会话或 UI 逻辑。&lt;/p&gt;
&lt;h2 id="agent-核心循环"&gt;Agent 核心循环
&lt;/h2&gt;&lt;p&gt;Pi 的中心，是大多数工具型 Agent 都会使用的反馈循环：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;准备当前上下文。&lt;/li&gt;
&lt;li&gt;将上下文流式发送给模型。&lt;/li&gt;
&lt;li&gt;收集 Assistant 回复。&lt;/li&gt;
&lt;li&gt;执行模型请求的工具。&lt;/li&gt;
&lt;li&gt;把工具结果追加到对话。&lt;/li&gt;
&lt;li&gt;再次调用模型。&lt;/li&gt;
&lt;li&gt;当模型给出最终答案或运行被终止时停止。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;简化后的伪代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;assistantMessage&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;streamModel&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;toolCalls&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findToolCalls&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;assistantMessage&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;toolCalls&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;results&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;executeTools&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;toolCalls&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;push&lt;/span&gt;(...&lt;span style="color:#a6e22e"&gt;results&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;生产实现可以通过强类型事件被完整观察。事件流描述了 Agent、Turn、Message 和 Tool 的生命周期：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;agent_start
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; turn_start
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message_start -&amp;gt; message_update* -&amp;gt; message_end
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_execution_start -&amp;gt; tool_execution_update* -&amp;gt; tool_execution_end
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; turn_end
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;agent_end
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;循环只负责发出“发生了什么”，而不决定“如何展示”。终端可以渲染事件，JSON 客户端可以序列化事件，会话管理器可以持久化事件，测试也可以验证它们的顺序。&lt;/p&gt;
&lt;p&gt;这个看似简单的循环背后还隐藏着几个重要细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;执行前检查：&lt;/strong&gt; Pi 会解析工具、验证参数，并在执行前运行相关 Hook。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行执行：&lt;/strong&gt; 相互独立的工具调用可以并发运行，例如同时读取或搜索多个文件，从而降低延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有序结果：&lt;/strong&gt; 即使工具完成顺序不同，写入对话前也会恢复为模型最初的调用顺序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一取消：&lt;/strong&gt; 同一个 Abort Signal 协调模型流、工具和更高层的会话操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显式终止：&lt;/strong&gt; 工具可以声明无需再次调用模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pi 还区分 &lt;strong&gt;Steering&lt;/strong&gt; 和 &lt;strong&gt;Follow-up&lt;/strong&gt; 输入。Steering 会在下一个安全边界改变当前运行方向；Follow-up 则等待当前运行稳定结束，再开启新的 Turn。两个独立队列让交互式输入的顺序更加可预测。&lt;/p&gt;
&lt;h2 id="上下文初始化"&gt;上下文初始化
&lt;/h2&gt;&lt;p&gt;模型无法直接看到代码仓库或会话。每次请求前，Pi 都必须构建一份代表当前相关状态的上下文。&lt;/p&gt;
&lt;p&gt;上下文可能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基础 System Prompt；&lt;/li&gt;
&lt;li&gt;全局和项目级指令；&lt;/li&gt;
&lt;li&gt;工具名称、描述与参数 Schema；&lt;/li&gt;
&lt;li&gt;会话历史中的当前活动路径；&lt;/li&gt;
&lt;li&gt;当前用户消息及附件；&lt;/li&gt;
&lt;li&gt;相关 Skill 指令；&lt;/li&gt;
&lt;li&gt;扩展提供或转换后的上下文。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pi 刻意保持基础提示词精简。专门行为通过环境逐层加入，而不是永久写进一个巨大的提示词中。这样更容易追踪规则来自哪里，也能让同一个 Agent 适配不同的仓库。&lt;/p&gt;
&lt;p&gt;在内部，Pi 可以保留 UI 通知、摘要和扩展数据等应用专用消息类型。向 Provider 发出请求前，&lt;code&gt;convertToLlm&lt;/code&gt; 管线才会把这些丰富消息投影成模型接受的 User、Assistant 和 Tool Result Schema。&lt;/p&gt;
&lt;p&gt;这个边界避免了把外部 API 格式变成整个应用的数据结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;应用消息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 上下文转换
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; convertToLlm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; Provider 兼容上下文
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="记忆会话与对话状态"&gt;记忆：会话与对话状态
&lt;/h2&gt;&lt;p&gt;Session 为 Agent 提供连续性。编程任务很少在一次回复后结束：Agent 需要读取文件、形成假设、修改代码、执行命令、检查错误并继续迭代。每一步都依赖之前发生的事情。&lt;/p&gt;
&lt;p&gt;Pi 使用 JSON Lines 存储会话。它便于追加和检查，而父节点引用则把对话变成一棵树，而不是扁平的聊天记录。&lt;/p&gt;
&lt;p&gt;树结构支持非常符合实际工程过程的工作方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回之前的决策点，同时保留后续消息；&lt;/li&gt;
&lt;li&gt;从同一点尝试另一种实现；&lt;/li&gt;
&lt;li&gt;总结不再活动的分支；&lt;/li&gt;
&lt;li&gt;沿父节点链接重建当前对话。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="使用-tree-浏览记忆"&gt;使用 &lt;code&gt;/tree&lt;/code&gt; 浏览记忆
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/tree&lt;/code&gt; 命令会打开当前会话的树形导航器。我们可以选择任何较早的记录并从那里继续。Pi 不会删除它后面的消息；下一条消息只会从选中的记录创建另一个子节点，于是新旧后续内容会成为同一个会话中的兄弟分支。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://edward40.com/p/pi-agent-internal/pi-tree-command.png"
width="1914"
height="398"
srcset="https://edward40.com/p/pi-agent-internal/pi-tree-command_hu_821810e354d14abf.png 480w, https://edward40.com/p/pi-agent-internal/pi-tree-command_hu_ea496196bc0b35ab.png 1024w"
loading="lazy"
alt="输入 Pi 的 tree 命令以浏览和切换会话分支"
class="gallery-image"
data-flex-grow="480"
data-flex-basis="1154px"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;输入 &lt;code&gt;/tree&lt;/code&gt; 会打开会话树导航器；连续按两次 Escape 也可以快速进入同一个界面。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;每条存储记录都有 &lt;code&gt;id&lt;/code&gt; 和 &lt;code&gt;parentId&lt;/code&gt;。当前对话就是从根节点到所选叶节点之间的路径。当我们跳到旧记录并继续时，新记录会指向这个旧节点，而不是之前的叶节点：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;会话根节点
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── 用户：实现功能
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── Assistant：方案 A
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── 用户：继续方案 A &amp;lt;- 原分支
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── 用户：尝试方案 B &amp;lt;- 使用 /tree 后创建的分支
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于两条路径都保存在同一个文件中，&lt;code&gt;/tree&lt;/code&gt; 很适合在不丢失早期工作的情况下探索不同方案。离开一个分支时，Pi 还可以总结该路径，并把摘要附加到目标位置。如果多个方案仍属于同一个会话，应使用 &lt;code&gt;/tree&lt;/code&gt;；如果新路径需要独立的会话文件，则更适合使用 &lt;code&gt;/fork&lt;/code&gt; 或 &lt;code&gt;/clone&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="jsonl-记忆存储在哪里"&gt;JSONL 记忆存储在哪里
&lt;/h3&gt;&lt;p&gt;Pi 会自动把会话保存到：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;~/.pi/agent/sessions/--&amp;lt;working-directory&amp;gt;--/&amp;lt;timestamp&amp;gt;_&amp;lt;uuid&amp;gt;.jsonl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;工作目录会被编码为目录名。例如，在 &lt;code&gt;/Users/edward&lt;/code&gt; 启动的会话会出现在 &lt;code&gt;~/.pi/agent/sessions/--Users-edward--/&lt;/code&gt; 下，如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://edward40.com/p/pi-agent-internal/pi-jsonl-session.png"
width="2316"
height="1774"
srcset="https://edward40.com/p/pi-agent-internal/pi-jsonl-session_hu_29df6f6ac513f52d.png 480w, https://edward40.com/p/pi-agent-internal/pi-jsonl-session_hu_1b0b7951745fccc8.png 1024w"
loading="lazy"
alt="Pi 的 JSONL 会话文件以及它在按工作目录划分的 sessions 目录中的位置"
class="gallery-image"
data-flex-grow="130"
data-flex-basis="313px"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JSONL 文件适合追加：每一行都是一条会话记录，其中可以包含模型切换、Thinking Level 变化、用户消息、Assistant 消息和用量元数据。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;截图也展示了树的编码方式。每一行都有自己的 &lt;code&gt;id&lt;/code&gt;，大部分行还包含当前分支中前一个节点的 &lt;code&gt;parentId&lt;/code&gt;。沿着父节点链接即可重建活动历史；拥有相同祖先的记录则代表不同分支。这就是 Pi 能在单个 JSONL 文件中保留全部历史，同时浏览对话树的原因。&lt;/p&gt;
&lt;p&gt;持久化 Session 和模型 Context 是两个刻意分离的对象。Session 回答“发生过什么”，Context 回答“模型下一步决策需要看到什么”。分离之后，Pi 可以保留完整记录，同时只向模型展示活动分支以及理解它所需的摘要。&lt;/p&gt;
&lt;h2 id="工具agent-如何作用于外部世界"&gt;工具：Agent 如何作用于外部世界
&lt;/h2&gt;&lt;p&gt;模型可以生成文本和结构化请求，但不能直接读取文件、搜索仓库或执行测试。Tool 是模型与环境之间的桥梁。&lt;/p&gt;
&lt;p&gt;Pi 默认使用一组刻意保持精简的编程工具：Read、Write、Edit 和 Bash。其他能力可以通过扩展添加。每个工具都会暴露名称、描述、参数 Schema 和执行函数。Schema 会进入模型上下文，让模型知道有哪些操作以及应如何请求它们。&lt;/p&gt;
&lt;h3 id="源码中的基础工具"&gt;源码中的基础工具
&lt;/h3&gt;&lt;p&gt;Pi 在 &lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/tools/index.ts" target="_blank" rel="noopener"
&gt;&lt;code&gt;tools/index.ts&lt;/code&gt;&lt;/a&gt; 中组装默认工具。去掉外围细节后，工厂函数非常直接：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;createCodingTools&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;)&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentTool&lt;/span&gt;[] {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;createReadTool&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;createBashTool&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;createEditTool&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;createWriteTool&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个工厂都返回同样的 &lt;code&gt;AgentTool&lt;/code&gt; Contract，因此核心循环不需要为文件操作或 Shell 命令编写特殊逻辑。&lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/tools/read.ts" target="_blank" rel="noopener"
&gt;&lt;code&gt;read&lt;/code&gt; 工具&lt;/a&gt; 是一个很好的例子。下面的改写版本去掉图片处理、截断和 TUI 渲染，只保留最核心的结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readSchema&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Type&lt;/span&gt;.Object({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Type.String&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;File path, relative or absolute&amp;#34;&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;offset&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Type.Optional&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Type&lt;/span&gt;.Number({ &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;First line, starting at 1&amp;#34;&lt;/span&gt; })),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;limit&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Type.Optional&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Type&lt;/span&gt;.Number({ &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Maximum lines to return&amp;#34;&lt;/span&gt; })),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;createReadToolDefinition&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;)&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ToolDefinition&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;read&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;read&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Read text files or images, with bounded output.&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;parameters&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;readSchema&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;execute&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;_id&lt;/span&gt;, { &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;offset&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;limit&lt;/span&gt; }, &lt;span style="color:#a6e22e"&gt;signal&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;signal&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;aborted&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;Operation aborted&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;absolutePath&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resolveReadPathAsync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;readFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;absolutePath&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;utf8&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;split&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Math.&lt;span style="color:#a6e22e"&gt;max&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;offset&lt;/span&gt; &lt;span style="color:#f92672"&gt;??&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;selected&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;slice&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;limit&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;limit&lt;/span&gt; : &lt;span style="color:#66d9ef"&gt;undefined&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [{ &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;selected.join&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;) }],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;details&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;totalLines&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;lines.length&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;createReadTool&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;)&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AgentTool&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;wrapToolDefinition&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;createReadToolDefinition&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cwd&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个例子包含两个层次。&lt;code&gt;ToolDefinition&lt;/code&gt; 包含面向模型的 Schema，以及提示词引导和 Renderer 等 Coding Agent 元数据。&lt;code&gt;wrapToolDefinition&lt;/code&gt; 再把它投影成核心运行时所需的精简 &lt;code&gt;AgentTool&lt;/code&gt; 接口：&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;description&lt;/code&gt;、&lt;code&gt;parameters&lt;/code&gt; 和 &lt;code&gt;execute&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;生产实现还包含真实工具所需的细节：可读路径检查、I/O 期间的 Abort Listener、图片检测与缩放、行数和字节截断、可替换的文件系统操作，以及自定义终端渲染。不过这些细节不会改变核心 Contract：输入经过验证，输出则是强类型文本或图片内容。&lt;/p&gt;
&lt;p&gt;工具的生命周期也远不止调用一个 JavaScript 函数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;模型流式生成工具调用及参数。&lt;/li&gt;
&lt;li&gt;Pi 验证并准备调用。&lt;/li&gt;
&lt;li&gt;扩展可以检查、阻止或修改调用。&lt;/li&gt;
&lt;li&gt;工具接收 Abort Signal 执行，并可以流式报告进度。&lt;/li&gt;
&lt;li&gt;Pi 将输出标准化成 Tool Result 消息。&lt;/li&gt;
&lt;li&gt;结果被加入上下文，供模型下一次调用使用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;安全策略也适合放在这个边界中。权限扩展可以在 Shell 命令执行前检查它；路径保护扩展可以拒绝修改 Secret 或生成文件。Agent 循环保持通用，而环境负责决定哪些操作被允许。&lt;/p&gt;
&lt;h2 id="扩展"&gt;扩展
&lt;/h2&gt;&lt;p&gt;扩展可以添加行为，同时避免核心膨胀成一个固定的全能框架。扩展可以注册：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自定义工具和命令；&lt;/li&gt;
&lt;li&gt;快捷键；&lt;/li&gt;
&lt;li&gt;模型 Provider；&lt;/li&gt;
&lt;li&gt;消息 Renderer 和 UI 组件；&lt;/li&gt;
&lt;li&gt;事件处理器和工作流策略；&lt;/li&gt;
&lt;li&gt;自定义压缩或会话行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;扩展还能观察 Input、Context、模型请求、Session 和 Tool 周围的生命周期事件。例如，&lt;code&gt;before_agent_start&lt;/code&gt; Hook 可以在循环开始前注入消息或修改已经组装好的提示词。Tool Hook 则可以实现审批、沙箱、审计或结果转换。&lt;/p&gt;
&lt;p&gt;一个最小的工具扩展大致如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;pi&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;ExtensionAPI&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pi&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;registerTool&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;deploy_preview&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Deploy the current branch to a preview environment&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;parameters&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;schema&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;execute&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;toolCallId&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;signal&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;onUpdate&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 执行工作，并可选择通过 onUpdate 发布进度。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [{ &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Preview ready&amp;#34;&lt;/span&gt; }] }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这就是为什么 Pi 可以被称为一个 &lt;strong&gt;反框架（Anti-Framework）&lt;/strong&gt;。它仍然有结构，但结构止于稳定的基础组件和生命周期边界。Pi 不会强制 Plan Mode、Sub-agent、权限或某一种项目工作流必须以唯一规定的方式实现。&lt;/p&gt;
&lt;p&gt;这种灵活性也把责任转移给了用户。扩展运行在能够执行命令和修改文件的 Agent 内部，因此第三方代码仍然需要经过审查、版本固定和谨慎更新。&lt;/p&gt;
&lt;h2 id="system-prompt-与项目指令"&gt;System Prompt 与项目指令
&lt;/h2&gt;&lt;p&gt;System Prompt 定义 Agent 的基础行为：如何沟通、拥有哪些工具，以及应如何处理编程任务。Pi 再在其上逐层加入更具体的指令。&lt;/p&gt;
&lt;p&gt;可以把它理解为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Agent 基础行为&lt;/li&gt;
&lt;li&gt;全局用户与环境指令&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; 等仓库指令&lt;/li&gt;
&lt;li&gt;Skill 或扩展提供的指令&lt;/li&gt;
&lt;li&gt;当前用户请求&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个仓库可能要求使用 &lt;code&gt;uv&lt;/code&gt; 管理 Python 依赖，另一个使用 &lt;code&gt;pnpm&lt;/code&gt;，第三个还可能规定发布或测试清单。项目指令让相同的 Agent 运行时可以正确适应每个环境，而无需把这些约定全局硬编码。&lt;/p&gt;
&lt;p&gt;组装过程也让定制行为变得可追踪。与其猜测一个黑盒产品为什么这样行动，开发者可以直接检查基础提示词、项目文件、已加载 Skills，以及共同组成最终上下文的扩展 Hook。&lt;/p&gt;
&lt;h2 id="pi-interactive终端-ui-层"&gt;Pi Interactive：终端 UI 层
&lt;/h2&gt;&lt;p&gt;Pi Interactive 是用户直接看到的层。它负责聊天输入、流式输出、工具进度、会话选择、命令、模型切换和中断。&lt;/p&gt;
&lt;p&gt;流式终端界面比看起来更难实现。文本逐 Token 到达，工具同时更新进度，用户也可能仍在输入。每个事件都重绘整个屏幕会很慢，并造成明显闪烁。&lt;/p&gt;
&lt;p&gt;Pi 的 TUI 使用差量渲染。它构建下一帧，与上一帧比较，再选择更新策略：追加新行、只替换发生变化的尾部，或者在必要时执行更大范围的重绘。这样既减少终端写入，也不牺牲正确性。&lt;/p&gt;
&lt;p&gt;界面只是 Harness 周围的一个 Adapter。Pi 可以通过四种模式暴露相同的运行时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;供用户直接操作的交互式终端模式；&lt;/li&gt;
&lt;li&gt;用于一次性命令和 Shell 脚本的 Print 模式；&lt;/li&gt;
&lt;li&gt;用于消费事件流的 JSON 模式；&lt;/li&gt;
&lt;li&gt;用于嵌入其他应用的 RPC 模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四种模式共享同一个 Harness，因此自动化场景不需要维护另一套功能缩水的 Agent 实现。&lt;/p&gt;
&lt;h2 id="上下文压缩"&gt;上下文压缩
&lt;/h2&gt;&lt;p&gt;长时间运行的会话最终会接近模型上下文上限。编程 Agent 尤其容易遇到这个问题，因为文件内容、命令输出、工具 Schema、系统指令、图片和生成 Token 都会占用空间。&lt;/p&gt;
&lt;p&gt;默认情况下，Pi 会为模型的下一次回复保留 &lt;strong&gt;16,384 个 Token&lt;/strong&gt;。&lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/agent/src/harness/compaction/compaction.ts#L251" target="_blank" rel="noopener"
&gt;&lt;code&gt;shouldCompact&lt;/code&gt;&lt;/a&gt; 会在估算上下文超过剩余预算时触发自动压缩：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DEFAULT_COMPACTION_SETTINGS&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;reserveTokens&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;16384&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;shouldCompact&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;contextTokens&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;number&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;contextWindow&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;number&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;contextTokens&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;contextWindow&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DEFAULT_COMPACTION_SETTINGS&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;reserveTokens&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;公式如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;触发压缩：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;contextTokens &amp;gt; contextWindow - reserveTokens
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于上下文窗口为 128,000 Token 的模型，默认阈值就是 &lt;strong&gt;111,616 个上下文 Token&lt;/strong&gt;。当估算值高于这个数字时，Pi 会进行压缩，把最后 16,384 个 Token 留给模型输出，而不是让输入占满整个窗口。&lt;code&gt;reserveTokens&lt;/code&gt; 可以在 &lt;code&gt;~/.pi/agent/settings.json&lt;/code&gt; 或项目的 &lt;code&gt;.pi/settings.json&lt;/code&gt; 中配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;compaction&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;reserveTokens&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;16384&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="pi-如何估算-token"&gt;Pi 如何估算 Token
&lt;/h3&gt;&lt;p&gt;Pi 不会在每次检查前，针对每条消息运行 Provider 专用 Tokenizer。&lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/agent/src/harness/compaction/compaction.ts#L275" target="_blank" rel="noopener"
&gt;&lt;code&gt;estimateTokens&lt;/code&gt;&lt;/a&gt; 使用一种保守的字符估算法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;estimateTokens&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;AgentMessage&lt;/span&gt;)&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;number&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;chars&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;countMessageCharacters&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Math.&lt;span style="color:#a6e22e"&gt;ceil&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;chars&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是说，Pi 按照大约 &lt;strong&gt;每四个字符一个 Token&lt;/strong&gt; 进行估算。具体字符数取决于消息类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User 和 Tool Result 消息计算文本内容；&lt;/li&gt;
&lt;li&gt;Assistant 消息计算文本、Thinking、工具名称和序列化后的工具参数；&lt;/li&gt;
&lt;li&gt;Bash 记录计算命令和输出；&lt;/li&gt;
&lt;li&gt;Branch 和 Compaction 记录计算摘要文本；&lt;/li&gt;
&lt;li&gt;每张图片固定按 4,800 个字符估算，也就是大约 1,200 个 Token。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完整的 &lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/agent/src/harness/compaction/compaction.ts#L220" target="_blank" rel="noopener"
&gt;&lt;code&gt;estimateContextTokens&lt;/code&gt;&lt;/a&gt; 会优先使用最近一条 Assistant 消息中 Provider 报告的真实用量。然后只对该用量快照之后新增的消息使用 &lt;code&gt;字符数 / 4&lt;/code&gt; 估算：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;上下文估算值 = Provider 最近一次报告的用量
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; + ceil(后续消息字符数 / 4)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果当前还没有 Provider 用量记录，则所有消息都使用 &lt;code&gt;ceil(字符数 / 4)&lt;/code&gt; 估算。这种混合方法可以频繁运行，成本很低，同时又能在有真实数据时以 Provider 计数为基准。最终得到的 &lt;code&gt;contextTokens&lt;/code&gt; 会与 &lt;code&gt;contextWindow - 16384&lt;/code&gt; 比较。&lt;/p&gt;
&lt;p&gt;Pi 会在下一次请求可能溢出上下文窗口之前进行压缩。较旧的活动被总结成更小的表示，同时保留继续工作所需的信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户目标；&lt;/li&gt;
&lt;li&gt;决策与约束；&lt;/li&gt;
&lt;li&gt;相关文件和修改；&lt;/li&gt;
&lt;li&gt;已完成的工作；&lt;/li&gt;
&lt;li&gt;尚未解决的错误和阻塞项；&lt;/li&gt;
&lt;li&gt;下一步计划。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;摘要 Contract 定义在 &lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/agent/src/harness/compaction/compaction.ts#L434" target="_blank" rel="noopener"
&gt;&lt;code&gt;compaction.ts&lt;/code&gt;&lt;/a&gt; 中。Pi 使用两个职责不同的 Prompt。下面的精简版本保留了结构，也更容易看清它们的分工：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SUMMARIZATION_SYSTEM_PROMPT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Act only as a conversation summarizer.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Return a structured checkpoint; never continue or answer the conversation.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SUMMARIZATION_PROMPT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Summarize the preceding conversation so another model can resume the work.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;## Goal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;## Constraints &amp;amp; Preferences
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;## Progress
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;### Done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;### In Progress
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;### Blocked
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;## Key Decisions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;## Next Steps
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;## Critical Context
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Keep the checkpoint concise and retain precise technical identifiers.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;SUMMARIZATION_SYSTEM_PROMPT&lt;/code&gt; 限定摘要模型的角色。这很重要，因为输入中包含真实对话、问题和指令，摘要模型只能描述它们，而不能执行它们。&lt;code&gt;SUMMARIZATION_PROMPT&lt;/code&gt; 定义交接 Schema：目标、约束、已完成和进行中的工作、阻塞项、决策、下一步，以及安全恢复工作所需的详细信息。&lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/agent/src/harness/compaction/compaction.ts#L434" target="_blank" rel="noopener"
&gt;完整源码 Prompt&lt;/a&gt; 还明确要求保留精确的文件路径、函数名和错误消息，避免它们在摘要过程中变得模糊。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;generateSummaryWithUsage&lt;/code&gt; 内部，对话和 Prompt 大致按以下方式组装：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertToLlm&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;currentMessages&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;history&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;serializeConversation&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;userPrompt&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&amp;lt;conversation&amp;gt;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;history&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;n&amp;lt;/conversation&amp;gt;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;n&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;SUMMARIZATION_PROMPT&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;summary&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;completeSimpleWithRetries&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;models&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;systemPrompt&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;SUMMARIZATION_SYSTEM_PROMPT&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [{ &lt;span style="color:#a6e22e"&gt;role&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [{ &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;userPrompt&lt;/span&gt; }] }],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果已经存在之前的 Checkpoint，Pi 会改用更新 Prompt，把旧 Checkpoint 放进 &lt;code&gt;&amp;lt;previous-summary&amp;gt;&lt;/code&gt; 标签，再要求模型把新进度合并进去。自定义压缩指令也可以作为额外关注点追加。完整流程如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;会话分支
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 选择旧消息和最近消息尾部
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 转换并序列化旧消息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 生成或更新结构化 Checkpoint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 存储 Checkpoint + 保留的消息尾部
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 使用 Checkpoint + 最近消息构建未来上下文
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;压缩并不需要破坏原始历史。Pi 可以追加一条摘要记录，并且只在构建未来模型上下文时，用它替代一段旧消息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;持久化 Session：完整、只追加的历史
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;模型 Context：活动分支 + 压缩摘要 + 最近细节
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种区别让压缩更安全，也更容易推理。它改变的是模型看到的内容，而不是实际发生过的事情。&lt;/p&gt;
&lt;h2 id="skills"&gt;Skills
&lt;/h2&gt;&lt;p&gt;Skill 是以 &lt;code&gt;SKILL.md&lt;/code&gt; 文件为中心的可复用操作指令。它不仅提供背景知识，还可以规定要查看哪些文件、执行哪些命令、如何验证结果，以及在哪些位置需要获得批准。&lt;/p&gt;
&lt;p&gt;与其让模型在每次会话中重新发现复杂流程，Skill 可以把流程变成可重复执行的能力。例如：准备发布、诊断 CI、生成文档，或遵守团队的 Review 清单。&lt;/p&gt;
&lt;p&gt;Skill 也有助于控制提示词大小。Agent 启动时只需要一个紧凑目录，只有当某个 Skill 与任务相关时才加载完整指令。这样既能保持默认上下文精简，也不会向 Agent 隐藏专门工作流。&lt;/p&gt;
&lt;p&gt;具体实现在 &lt;a class="link" href="https://github.com/earendil-works/pi/blob/main/packages/agent/src/harness/system-prompt.ts" target="_blank" rel="noopener"
&gt;&lt;code&gt;system-prompt.ts&lt;/code&gt;&lt;/a&gt; 中。下面是 &lt;code&gt;formatSkillsForSystemPrompt&lt;/code&gt; 的简化版本，重点展示 Skill 被插入的位置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;formatSkillsForSystemPrompt&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;skills&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Skill&lt;/span&gt;[])&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;visible&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;skills&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;filter&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;skill&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;skill&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;disableModelInvocation&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;visible&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;renderXml&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;available_skills&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;visible&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;skill&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; ({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;skill.name&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;skill.description&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;skill.filePath&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;真实函数会先输出相关指令，然后生成如下形式的 XML 目录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;available_skills&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;skill&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;release&lt;span style="color:#f92672"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Prepare and validate a project release&lt;span style="color:#f92672"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;location&amp;gt;&lt;/span&gt;/path/to/release/SKILL.md&lt;span style="color:#f92672"&gt;&amp;lt;/location&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/skill&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/available_skills&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;需要注意，Pi &lt;strong&gt;不会&lt;/strong&gt; 把每个 &lt;code&gt;SKILL.md&lt;/code&gt; 的正文全部插入 System Prompt。它只插入允许模型调用的 Skill 的名称、描述和文件位置。外围提示词会告诉模型：当前任务与描述匹配时，再读取完整 Skill 文件。因此，Skill 加载分为两个阶段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;启动阶段：发现 Skills -&amp;gt; 插入紧凑的元数据目录
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;运行阶段：匹配请求 -&amp;gt; 读取相关 SKILL.md -&amp;gt; 遵循其中指令
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种渐进加载设计可以节省上下文 Token。大型 Skill 库只会在 System Prompt 中增加一个很小的路由索引；只有 Agent 真正需要某个详细流程时，才会付出对应的上下文成本。标记了 &lt;code&gt;disableModelInvocation&lt;/code&gt; 的 Skill 不会出现在索引中，因此它仍可被显式调用，但不会由模型自动选择。&lt;/p&gt;
&lt;p&gt;扩展和 Skill 的区别也很有用：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;机制&lt;/th&gt;
&lt;th&gt;更适合的场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extension&lt;/td&gt;
&lt;td&gt;新的可执行能力、Hook、集成或 UI 行为&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;td&gt;教 Agent 如何使用已有能力的可重复指令&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Extension 改变运行时“能做什么”，Skill 则告诉模型“何时以及如何去做”。&lt;/p&gt;
&lt;h2 id="为什么这种架构有效"&gt;为什么这种架构有效
&lt;/h2&gt;&lt;p&gt;Pi 的架构之所以有效，是因为每项职责都有清晰可见的边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provider 层标准化模型流；&lt;/li&gt;
&lt;li&gt;Agent 循环协调推理与操作；&lt;/li&gt;
&lt;li&gt;Context Builder 决定模型应看到什么；&lt;/li&gt;
&lt;li&gt;Tool 把模型请求连接到外部环境；&lt;/li&gt;
&lt;li&gt;Session 保存完整的对话树；&lt;/li&gt;
&lt;li&gt;Extension 添加可选运行时行为；&lt;/li&gt;
&lt;li&gt;TUI 展示事件，但不拥有 Agent；&lt;/li&gt;
&lt;li&gt;Compaction 让长会话保持可用；&lt;/li&gt;
&lt;li&gt;Skill 封装可重复工作流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些概念单独看都不是 Pi 独有的。真正的价值来自把它们组合起来，同时不隐藏彼此之间的边界。&lt;/p&gt;
&lt;p&gt;如果要从零构建一个编程 Agent，我会遵循同样的顺序：先实现强类型模型流，再构建最小且正确的工具循环；让每个生命周期转换都可被观察；将会话持久化与模型上下文分离；最后再增加提示词、扩展、压缩、Skills 和用户界面。&lt;/p&gt;
&lt;p&gt;最核心的结论是：一个生产级 Agent 并不只是包裹在 LLM 外面的 System Prompt。它同时是并发运行时、工具调度器、上下文投影系统、持久化层和用户界面。Pi 容易理解，是因为它让每一个组成部分都保持足够小。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=llN-fnfwM9A" target="_blank" rel="noopener"
&gt;视频：PI Agent Internals—Architecture, Loops, and the Anti-Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/badlogic/pi-mono" target="_blank" rel="noopener"
&gt;Pi 源码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/docs" target="_blank" rel="noopener"
&gt;Pi Coding Agent 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/examples/extensions" target="_blank" rel="noopener"
&gt;Pi 扩展示例&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://alejandro-ao.com/pi-architecture/" target="_blank" rel="noopener"
&gt;参考文章：How Pi Works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>