Skip to content
GEEKR.Lee
Go back

Pi:一个最小 agent harness,和我怎么把它跑在本地

目录

Pi 不是”功能最全的 coding agent”。它更像一个 agent harness:模型调用、工具执行、上下文组装、会话存储,这四件事做得干净,其余一概不管。没有内置 sub-agent,没有 plan mode,也没有花哨的工作流——取舍很明确,内核最小,扩展面完整。我目前用它跑本地模型加浏览器界面(pi-web-ui),日常写代码够用,比那些大而全的 CLI 更省心。

版本基线是 pi --version → 1.2.0,Node ≥ 22.19。


1. 设计理念

Pi 官方 README 里那句话基本就是全部哲学:

Adapt Pi to your workflows, not the other way around.

拆开看有三层意思。

第一层:内核只做必要的事。 一个 agent harness 的最小职责是:收到消息 → 组装请求(system prompt + 历史 + 工具定义)→ 发给 provider → 流式拿回复 → 执行 tool call → 记录结果 → 决定要不要再来一轮。Pi 就做到这里为止。

第二层:可扩展性靠机制,不靠功能。 extensions(TypeScript 模块,注册工具/命令/快捷键/provider/渲染器)、skills(按需加载的指令文件)、prompt templates、themes,这四类是一等公民,可以打包成 Pi package 用 npm 或 git 分发。想要 sub-agent?装个包,或者让 Pi 自己写一个 extension。

第三层:界面是壳,机制只有一套。 interactive TUI、print/JSON 模式、RPC、TypeScript SDK、以及第三方 web UI,全都走同一个 agent 和 session 实现。这一点很关键——它意味着 web UI 不是”另一套产品”,只是同一内核换了个前端。后面讲 pi-web-ui 时你会看到这点带来的实际好处(会话共享)。

我喜欢的地方:它不替我决定工作流。我的 skills/extensions 里放的是我自己的流程,而不是某个厂商的”最佳实践”。


2. 架构

2.1 agent loop

一轮(turn)的生命周期:

提交消息 → 加入 active branch
         → 组装请求:system prompt + active branch + tools + model settings
         → provider 流式返回(文本 + tool calls)
         → 执行每个 tool call,记录结果
         → 需要再问模型则进入下一轮,否则结束

steering message(中途插话)在当前 assistant turn 结束后注入,follow-up 在 agent 干完手头的活之后才生效。这个语义和 TUI、web UI、RPC 是一致的——同一个 loop,不同接口只是喂消息的方式不同。

2.2 session 是一棵树

Pi 的会话不是线性数组,是 JSONL 文件里的树:每条 entry 有 id,指向它的 parent;当前 entry 所在的路径就是 active branch,下一次请求的历史由它提供。

这个设计带来两个很实用的后果:

2.3 context 组装

system prompt = 基础指令 + 从工作目录发现的上下文文件(AGENTS.md 之类)。工具定义和 skill 描述随请求一起发出去;skill 的全文按需加载,不是每次都塞进 prompt——这点在 skills 多的时候对 token 开销影响很大。

2.4 接口层

模式用途
interactive(TUI)日常交互
print / JSON脚本、CI,输出最终回复或 JSONL 事件流
RPCstdin/stdout 控制一个独立 Pi 进程
TypeScript SDK在应用内跑 agent session(pi-web-ui 就是这么接的)

3. 本地部署

安装走官方脚本最省事,它会把依赖 pin 住:

curl -fsSL https://pi.dev/install.sh | sh
# 或 npm(不 pin 传递依赖)
npm install -g --ignore-scripts @earendil-works/pi-coding-agent

Pi 正常运行不需要依赖的 lifecycle script,所以 --ignore-scripts 是安全的。Node 要求 ≥ 22.19——我机器上跑的是 v22.19 和 v24.15 两个版本,注意 nvm 默认版本要对得上。

进项目目录再启动,工作目录影响上下文文件发现和会话分组:

cd /path/to/project
pi

/login 选 provider(订阅或 API key),凭据存在 ~/.pi/agent/auth.json;/model 选模型。配置目录 ~/.pi/agent 下就是 auth.json、models.json、sessions/、skills 这些,整个状态都在明文文件里,备份和迁移都很直接。

3.1 接本地模型

两条路。

llama.cpp router。 启动 llama-server 时不要带 --model(带了就是单模型模式),用 --models-dir 让它进 router:

llama-server \
  --models-dir ~/models \
  --no-models-autoload \
  --jinja \
  --host 127.0.0.1 --port 8080 \
  -ngl 999 -c 32768

Pi 里用 /llama 管理 router,/model 选它已加载的模型。--no-models-autoload 让加载显式化——router 按需装卸 GGUF,多模型共用一个 server,这个比手动开多个 server 舒服很多。多 shard / 多模态模型放子目录,单文件模型直接放根目录。

OpenAI/Anthropic/Google 兼容端点。 Ollama、LM Studio、vLLM、SGLang 走 models.json:

{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [{ "id": "qwen2.5-coder:7b" }]
    }
  }
}

apiKey 写个占位值让模型出现在选择器里就行(Ollama 会忽略它)。需要真凭据的话,apiKey 和 header 支持 $NAME / ${NAME} 环境变量插值、字面值,或者 !command(请求时执行,不缓存)。打开 /model 会重新读文件,改完不用重启。

我的判断:本地模型走 router/兼容端点就够了;只有 provider 协议或鉴权流程特殊才需要写 provider extension。另外 pi update --models 刷新模型目录,离线时缓存数据仍可用——在网不通的环境里这个细节挺关键。


4. pi-web-ui

pi-web-ui(作者 xing-shuyin)是社区做的浏览器前端:agent 跑在服务端,事件通过 WebSocket 推到浏览器。它不是”给 pi 加了个网页”,而是把日常开发需要的那几块拼在一个 tab 里——chat、文件、终端、Git、模型管理。

安装和运行:

npm i -g --allow-scripts=node-pty pi-web-ui   # npm 12+ 默认拦 install script,node-pty 是原生模块
pi-web-ui                                      # http://localhost:8787

Linux 上 node-pty 没有 prebuild,必走 node-gyp,先装好 build-essential python3。macOS/Windows 有预编译。

我把它装成了系统服务(pi-web-ui server install,macOS 上生成 launchd plist、Linux 开机自启),工作目录固定,PI_WEB_PORT、PI_WEB_CWD、PI_WEB_DATA_DIR 这些环境变量写在 plist 里,崩了自动拉起。

4.1 实际用得最顺的几块

会话和 CLI 共享。 web UI 读的是 <agentDir>/sessions/--<cwd>--/,也就是 pi CLI/TUI 写的同一份 transcript。浏览器里看到的和终端 pi 打开的是同一个项目、同一份历史——我经常终端起一个任务、浏览器里看进度,或者反过来。

steer / follow-up 队列。 agent 正在回复时插话会被排队,等当前 turn 的 tool call 收尾后注入。语义和 TUI 里的 Interrupt 等价。排队的气泡可以丢弃或撤回成草稿,这个比”打断重打一遍”温和很多。

subagent。 pi 内核故意不做 sub-agent,pi-web-ui 用工具层补上了:subagent 工具 spawn 独立的后台会话做并行探索/实现/review,action="wait_all" 收集结果不用轮询;模板(Settings → Subagent templates)配 role prompt、skills/extensions 白名单、模型和 thinking level。内置 13 个模板里 review / implement / research / scout / audit 这几个我常直接用。注意这些是 web UI 加的,不是 pi 内核的能力——用 SDK 或 CLI 时想要同样的东西得自己装包或写 extension。

Git panel 和终端。 status/diff/history、单文件 stage、commit box、分支选择器;写入操作跑在可见终端里,面板跟着走。外部提交的 commit 会自动出现(30s 兜底轮询)。内置终端是 xterm.js + node-pty,支持”terminal-backed bash”——agent 的 bash tool 跑在一个持久可见的终端里,cd、venv、ssh 状态跨调用保留。默认关闭,我开着,调试长任务时很值。

模型管理。 UI 里直接编辑 models.json、每个 provider 存多个命名 key、Custom providers 支持 server 端 probe /models(所以局域网/loopback 端点不受 CORS 影响)。key 和 header 不出服务端,只回昵称。

4.2 两个容易踩的坑

SDK 副本问题。 pi-web-ui 依赖 @earendil-works/pi-coding-agent,npm 全局安装会把依赖嵌套,Node 先解析嵌套副本——所以升级全局 pi CLI 不会改变服务端实际跑的 SDK。看用哪个副本:curl /api/health(piSdkCopies 第一项是生效的)。想让服务跟随较新的全局 SDK,用 PI_WEB_SDK=global 启动。我一开始被这个绕了一圈,后来干脆只升级 pi-web-ui 本身。

安全边界。 web UI 默认只听 loopback;provider key、header 不下发到浏览器。但要清楚:pi 的工具和 extension 以进程权限运行,trust 决定加载哪些项目资源,不沙箱 tool call。这个限制对 CLI 和 web UI 同样成立——在不信任的目录里跑之前先看 Security 那页。


5. 我的用法小结

分工大概是:终端 pi 做短任务和小改动;需要挂着跑、要看文件树/Git/多会话并行时切浏览器;脚本和 CI 用 print/JSON 模式。skills 放流程性指令(比如审计、批量处理这类),extensions 放真正需要执行代码的集成——这两层就是 Pi 的全部”个性化”,内核保持干净。

Pi 的价值在于它把 harness 这一层做得稳:会话是文件、分支可回溯、compaction 不丢原文、接口共用同一机制。功能上它故意留白,而这些留白恰好是可以用 extension/skill 精确补上的部分。如果你要的是”开箱即用但工作流属于自己”,这个组合比大部分同类更合手。


参考


Share this post:

Previous Post
在四块 2080 Ti 上跑 Qwen3.8-Flash-Next(Strata)