Pi Agent 如何用极简 Harness 加载按需 Skills

核验范围: 本文以 earendil-works/pi 2026 年 7 月的公开仓库和文档为依据。Pi 是 TypeScript 开源项目,采用 MIT 许可证。文中把公开文档能确认的字段、默认值、命令与我对设计取舍的理解分开写;源码和文档会迭代,具体命令、默认值与文件路径请以你安装的版本为准。文中出现的默认数值(如 token 阈值)均来自当时的文档,用来帮你建立量级感,不必当成永久承诺。

假设你把一个小任务交给编码 Agent:找出测试为什么失败,修复它,并跑一遍验证。很多 Agent 会在模型外再包上不少东西:权限判断、上下文压缩、错误恢复、工作流约束。Pi 也能做这些事,但它先问了一个更朴素的问题:哪些能力必须塞进核心,哪些应该留给用户和扩展?

读完 Pi,我对它的理解是:它把稳定且通用的部分留在核心,把变化快、和团队习惯强相关的部分放到 Skills、扩展和宿主环境里。这个选择让 Pi 很适合想自己搭建工作流的开发者,也意味着你要更主动地管理权限和运行环境。

这篇文章不打算停在”它很轻”这句评价上。我会把上面那个修复测试的任务一直带在身边,用它穿过 Pi 的每一层:核心循环怎么把任务往前推、上下文塞满了怎么办、专项知识从哪里进来、你自己的规则写在哪、以及当这个 Agent 真的能读写你的磁盘时安全边界在哪。每讲一层,我都尽量给出你能在自己机器上核对的东西。

补充:架构文章与教学实现不能混为一谈。 《动手学 Pi》 是一个社区维护、明确标注为非官方的 Pi-style 教材。它从固定上游版本抽取出 15 个可运行 checkpoint,用简化而可测试的 TypeScript 实现讲解协议、循环、会话和运行时;本文讨论的则是 earendil-works/pi 的真实产品架构。课程很适合验证“一个机制最低要守住哪些边界”,但其中的教学接口、测试夹具与实现顺序不应被当作当前 Pi 的逐行源码说明。

cellinlab/how-pi-agent-works 提供了另一条更适合刚开始动手的路线。它同样不是 Pi 官方源码的镜像,而是将学习过程压成四个独立 TypeScript Demo:最小 Loop、工具调用、会话树、上下文压缩;最后再把这些部件组合为 React + Node 的教学项目。默认使用可预测的 MockModel,因此读者不必先配置模型密钥,就能观察工具调用、事件和会话记录如何流动。

这两类资料和本文并不互相替代。本文用于理解真实 Pi 为什么分层,以及哪些边界属于核心、会话层、Skills、扩展或宿主环境;教学项目用于把其中一条链路跑起来。建议不要反过来根据 Demo 的目录、接口或压缩策略推断 Pi 的当前实现。

可以按下面的顺序阅读和练习:

学习材料 适合验证什么 本文对应位置
pi-textbook 的 checkpoint 03、07 完整 assistant 消息、工具结果配对与 Agent Loop 第二节
pi-textbook 的 checkpoint 10、11 id / parentId 选定会话路径;完整工具交互不能被压缩切开 第三节
pi-textbook 的 checkpoint 12 至 14 Skills 发现与信任边界;Runtime 提交会话;独立 fixture 评测 第四至六节
how-pi-agent-works 的 Demo 01、02 先观察最小循环,再把 tool call 与 tool result 接回模型 第二节
how-pi-agent-works 的 Demo 03、04 idparentIdleafId 表达会话分支;用摘要和近期消息重建上下文 第三节
how-pi-agent-works 的教学项目 区分 Loop、工具注册、事件流与 JSONL Session Store 的职责 第六节

第一次练习时,不要急着接真实模型。先运行 Demo 01,给 TinyModel 增加一个事件,看看 UI 或终端为什么能持续更新;再运行 Demo 02,故意让工具返回错误,确认循环会把错误写成 tool result,而不是直接中断。接着在 Demo 03 从同一个节点拉出两条分支,打印两个 leaf 的上下文路径。最后观察 Demo 04:压缩后不是删除会话,而是让下一次请求看到“旧摘要 + 最近消息”。这四步跑通后,再回来看真实 Pi 的流式占位、steering/follow-up 队列、分支摘要和压缩切点,复杂度会低很多。

先建立几个最基本的概念

如果你刚接触 Agent,这一段先把后面反复出现的词说清楚,读起来会顺很多。

  • 大模型(LLM)本身只会做一件事:给它一段文本,它续写一段文本。它不能自己读文件、跑命令、上网。
  • 工具调用(tool call)就是让模型”开口要”这些能力:模型在回复里输出一个结构化请求,比如”请帮我执行 pytest tests/“,外面的程序真正去执行,再把结果作为新文本喂回模型。模型据此决定下一步。
  • 上下文(context)是你这一轮实际发给模型的全部文本:系统提示、历史对话、工具返回的结果,都算在内。
  • 上下文窗口(context window)是模型单次能接收的文本上限,通常用 token(约等于”词的碎片”,一个汉字大致 1~2 个 token)来计。窗口是有限的,塞满了就得想办法腾地方,这正是后面”压缩”要解决的问题。
  • Harness(直译”挽具”)指的是套在模型外面、驱动它连续干活的那层程序:它负责把上下文组装好发出去、把工具调用接住执行、把结果写回去,如此循环。Pi 就是这样一个 harness。说它”极简”,说的正是这层程序刻意做得薄。

把这几个词记住,下面就好读了。

一、先建立全局认识:Pi 不是一个单体

Pi 不是一个只有 CLI 的单体程序。官方仓库把它拆成几块,各自职责清晰:

组成 负责什么
pi-ai 统一 OpenAI、Anthropic、Google 等不同模型提供商的调用接口,让上层不必关心具体是哪家模型
pi-agent-core Agent 运行时:处理工具调用与 Agent 状态管理
pi-coding-agent 面向终端的编码 Agent:会话、上下文、Skills、扩展等能力都在这一层
pi-tui 终端界面渲染,采用差量刷新

(此外还有一个独立的 pi-chat,负责 Slack 一类的工作流自动化,不在本文范围内。)

理解这张表,就能纠正一个常见误会。”Pi 很小”不是说它没有上下文管理或会话能力,而是说它没有把每一种团队工作流都硬编码进最底层的 Agent Loop。能力分布在不同的层,而不是全挤在核心。把层次分开后,下面几个问题会更容易一个一个谈清楚:核心循环怎么转,额外能力从哪来,会话怎么回溯,安全边界又在哪。

二、核心循环只负责把任务往前推进

回到修复测试的任务。你输入”找出测试为什么失败并修复”,从这一刻起,Pi 的核心循环开始转动。它的基本形状是这样:

用我们的任务走一遍,就是:

  1. Pi 把系统提示 + 你的任务组装成上下文,发给模型,流式(一边生成一边显示,不用等整段写完)接收回复。
  2. 模型没法凭空知道测试为什么挂,于是发起一个工具调用:bash("pytest -x")
  3. 循环判断”模型请求了工具”,于是先校验参数再执行,把 pytest 的失败输出接住。
  4. 这段输出被写回上下文,再次发给模型。模型现在”看见”了报错栈,可能接着调用 read 打开出错的文件。
  5. 如此往复,直到模型认为改完了、不再请求工具,本轮结束。

这个循环的职责很克制:模型给出回复,若有工具调用就执行,再把观察结果交还给模型。它不替你规定”改代码前必须先读三个文件”,也不预设某种产品流程。它只保证”转得动”。

薄,不等于执行时没有保护。有几件事属于”让循环可靠运行”的基础能力,核心必须管:

  • 参数不完整时不能直接甩给 shell。比如模型请求了一个缺字段的工具调用,执行层要把”参数无效”这个失败结果交回模型,让它自己纠正,而不是把残缺命令交给系统去跑。
  • 用户中途插话要在合适的回合进上下文。你在 Agent 跑到一半时补一句”顺便把这个函数也改了”,这条引导消息要在下一个合适的回合并入,而不是被丢掉或打断当前工具执行。

它们是通用的、和任何团队规则无关的能力,所以留在核心是合理的。

对初学者,可以把 Pi 的核心理解成一台好替换的发动机:它负责转动,但不决定你要开去哪里。方向盘、导航、限速,都在上层。

三、薄核心不等于没有上下文管理

我最初看到 Pi 的极简定位时,很容易把它误读成”没有压缩、没有会话治理,塞满了就崩”。看官方文档后,这个理解并不准确。压缩机制是有的,而且做得相当细,只是它放在编码 Agent 层,不在最底层的循环里。

3.1 什么时候触发压缩

还是修复测试那个任务。假设它比想象中麻烦,模型读了十几个文件、跑了好几轮测试,上下文越堆越多,逼近窗口上限。这时压缩登场。

文档给出的自动触发条件很直白:

1
contextTokens > contextWindow - reserveTokens

也就是”当前已用 token 超过(窗口上限 − 预留量)”就触发。reserveTokens 默认约 16384,可在 ~/.pi/agent/settings.json 或项目里的 .pi/settings.json 配置。你也可以手动执行 /compact [指令] 立刻压一次,甚至在指令里告诉它”重点保留和测试相关的内容”。

3.2 压缩时保留什么、丢什么

关键在于它不是把整段历史无差别地”抹成一句话”。流程大致是:

  • 从最新的消息往回走,一路累加 token,直到攒够 keepRecentTokens(默认约 20000)。这条线之后的近期消息原样保留,因为你最近在干的事往往最重要。
  • 这条线之前的旧消息,才是被摘要的对象:由模型生成一段结构化摘要,替换掉原文。
  • 工具调用和它的结果始终绑在一起:切割点不会落在”工具调用还在、结果被砍掉”的中间状态,否则模型会看到一个没有下文的调用。
  • 万一单独一个回合就超预算(比如一次超长的工具输出),Pi 会分别为”更早的历史”和”这个未完成回合的前缀”各生成一份摘要再合并。

摘要不是随便写的散文,而是有固定骨架,大致包含:目标(Goal)、约束与偏好(Constraints & Preferences)、进展(Done / In Progress / Blocked)、关键决策(Key Decisions)、下一步(Next Steps)、关键上下文(Critical Context),并用 <read-files><modified-files> 两个区块单独列出读过和改过的文件路径。

压缩后会往会话里追加一条 compaction 记录,里面存着摘要文本、”从哪条消息开始保留”的标记(firstKeptEntryId)、压缩前的 token 数,以及累积的文件操作清单。之后会话就用”摘要 + 保留的近期消息”重新装载继续跑。

对我们的任务来说,效果就是:模型忘掉了它十轮前逐字读过的某个无关文件,但记得”目标是修复这个失败测试、已经定位到 X、改了 Y、还差验证”。该丢的细节丢了,该留的主线留着。这就是”上下文工程”落到实处的样子。

3.3 会话不是一条直线,而是一棵树

普通对话是一条只能往前接的消息链。Pi 的会话是一棵带父子关系的树,文档里说得很清楚:”每条记录都有 idparentId,当前位置是活跃的叶子(active leaf)”。这意味着你可以在任意节点分叉,尝试另一条路,需要时再回到岔路口。

用得上的命令:

命令 作用
/tree 在当前会话里可视化整棵树,方向键浏览、折叠/跳转分支
/fork <路径或 id> 从某个更早的节点拉出一个新会话文件
/clone 把当前活跃分支复制成一个新会话
/resume(或 pi -r 打开会话选择器,搜索、重命名、删除历史会话
/session /name /export /share 查看会话信息、命名、导出 HTML、上传为私密 gist

会话默认按工作目录存放在 ~/.pi/agent/sessions/,每个会话是一个 JSONL 文件,里面记录消息、模型切换、思考等级变化、标签、压缩记录、分支摘要、扩展写入的数据等。

分支摘要是这套设计里我最喜欢的一点。回到任务:你走了”方案 A,直接改断言”,改了半天发现是错的。你用 /tree 跳回岔路口去试”方案 B,修数据初始化”。这时 Pi 不会让方案 A 的经验白费:它会找到新旧位置的最近公共祖先,把被放弃的那条分支收敛成一段摘要,作为一条 branch_summary 记录注入过来。于是在方案 B 里,模型仍然”知道”方案 A 试过什么、为什么不行,不会重蹈覆辙。

这就是为什么会话树特别适合编码:探索性的工作天然是带回溯的,”改坏了退回去换个思路”是常态,而 Pi 把这件事做成了一等公民,而不是让你复制粘贴重开一个对话。

读源码或做二次开发时,请把”核心层很薄”和”整个产品只有一个 while 循环”分开,后者是误解。

四、Skills:让专项能力按需进入上下文

一个编码 Agent 很容易遇到上下文膨胀:项目规范、数据库约定、部署说明、测试流程、各种工具定义……如果全塞进开场提示词,模型还没开始干活就先背了一大包,既占窗口又分散注意力。

Pi 的 Skills 走的是另一条路:平时只放”目录”,用到了才读”正文”。这在提示工程里叫渐进式披露(progressive disclosure)。

4.1 一个 Skill 到底长什么样

文档说得很干脆:”一个 Skill 就是一个包含 SKILL.md 文件的目录,其余都是自由的。”典型布局:

1
2
3
4
5
my-skill/
├── SKILL.md # 必需:frontmatter + 指令正文
├── scripts/ # 可选:辅助脚本
├── references/ # 可选:详细文档
└── assets/ # 可选:模板等资源

SKILL.md 开头是一段 YAML frontmatter,只有两个字段是必填的:

  • name:最长 64 字符,只能用小写字母、数字和连字符。pdf-processingdata-analysis 合法;PDF-Processingpdf--processing 不合法。
  • description:最长 1024 字符,说明这个 Skill 做什么、什么时候用它。

可选字段包括 licensecompatibilitymetadataallowed-toolsdisable-model-invocation 等。一个最小例子:

1
2
3
4
5
6
7
8
9
---
name: run-migration-tests
description: 当改动涉及数据库 schema 或迁移文件时使用;说明本项目跑迁移测试的正确顺序。不涉及数据库时不要加载。
---

本项目的迁移测试必须按以下顺序执行:
1. `make db-reset`
2. `pytest tests/migrations -x`
3. 若失败,检查 `alembic/versions/` 下最新的 revision ...

4.2 它是怎么被发现和加载的

Pi 在启动时扫描几个约定位置,只抽取每个 Skill 的 namedescription

  • 全局:~/.pi/agent/skills/
  • 项目:.pi/skills/
  • 以及命令行参数指定的位置

(在这两个 skills 目录里,直接放在根下的 .md 文件也会被发现。)

启动后,系统提示里只带着这些简短的名称和描述,相当于一份目录。真正干活时:

模型判断当前任务和某条描述对得上,就用 read 把那份完整的 SKILL.md 读进来;对不上,正文就一直躺在磁盘上不占窗口。你也可以用 /skill:name 强制加载某个 Skill,或在提示里直接点名。

回到修复测试的任务:如果失败的是一个迁移测试,模型看到 run-migration-tests 这条描述就会把它读进来,照着”正确的迁移测试顺序”走;如果只是个普通单元测试,这个 Skill 的正文永远不会进上下文。能力的数量和常驻上下文的成本,就这样被解耦了。

4.3 写好一个 Skill 的关键在 description

这套机制的成败几乎全压在 description 上。写得太模糊,模型不知道何时该加载它;写得太长太杂,那份”目录”本身又开始膨胀。我的经验是:一个好 Skill 的描述要回答两个问题,它解决什么任务,以及什么时候不要用它。上面例子里那句”不涉及数据库时不要加载”就是在做后半件事,它和正面描述同样重要。

4.4 Skill 和扩展的分工

一句话区分:Skill 是”按任务临时读进来的知识/流程”,扩展是”常驻的新功能”。Skill 不改变 Pi 能做什么,只在合适的时机给模型一段专门指令;扩展则真的往 Pi 里加工具、命令、事件处理。下一节就讲扩展。

五、扩展:把”你自己的规则”写成代码

到这里,前面那句”可扩展”才真正落地。Pi 允许用 TypeScript 扩展、.pi/ 目录、提示词模板、主题和包来改变行为。我重点讲 TypeScript 扩展,因为它最能说明”规则由谁决定”。

5.1 扩展放在哪、长什么样

扩展从几个受信任的位置自动发现:

  • 全局:~/.pi/agent/extensions/*.ts~/.pi/agent/extensions/*/index.ts
  • 项目:.pi/extensions/*.ts.pi/extensions/*/index.ts

一个扩展导出一个默认工厂函数,拿到 ExtensionAPI

1
2
3
4
5
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
// 在这里订阅事件、注册工具和命令
}

工厂可以是同步或异步的;异步的话,Pi 会等它初始化完再触发 session_start。开发时用 pi -e ./my-extension.ts 挂载测试,放进自动发现目录后还支持 /reload 热重载。

5.2 用扩展实现”改危险代码前必须确认”

第四节我留了个坑没填:团队希望 Agent 执行危险操作前先人工确认。现在给出可运行的实现,这几乎是官方文档里的示例:

1
2
3
4
5
6
7
8
9
10
11
12
export default function (pi: ExtensionAPI) {
// 每次模型请求工具调用,都先过一遍这个钩子
pi.on("tool_call", async (event, ctx) => {
if (
event.toolName === "bash" &&
event.input.command?.includes("rm -rf")
) {
const ok = await ctx.ui.confirm("危险操作", "确定要执行 rm -rf 吗?");
if (!ok) return { block: true, reason: "已被用户拦截" };
}
});
}

tool_call 事件能拿到 event.toolNameevent.input,并且允许返回 { block: true, reason: "..." } 直接拦下这次调用。把 rm -rf 换成”命中支付相关文件路径就弹确认”,你要的那条团队规则就成立了。关键在于:这条规则是你写的、由你的代码判断的,Pi 核心不替你预装。

5.3 扩展能挂到哪些点上

pi.on 能订阅的生命周期事件相当密,几个有代表性的:

  • 会话:session_startsession_shutdown
  • Agent 回合:before_agent_startagent_startturn_startturn_endagent_end
  • 工具:tool_call(可拦截)、tool_result(可修改)、tool_execution_start/update/end
  • 输入:input(可拦截/改写用户输入)
  • 与模型提供商的交互:before_provider_requestafter_provider_response

除了监听事件,扩展还能主动往里加东西:

  • pi.registerTool({...}) 注册一个模型可调用的新工具(带 namedescription、参数 schema 和 execute);
  • pi.registerCommand("stats", {...}) 注册一个 /stats 斜杠命令;
  • 通过 ctx.ui 弹出 select / confirm / input / editor 对话框、发通知、设状态栏;
  • 通过 ctx.sessionManagerctx.getContextUsage()ctx.compact() 读会话状态、看 token 用量、甚至主动触发压缩。

这套 API 的存在,正是”薄核心 + 厚上层”这句话的技术底座:核心保持通用,项目规则跟着项目走,写成代码放进 .pi/

六、从部件到产品:还需要一个 Runtime 和独立评测

前面讲的 Loop、会话、Skills 与扩展各自都能工作,却还没有回答一个产品问题:用户提交一条新指令后,什么时候才能算“这次调用完成”?《动手学 Pi》的第 13、14 个 checkpoint 把这个容易被忽略的接缝单独拎出来,我认为这个拆法很值得借鉴。

6.1 Runtime 负责组合,不应把所有责任塞回 Agent

一个合格的 Runtime 至少要明确四件事:从哪个活跃叶子恢复历史、每次请求前如何投影上下文、这次运行新增了哪些消息、以及何时把它们提交到 Session Store。它不是又一个“万能 Agent 类”,而是给已有部件划定协调顺序的 composition root。

1
2
3
4
5
6
指定 active leaf
→ 恢复该路径上的已提交消息
→ 本轮临时消息与路径共同构造模型上下文
→ Agent Loop 产生新增消息
→ 逐条追加到 Session Store,并推进 active leaf
→ Runtime.prompt() 才向调用方报告成功

这里有两条特别容易在 demo 中漏掉的约束:

  • 非空会话不能靠“磁盘最后一条记录”猜当前分支;调用方必须显式选择 active leaf。否则分叉之后,恢复到哪一段历史会变成不可解释的偶然行为。
  • 模型完成不等于产品调用完成。若模型已在内存中答完、但新消息写入会话时失败,内存 transcript 和可恢复历史就已经分叉。教学 Runtime 选择把这种部分提交标为不可继续的 poisoned 状态,拒绝后续请求,直到调用方重新恢复或诊断。它没有声称这等于 fsync 级的崩溃耐久性,但至少不会默默拿不一致的状态继续工作。

真实 Pi 的持久化时机和课程中的教学实现并不完全相同;这里可迁移的结论不是“照抄某个 append 顺序”,而是:恢复、上下文投影、提交和失败后的可继续性,必须由同一个明确边界负责,并且可测试。

6.2 评测既要看答案,也要看轨迹

“最后回复看起来对”不足以验证一个 coding Agent。课程的收尾做法是:每个 EvalCase 都重新建立 Runtime、会话与临时工作区;运行后只取 active path 和声明过的文件,再交给 judge。这样旧文件、未选分支或上一次运行的缓存不能偷渡成这一次的成功证据。

我会把它归纳为四条可直接迁移到 Pi 扩展或自建 Harness 的评测原则:

  1. 每个 case 使用全新的 fixture,不能复用上一次任务的目录、会话或 Runtime。
  2. 先验证协议:工具调用与结果是否一一配对、运行结果与已提交活动路径是否一致;协议坏了,就不应让 judge 把它判成“任务完成”。
  3. judge 只接收深拷贝、冻结后的 observation,以及任务明确声明的文件;评测代码不应反向修改运行现场。
  4. 报告保留稳定的分类和计数(调用数、错误数、检查通过数),而不是把可能含凭据或项目正文的原始 trace 全量外泄。

这四条并非 Pi 官方的唯一评测规范,而是教材为可教学性作出的设计选择;我认为它们对生产验证同样合理。它们也补上了本文此前的空白:可扩展不只是“能加功能”,还必须能独立证明扩展没有破坏运行边界。

七、可扩展性的代价:选择权交回给你

“能扩展”听起来总是好事,但它把选择权也一并交给了使用者。

Pi 不会替每个团队预装”改支付代码前先跑测试”这类规则。你得像上一节那样把它写进项目扩展或 Skill,再通过真实任务验证它是否真的会触发。写完不测,等于没写。

这也是 Pi 与偏重内置工作流的编码 Agent 最明显的区别:前者更适合”你已经知道团队要什么,并愿意把规则工程化”;后者更适合”先拿到一套较完整的默认行为,慢慢改”。两者不是能力高低,而是默认分工不同。

社区项目 oh-my-pi 是观察这个生态的好例子:它在 Pi 基础上组合了更多工具、IDE 相关能力和自动化功能。它说明薄核心可以承载很重的分发版本,但也提醒你,别把社区分发版的能力直接当成 Pi 核心的默认能力。

八、权限边界不在 Pi 内部,而在它外面

这一点需要单独、认真地说清,因为它直接关系到安全。

Pi 官方 README 写得毫不含糊:它不内置限制文件系统、进程、网络或凭据访问的权限系统,默认继承启动它的用户和进程的权限。上一节那个 tool_call 拦截 rm -rf 的扩展也顺带说明了同一件事,文档明确提醒:”扩展以你的完整系统权限运行,能执行任意代码,只安装你信任来源的扩展。”换句话说,Pi 里”要不要拦某个操作”这件事,本身就是交给你写的代码去决定的。

这意味着:你在本机跑 Pi 时,Agent 能做什么,首先取决于你给当前进程什么权限。它不会天然提供一个”只允许改当前工作区”的默认沙箱。

所以,如果任务会接触不可信输入、敏感凭据或生产资源,正确的做法是把隔离放到 Pi 外面。官方给出三个方向:

方案 适合的边界
Gondolin 扩展 把宿主机上的 Pi 与模型认证保留在外,把内置工具与命令放进本地 Linux 微虚拟机执行
Docker 把整个 Pi 进程放进容器,获得相对直接的隔离
OpenShell 用策略控制的沙箱运行整个 Pi 进程

还要区分两类完全不同的安全,别混为一谈:

  • 供应链安全,也就是”装进来的依赖有没有问题”。Pi 在这方面相当严格:直接依赖锁定精确版本、设置依赖的最小发布时间(.npmrc 里的 min-release-age)、用 shrinkwrap 固定解析结果、CI 里跑 npm audit、新增带生命周期脚本的依赖需要显式审查。
  • 运行时安全,也就是”正在跑的 Agent 能对你的机器做什么”。这靠上面的容器或沙箱来管。

前者不能替代后者。依赖再干净,也拦不住一个拥有你全部权限的 Agent 去读你的 SSH 私钥,那是运行时边界的职责。

九、什么时候适合选 Pi

Pi 不是”越轻越好”的答案,而是一种有前提的选择。

比较适合的情况:

  • 你想用终端完成编码任务,并且希望更换模型提供商或自定义工具。
  • 你有明确的项目规范,愿意把它整理成 Skills 或扩展。
  • 你需要会话分支和可回溯的探索过程。
  • 你能为运行环境提供容器、沙箱或其他权限控制。

需要谨慎的情况:

  • 你希望安装后就获得严格的文件、网络和命令审批边界。
  • 任务会接触不可信输入、线上凭据或生产系统,但你还没准备好外层隔离。
  • 团队没人维护 Skills 和扩展,却希望规则会自动长期保持一致。

我的理解:先把可变的规则放到正确的层

把八节连起来看,Pi 最值得学的地方不是”提示词一定要多短”,而是它对分层的坚持:

  • 模型调用和工具循环,稳定、通用,留在核心;
  • 会话分支与压缩,属于编码场景的通用能力,放在编码 Agent 层;
  • 专项知识和流程,随任务变化,做成按需加载的 Skills;
  • 团队规则和自定义功能,随项目变化,写成 .pi/ 里的扩展;
  • 权限边界,和你的运行环境强相关,整个交给宿主的容器/沙箱。

每一样东西,它都先问”这会不会随团队/项目/环境而变”,会变的就往外推一层。这种分层不会减少你的工程工作,只会让工作的位置更清楚。对个人项目,它给了很大自由度;对团队和生产环境,它要求你把权限、测试、发布和审计规则认真补齐。

真正该验证的,不是 Pi 的介绍页有多简洁,而是你为它配置的那些规则,能不能在真实任务里稳定地触发。


参考资料