wth2jhl.online

Back

这个项目把”什么不做”当成了核心设计原则:No MCP. No sub-agents. No permission popups. No plan mode. No built-in to-dos. No background bash. 然后把”做这件事该有的能力”以扩展(Extension)、技能(Skill)、提示模板(Prompt Template)和主题(Theme)的形式打包成 npm 包,让 Agent 自己长出来——甚至让 Agent 自己写这些扩展。

这就是 earendil-works/pi ↗ 在做的事。作者是 Mario Zechner(X 上的 @badlogicgames ↗),他也是 libGDX 的作者、Lemuroid 的作者;Eärendil 是托尔金笔下那颗”晨星”——Earendil Works 是他现在的公司,仓库里还埋着 Gondolin(一种 microVM 沙箱)的彩蛋。

官方一句话定位:“Pi is aggressively extensible so it doesn’t have to dictate your workflow.”(Pi 极度可扩展,所以不需要替你决定工作流。)所有的”该有却没有”——子代理、计划模式、权限弹窗——官方都明示可以用扩展、tmux 或第三方 Pi 包来自己补。设计哲学贯彻到 README 的 “6 个 No”、AGENTS.md 的”提交规则”、CONTRIBUTING.md 的”自动关闭”——本文会逐个拆开。

Pi 交互模式 TUI(仓库原生截图)

Pi 交互式 TUI(原生保留,来自 packages/coding-agent/docs/images/interactive-mode.png)


一、项目速览:定位、体量与治理#

1.1 一句话定位#

Pi = 一个极度极简、自扩展、TS 全栈、严格管 npm 依赖的终端编码 Agent harness,配套一套专门给 AI 看的工程治理规则。

四种使用姿势:

姿势入口适合谁
Interactive TUIpi 启动(main-screen / alternate-screen 两套渲染)本地开发者长期陪跑
Print / JSONpi -p "..." / --mode jsonCI、脚本、单次执行
RPC 子进程pi --mode rpc自定义前端、IDE 插件、多 agent 编排器
SDK@earendil-works/pi-coding-agent 的 createAgentSession()把 Agent 当库嵌入自己的应用

1.2 体量数据(v0.8.x,2025-11 上线,截至 2026-09 仍在快速迭代)#

维度数字说明
仓库体积33 MB(≈ 1700+ 文件)比 HyperFrames 轻一个数量级
业务源码148,854 行 TS/TSX(不含测试)11 个 package
测试代码535 个测试文件coding-agent 269 个、ai 143 个是主力
AI Provider 适配器39 个 provider、46+ provider 列表项(含 minimax-cn / kimi-coding / moonshot-cn 等中国变体)共用 3 套底层 wire 协议
LLM 模型目录构建时从 models.dev 拉取并生成 models.generated.ts单一权威来源,便于自动同步
文档packages/coding-agent/docs/*.md 30 篇 + .pi/ dogfooding 配置含 extensions/skills/sessions/compaction/rpc/sdk/containerization/tmux…
官方示例examples/extensions/ 79 个、examples/sdk/ 14 个含 doom-overlay、gondolin、plan-mode、custom-provider-gitlab-duo
提交策略depth-50 已 50 个 commit旧仓库 badlogic/pi-mono 的历史延续到 Earendil Works
Package源码行数(含测试)职责
coding-agent70KCLI + 4 种模式 + toolset + session + compaction + extensions runtime
ai24K39 个 provider 适配层 + EventStream + model catalog + OAuth
agent24KAgent 运行时 + 状态机 + JSONL session repo + tool 执行
tui18K差分渲染 TUI 框架 + CSI 2026 同步输出 + 主题
chord5.8K通用插件/服务/复制状态/Delta 跟踪/Go-like context
server2K实验性本地服务:durable session + 多端 presentation 路由
client1.1Ktransport-neutral 协议客户端(WebSocket / Unix socket / 任意有序字节流)
evals1.4K文档即评测:用 pi 自己的文档生成任务套件 + LLM judge 打分
protocol0.9KCBOR + routed envelope + attachment routing(版本号 v8)
telemetry0.9Kvendor-neutral telemetry contracts + 一致性测试
session-backends0(仅 sqlite-node 子包)备选后端;JSONL 是默认,避免 native binding 默认污染

1.3 治理:自动关闭、lgtm、You must understand your code#

CONTRIBUTING.md 的几条规则非常”凶”:

  • 新人 issue / PR 默认自动关闭,维护者每日 review 后才会 reopen;周末不保证看
  • 维护者用 lgtm / lgtmi 解除自动关闭(必须在回复开头或结尾)
  • The One Rule:“You must understand your code. If you cannot explain what your changes do and how they interact with the rest of the system, your PR will be closed.” 用 AI 写代码可以;提 AI 生成的”自己不懂的代码”不行
  • 群里对”批量 agent 生成 issue”和”无视 CONTRIBUTING 两次以上”的人是永久封号
  • 引一段 README:“Using AI to write code is fine. Submitting AI-generated slop without understanding it is not.”

这种治理的本质是用流程换质量:仓库的核心不是更多 PR,而是”每个 PR 都是被读懂过的”。


二、设计哲学:六个 No 和”适配你的工作流,不是反过来”#

packages/coding-agent/README.md 里有一段 Philosophy(原生引用),原文 6 个 No 是核心:

No MCP. Build CLI tools with READMEs (see Skills), or build an extension that adds MCP support. Why? ↗

No sub-agents. There’s many ways to do this. Spawn pi instances via tmux, or build your own with extensions, or install a package that does it your way.

No permission popups. Run in a container, or build your own confirmation flow with extensions inline with your environment and security requirements.

No plan mode. Write plans to files, or build it with extensions, or install a package.

No built-in to-dos. They confuse models. Use a TODO.md file, or build your own with extensions.

No background bash. Use tmux. Full observability, direct interaction.

读完整段才发现这不是”什么都没做”,而是每一句都在指向一个外部替代品:CLI tools + Skills 替代 MCP;tmux 替代 sub-agent 和 background bash;容器替代 permission popup;TODO.md 替代内建 todos;文件替代 plan mode。每一条都同时配一句 “or build your own with extensions, or install a package.”——核心不让步,扩展接口齐全。

README 里另一段(原生)把这件事讲得很透:

“Pi ships with powerful defaults but skips features like sub agents and plan mode. Instead, you can ask pi to build what you want or install a third party pi package that matches your workflow.”

(Pi 自带强力默认值,但主动跳过子代理和计划模式。你可以直接让 pi 自己造,或者装个第三方 pi 包匹配你的工作流。)

这一条”Don’t dictate workflow”的反面是:你装哪个 pi 包,就得到哪种 Agent。pi install npm:@someone/claude-style-harness 真的可以一键换肤。

README 里那句更”扎心”的是:

“Pi is aggressively extensible so it doesn’t have to dictate your workflow. Features that other tools bake in can be built with extensions, skills, or installed from third-party pi packages. This keeps the core minimal while letting you shape pi to fit how you work.”


三、三层架构:ai → agent → coding-agent#

11 个 package 不是平铺的,而是清晰的三层堆叠:

3.1 pi-ai:39 个 provider 的统一接口 + 跨模型切换#

@earendil-works/pi-ai 是统一 LLM API(README 自述):

Unified LLM API with provider collections, automatic auth resolution, token and cost tracking, and simple context persistence and hand-off to other models mid-session.

三层抽象:

  1. Provider 工厂(anthropicProvider() / openaiProvider() / googleProvider() …)→ models.setProvider(provider) 注册

  2. Wire protocol 复用(README 原生):

    Providers internally share API implementations (the wire protocols): Anthropic models use anthropic-messages, OpenAI uses openai-responses, while xAI, Groq, Cerebras, OpenRouter, and most others share openai-completions. Mixed-API providers (GitHub Copilot, OpenCode Zen) dispatch per model.

    39 个 provider 不是 39 套实现,而是 3 套 wire 协议 + 39 套鉴权/路径/format 适配

  3. Auth 自动解析:env → 凭证存储 → OAuth → 特定 provider 的目录(~/.pi/agent/auth.json),并允许扩展改写 header 做透传代理

模型目录本身也是自动维护:packages/ai/scripts/generate-models.ts 拉 models.dev,生成 models.generated.ts(“authoritative values until models.dev and passthrough catalogs catch up”)。AGENTS.md 明确禁止手动编辑这个文件,必须走脚本重新生成。

最牛的还是 跨 provider 切换(README 原生示例):

// Start with Claude
const claude = models.getModel('anthropic', 'claude-sonnet-4-5')!;
context.messages.push({ role: 'user', content: 'What is 25 * 18?', timestamp: Date.now() });
context.messages.push(await models.completeSimple(claude, context, { reasoning: 'medium' }));

// Switch to GPT-5 - it will see Claude's thinking as <thinking> tagged text
const gpt5 = models.getModel('openai', 'gpt-5-mini')!;
context.messages.push({ role: 'user', content: 'Is that calculation correct?', timestamp: Date.now() });
context.messages.push(await models.complete(gpt5, context));

// Switch to Gemini
const gemini = models.getModel('google', 'gemini-2.5-flash')!;
typescript

文档总结的规则:

  • user / toolResult 透传不变
  • 同 provider 同 API 的 assistant message 原样保留
  • 跨 provider 的 thinking blocks 转成 <thinking> 标签文本(各家 reasoning 内容不能直接互换,转成 tagged text 是稳妥做法)
  • 工具调用和普通 text 全部保留
  • aborted 但有部分内容的消息也支持(“Including aborted messages with partial content”)

这一套意义重大:你可以”先用便宜小模型起稿,遇难题换 o3 / Sonnet 4.6 出活”,整个对话上下文不会被切碎。这是 2025-2026 的 multi-provider 时代最被低估的工程能力——大多数同类工具把每个 provider 的 message schema 当私货,跨 provider 切换要么报错要么丢 reasoning;pi-ai 把这件事做成了一等公民。

3.2 pi-agent-core:Agent 运行时#

@earendil-works/pi-agent-core 自述为 “Stateful agent with tool execution and event streaming. Built on @earendil-works/pi-ai.”。核心抽象:

  • AgentMessage(含 user/assistant/toolResult 标准类型 + 声明合并扩展自定义类型)
  • convertToLlm(messages):把 AgentMessage[] 过滤并转换成 LLM 能吃的 Message[]——required,每轮 LLM 调用前必跑
  • transformContext(messages):optional,用于裁剪/注入上下文
  • streamFn:由 models.streamSimple.bind(models) 提供,把 provider 协议桥到 agent 的事件流

prompt("Hello") 的事件序列(README 原生 ASCII):

prompt("Hello")
├─ agent_start
├─ turn_start
│   ├─ message_start (assistantMessageEvent)
│   │   ├─ text_delta ...
│   │   ├─ thinking_delta ...
│   │   ├─ toolcall_start / toolcall_delta ...
│   ├─ message_end
├─ turn_end
├─ agent_end
plaintext

事件类型有 agent/turn/message/tool_execution 四对 start/end,配合 subscribe() 就是完整的响应式编程面。

工具以 schema + execute 注册(@sinclair/typebox 声明参数):

const weatherTool = {
  label: "Weather", name: "weather",
  description: "Get weather for a location",
  parameters: Type.Object({ location: Type.String() }),
  execute: async (_toolCallId, params) => ({
    content: [{ type: "text", text: `Weather in ${params.location}: sunny` }],
    details: {},
  }),
};
typescript

会话通过 SessionRepo 持久化——JSONL 是默认后端,SQLite 在独立的 @earendil-works/pi-session-backend-sqlite-node 包(避免 native binding 默认污染 core)。


3.3 pi-coding-agent:CLI、模式、工具集#

最大包(70K 行)。src/modes/ 下四种模式:

模式入口用途
interactive默认启动main-screen / alternate-screen 两套 TUI
print-mode-p "..."单次执行,打印模型响应
json-event--mode json每条事件打一行 JSON,便于日志和 CI
rpc--mode rpcJSON-RPC 2.0 over stdio,给外部进程当 Agent 后端

加上 sdk.ts 暴露的 createAgentSession(),是第五种”嵌入式”使用姿势。

工具集在 src/core/tools/:bash、edit、read、write、find、grep、ls、powershell(Windows)、truncate、output-accumulator、renderers……每个工具都有”内置 renderer”(决定结果在 TUI 里长什么样),且renderer 可以被扩展覆盖(built-in-tool-renderer.ts 示例就是干这个的)。外圈还有 file-mutation-queue.ts(写盘序列化)、output-guard.ts(防爆量输出)、bash-executor.ts(隔离 bash 执行)、session-cwd.ts(CWD 跟踪)等支撑件。

core/ 还有 compaction/ 子目录(auto compaction + branch summarization)、extensions/(扩展 API + 事件类型)、session-manager.ts、skills.ts、prompt-templates.ts、slash-commands.ts、settings-manager.ts、trust-manager.ts(项目信任管理)、system-prompt.ts、telemetry.ts……一份”CLI + Agent”该有的轮子全在这里。

3.4 顶层依赖:server / client / protocol / chord(实验性多端)#

仓库里 client / server / protocol 三个包加上 chord 是一组实验性多端基础设施:

  • pi-protocol:runtime-neutral routed envelopes、CBOR 编码、字节流成帧,协议版本 v8
  • pi-chord:插件(plugins)+ 分面(facets)+ 服务(typed tokens,单例/keyed)+ 复制状态(replicated state)+ Delta tracking + Go-like context
  • pi-server:本地实验服务,把 durable Session 和 AgentHarness 暴露成 routed 服务
  • pi-client:transport-neutral 客户端(WebSocket / Unix socket / 任意有序字节流)

pi-agent-core 的 README 末尾还有一句:“Experimental facet services primitives live in @earendil-works/chord. The agent core does not export the service runtime.”——意思是 chord 是 agent core 想做”多端”时的下一层,但 agent 本身没有强依赖。这套设计是 Pi 面向未来的”同一 Agent,多种前端(终端/WebUI/IDE)“的雏形。


四、会话系统:JSONL + 树状分支 + compaction#

Pi 会话最值得抄的设计是用 JSONL 把会话存成树。

4.1 文件格式与版本演进#

docs/session-format.md 写到:

~/.pi/agent/sessions/--<path>--/<timestamp>_<session-id>.jsonl
plaintext

每行一个 JSON 对象,靠 id / parentId 字段构成树形结构:

  • v1:线性 entry 序列(legacy,载入时自动迁移)
  • v2:树结构 + id / parentId 链接
  • v3:hookMessage 重命名为 custom(扩展统一用)

载入时自动迁移到当前版本(v3)。这种 in-place branching 不需要新建文件,每次”回到之前的对话分支”是在同一个 JSONL 上加新条目 + 改 parent 指针——天然支持 git diff / git log 风格的会话回放。

4.2 CLI 命令#

命令说明
/resume浏览/选择过往会话
/new新会话
/name <name>给当前会话起名(便于 resume 时找)
/session显示当前 session 文件、id、消息数、tokens、cost
/tree树状浏览当前会话的所有分支
/fork从历史某条 user message 开新会话
/clone把当前活跃分支复制成新会话
/compact [instructions]手动压缩
/export [file]导出 HTML
/share上传到 GitHub Gist 拿可分享 HTML 链接

底层 CLI:pi -c 续接最近一次会话;pi -r 打开 session 选择器;pi --fork <id> 直接 fork;pi --no-session 完全不持久化。

/tree 命令的会话树视图(仓库原生截图)

/tree 树状分支视图(原生保留,来自 docs/images/tree-view.png)

4.3 Compaction 与 branch summarization#

docs/compaction.md 给出两个机制:

机制触发目的
Compaction上下文超阈值,或 /compact总结老消息腾出 context
Branch summarization/tree 切换分支切分支时保上下文

两套机制都共用结构化摘要格式 + 文件操作累积追踪。摘要请求用全新 routing session id,并对支持的 provider 关闭 prompt cache(“these one-off prompts are unlikely to be reused”)。

自动 compaction 触发条件(README 原文):

contextTokens > contextWindow - reserveTokens
plaintext

默认 reserveTokens = 16384(在 ~/.pi/agent/settings.json 或 .pi/settings.json 里可调)。检查时机:

  • 多轮 agent run 中,所有 tool 执行完且结果已追加后、下一个 assistant response 之前
  • 新 user prompt 之前
  • 底层 agent run 结束后

如果阈值越过,就在同一个 agent run 内做 compaction,保留 summary 与近期消息后继续运行——这不是”停 + 重启”,是 in-turn compaction。

工作流三步(README 原生):

  1. Find cut point:从最新消息倒着走,token 估算累加到 keepRecentTokens(默认 20k,可配)为止
  2. Extract messages:从上一个 keep boundary(或会话起点)到 cut point 之间的消息全部取出
  3. Generate summary:调用 LLM 生成结构化摘要;如果之前已有摘要,把旧摘要当迭代上下文一起传——摘要可以叠加,越往后越短

手动触发可以带 instructions:/compact 重点保留所有 bash 输出和错误。branch-summarization.ts 在 /tree 切换分支时,把整条切走的分支也压成一条摘要挂回主树,避免回到主分支时上下文全丢。


五、扩展系统:TypeScript 取代 MCP#

docs/extensions.md 开头就把这件事讲明白:

Extensions are TypeScript modules that extend pi’s behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.

5.1 一个最小扩展(原生代码)#

上面那段 rm -rf 的拦截就是把 “No permission popups” 这条 No 给实现掉的官方示例——你没有的话,照着写一个就行。其他示例:bookmark.ts(书签)、git-checkpoint.ts(每轮 stash,回滚)、dirty-repo-guard.ts(脏仓库保护)、claude-rules.ts(套用 Claude Code 的规则集)、custom-compaction.ts(自定义压缩)、file-trigger.ts(文件变更触发)、inline-bash.ts(内联 bash)、entry-renderer.ts(重写消息渲染)。

扩展自动发现的目录:

路径范围
~/.pi/agent/extensions/*.ts全局
~/.pi/agent/extensions/*/index.ts全局(子目录)
.pi/extensions/*.ts项目本地
.pi/extensions/*/index.ts项目本地(子目录)

文档明确:Auto-discovered locations 的扩展可以用 /reload 热重载。-e ./path.ts 只适合快速测试。

5.2 Skills:实现 Agent Skills 标准的渐进披露#

docs/skills.md 第一句:

Pi implements the Agent Skills standard ↗, warning about most violations but remaining lenient.

Skills 是自包含的能力包:专门的 workflow、setup 指令、辅助脚本和参考文档。Pi 加载位置:

  • ~/.pi/agent/skills/ / ~/.agents/skills/(全局)
  • .pi/skills/ / .agents/skills/(项目本地,需先 trust)
  • Pi Packages 自带的 skills/ 目录或 package.json 里的 pi.skills
  • settings.json 的 skills 数组
  • --skill <path>(可重复,--no-skills 时仍生效)

四条加载规则:

  1. 启动时扫描所有位置,只抽取名字和描述
  2. 系统提示里以 XML 形式列出可用 skills(按 spec)
  3. 任务匹配时,Agent 用 read(或 bash 当 read 不可用)加载完整 SKILL.md——“models don’t always do this; use prompting or /skill:name to force it”
  4. Agent 遵循指令、用相对路径引用脚本和资源

这是渐进披露(progressive disclosure):只有描述永远在 context 里,正文按需加载。跨 harness 兼容:settings.json 加一行 "skills": ["~/.claude/skills", "~/.codex/skills"],Claude Code / Codex 的 skills 直接拿来用。

Skills 作为 /skill:name 命令暴露:

/skill:brave-search           # Load and execute the skill
/skill:pdf-tools extract      # Load skill with arguments
bash

5.3 自扩展:让 Agent 自己写扩展#

README 里反复出现的一句话是 “pi can create extensions. Ask it to build one for your use case.”——这不是 PR 话术,是真正的功能定位。最有意思的两个官方扩展示例:

examples/extensions/doom-overlay/(标题就够说明问题):

/**
 * DOOM Overlay Demo - Play DOOM as an overlay
 *
 * Usage: pi --extension ./examples/extensions/doom-overlay
 *
 * Commands:
 *   /doom-overlay - Play DOOM in an overlay (Q to pause/exit)
 *
 * This demonstrates that overlays can handle real-time game rendering at 35 FPS.
 */
typescript

扩展装载一个 DoomEngine,注册一个 doom-overlay 命令,把一个 35 FPS 的实时游戏渲染到 TUI overlay 里——WAD 文件自动从常见位置找。这个扩展在解释扩展能力的同时,也在嘲笑那些把 Agent CLI 框得死死的工具。

在 TUI 里跑 DOOM 的截图(仓库原生截图)

DOOM 在 Pi 的 TUI overlay 里跑(原生保留,来自 docs/images/doom-extension.png)

examples/extensions/plan-mode/(给”无计划模式”加一个计划模式)——直接复刻官方不肯做的功能。这一条说明 “No X” 的真实含义:“Pi 不替你做选择;你做。”

其他值得看一眼的官方扩展:

  • gondolin/:把 pi 和 provider 鉴权留在 host,工具和 ! 命令路由进本地 Linux microVM(Gondolin——和项目同源的另一座精灵城市)
  • sandbox/:通用沙箱接口
  • custom-provider-anthropic/ / custom-provider-gitlab-duo/:写一个扩展就接一个新 provider(无需改 ai 包本身),docs/custom-provider.md 有完整协议
  • dynamic-resources/:运行时按需加新扩展
  • plan-mode/、handoff.ts、git-merge-and-resolve.ts、github-issue-autocomplete.ts、claude-rules.ts……

5.4 .pi/ dogfooding:项目自己用自己#

仓库根的 .pi/ 是 Pi 的活样本——官方把自己的工作流也写在这里:

.pi/skills/add-llm-provider.md   ← 给 Agent 用的"如何加新 LLM provider"技能
.pi/prompts/is.md                ← "/is" 命令的提示模板(分析任务)
.pi/prompts/sa.md                ← "/sa" 命令(系统架构)
.pi/prompts/wr.md                ← "/wr" 命令(写代码)
.pi/prompts/pr.md                ← "/pr" 命令(PR review)
.pi/prompts/deslop.md            ← "/deslop" 命令:去 AI 味的代码清理
.pi/prompts/cl.md                ← "/cl" 命令(changelog)
.pi/extensions/prompt-url-widget.ts
.pi/extensions/import-repro.ts
.pi/extensions/tps.ts            ← tokens-per-second 角标(计算 agent 速度)
.pi/extensions/redraws.ts        ← 重绘统计
plaintext

其中 deslop.md 是一份很有意思的”文化产物”——它是 /deslop 这个内置命令的提示词,开头:

Review the completed workpackage and simplify its implementation without changing its intended behavior.

  • Remove abstractions that have no clear responsibility or do not reduce real complexity.
  • Inline trivial helpers, wrappers, adapters, and pass-through layers when they obscure rather than clarify behavior.
  • Remove duplicated logic and put shared behavior at the appropriate existing layer.
  • Remove speculative flexibility, configuration, extension points, and generic machinery that have no current requirement.
  • Remove speculative defensive programming inside trusted code. Validate at real trust boundaries; rely on types and established invariants internally.
  • Do not keep fallback behavior for states that should be impossible. Prefer fixing the invariant or type model.
  • Simplify excessive state, boolean flags, branching, indirection, and special cases. Rework the underlying representation when that is clearer.
  • Tighten TypeScript types so invalid states are unrepresentable. Avoid any, unnecessary assertions, broad types, and optional fields for impossible states.
  • Prefer direct, readable code over cleverness, premature abstraction, and framework-like infrastructure.
  • Delete comments that merely restate the code, but preserve comments that explain constraints, intent, or non-obvious decisions.

整段都在和 AI 生成代码的”过度防御、抽象过早、冗余注释”正面对抗。这就是为什么 AGENTS.md 第 7 条写的是 “Always ask before removing functionality or code that appears intentional.”——作者清楚 Agent 会过度工程化,所以把”主动去抽象”做成了内置命令。

tps.ts 则展示了扩展能有多轻:监听 agent_start / agent_end 算时间差,算 tokens-per-second 推到 footer。整个扩展就是一个”agent 速度计”。


六、TUI:差分渲染 + 同步输出#

@earendil-works/pi-tui 自述为 “Minimal terminal UI framework with differential rendering and synchronized output for flicker-free interactive CLI applications.” 关键能力(README 原文):

  • Interchangeable Renderers: Shared TUI interface with main-screen and alternate-screen implementations
  • Differential Rendering: Updates only changed lines or viewport rows
  • Application-owned Scrolling: Alternate-screen viewport supports mouse, trackpad, and keyboard navigation
  • Synchronized Output: Uses CSI 2026 for atomic screen updates (no flicker)
  • Bracketed Paste Mode: Handles large pastes correctly with markers for >10 line pastes
  • Component-based: Simple Component interface with render() method
  • Inline Images: Renders images in terminals that support Kitty or iTerm2 graphics protocols

两个值得一提的工程点:

  1. CSI 2026 同步输出:终端支持 DCS 序列 CSI ? 2026 h/l 来同步多次绘制——开启后多次 print 在终端一次原子刷新,避免半画状态闪烁。TUI 框架默认开启。
  2. Kitty / iTerm2 图形协议:TUI 里能直接渲染 PNG(hex-encoded escape sequence)。interactive-mode.png 这张截图就是用 Kitty 协议渲染的——你看到的”图”其实是终端里的 escape sequence。

内置组件:Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, MouseRegion, Spacer, Image, Box, Container, VStack, HStack, ScrollView——一套完整的”没有 React 也能写出漂亮 CLI”的素材库。配套的 themes.md 让所有组件主题可换。

仓库根的 tui-plan.md(36 KB 的设计交接文档)值得专门看——这是一份叫 “Alternate-Screen Layout System Plan” 的 implementation handoff,开头写明:

This document is an implementation handoff. It records the decisions made during design discussion and should be treated as the intended scope unless implementation findings require revisiting a decision.

里面列了 10 条”Core decisions”(例如 “Rebuild the internal layout tree on each requested render. Do not rebuild component state.”),附理由 + 替代方案否决记录。把”为什么这样设计”和”否决掉的其他设计”公开在仓库根,让任何想动手改 TUI 的人先读这份文档——这是 Agent-friendly 设计的极致形态。


七、chord + protocol:为”多端 Agent”铺的路#

7.1 chord:通用插件/服务合成运行时#

@earendil-works/chord 在 README 里自述:

Chord is an application-composition runtime for systems assembled from plugins/extensions. It provides facets, services, replicated state, and a pluggable remote-service boundary. It is developed as a standalone package in the Pi monorepo, but it is not a Pi package: it does not depend on any other Pi workspace package and can be used by unrelated applications.

核心概念:

  • Plugins 是同步的 setup 单元,声明它们提供和需要的服务。所有 plugin 声明完后,host 校验完整依赖图、绑定服务、先 activate providers 再 activate consumers、反向顺序 dispose
  • Facets 是 plugin 的一个部分,单独打包,可以运行在不同进程或环境——同一个 plugin 切成 backend/frontend 两片装在不同进程里
  • Services 是类型化的稳定 token,单例(singleton)或 keyed;可进程内(任意 JS contract)或可远程调用
  • Replicated state:producer 改 tracked state proxy + 调 publish(context);consumer 收到完整 immutable 值。一次 publish flush 一个 decoded operation batch,每个远端 stream 有独立 path-codec state
  • Delta tracking:在 flush 时从 tracked plain JSON 派生紧凑的 ops,保留 string append / front-truncation / array-append 行为,不保留 mutation 历史(节省内存),支持 durable base batches、校验不可信 ops
  • Remote service adapters:暴露服务 + 打开 bindings,严格 JSON 入参/出参,不约束 framing/routing/transport/wire envelope
  • Context:Go-style context,传 cancellation 和 invocation-scoped application values(permissions、telemetry)

命名风格延续了 Tolkien 主题:服务前缀 $chord.*,标识符 chord.*。

7.2 protocol / server / client:routed envelopes + CBOR#

@earendil-works/pi-protocol 是 chord 的”传输皮”——运行时中立:

Runtime-neutral routed envelopes, CBOR encoding, and byte-stream framing for the experimental Pi protocol.

Protocol version 8 defines:

  • a version handshake that identifies the logical serverId;
  • explicit server and Session request targets;
  • correlated requests and responses with opaque strict-JSON payloads;
  • request cancellation, opaque subscription updates, and out-of-band attachment changes;
  • non-empty opaque error codes and bounded transport messages.

设计要点:

  • Server target = { serverId },Session target = { serverId, sessionId, attachmentId }——三次路由锁定”逻辑 server + durable session + 当前活跃的 presentation attachment”
  • attach() / detach() 不返回路由 ID,server 通过 out-of-band attachment 消息发布当前 live route——防止路由泄漏
  • 每个 wire frame = 4 字节大端 payload length + 一个 definite-length CBOR item——encodeClientMessage() / encodeServerMessage() 校验完整 frame,ClientMessageDecoder / ServerMessageDecoder 处理任意流分片与合并
  • payload 由 chord 决定:service catalogue / subscription snapshot+update / Delta path codecs / 服务错误码全部归 chord;pi-protocol 只校验 strict JSON,不解析 chord 语法

pi-server 把 durable session 和 AgentHarness 暴露成 routed 服务,一个 Session 多个 presentation——同一个 Agent 会话可以同时连终端、WebUI、IDE 三种前端,互不干扰。pi-client 给 WebSocket / Unix socket / 任意有序字节流都留了适配点。

这套是 Pi 迈向”Web / IDE / TUI 三端共存 Agent”的底座。还没发布 1.0,但设计文档清晰、命名一致、版本号一路打到 v8——看得出作者的耐心。


八、工程文化:supply chain、evals、治理#

8.1 supply chain hardening#

README 自述一段(原生):

We treat npm dependency changes as reviewed code changes.

具体落地:

  • .npmrc:save-exact=true + min-release-age=2(禁止当天发布的依赖进 lockfile——给 npm 留出发现坏发布的窗口)
  • package-lock.json 是依赖真理源;pre-commit 默认禁止 lockfile 改动,需要 PI_ALLOW_LOCKFILE_CHANGE=1
  • npm run check 校验 pinned direct deps、TypeScript import 兼容性、生成的 shrinkwrap
  • 发布到 npm 的 CLI 包自带 packages/coding-agent/npm-shrinkwrap.json——给 npm 用户固定 transitive deps
  • npm run release:local 在仓库外构建、打包、独立 npm + Bun 装一遍再 tag
  • CI 用 npm ci --ignore-scripts,每天跑 npm audit --omit=dev + npm audit signatures
  • shrinkwrap 生成有 lifecycle scripts 白名单——新加依赖的 lifecycle script 必进白名单才能过 CI

这一套下来,恶意/有 bug 的 npm 包要影响 Pi 的发布,需要同时通过”发布两天后才能用 + 签名校验 + 锁死的 shrinkwrap + 审计流水线”。对一个小团队的开源项目来说,这远超行业平均。

8.2 文档即评测(evals)#

@earendil-works/pi-evals 自述:

Documentation-grounded eval harness for pi. Runs pi against task suites derived from its own documentation, scoring agent transcripts with deterministic checks plus LLM-judge rubrics.

也就是:用 pi 自己的文档生成任务套件,跑 pi,再用 deterministic 检查 + LLM judge rubric 打分。最近的 commit feat: add implementation-backed documentation evals (#9280) 把这条路线又往前推了一步(“implementation-backed”——文档描述的行为和实现双向对照)。

docs/evals 没有详细的 cheat sheet,但 package.json 里能找到跑法:

cd packages/evals
npm install
npm run eval -- --suite skills --model anthropic:claude-sonnet-4-5
bash

这是一种自我强化的飞轮:改完文档要过评测、改完实现也要过评测。两边漂移的成本极高。

8.3 AGENTS.md:给 Agent 的项目规则#

AGENTS.md 全文是给 pi 的开发者看的,但每一行都能搬到任何大型 monorepo:

  • “Keep answers short and concise. No emojis in commits, issues, PR comments, or code.”
  • “No any unless absolutely necessary.”
  • “No inline imports (await import(), import("pkg").Type). Top-level imports only.”
  • “Use only erasable TypeScript syntax (Node strip-only mode) in code checked by the root config: no parameter properties, enum, namespace/module, import =, export =. Use explicit fields with constructor assignments.” —— 强制让 tsc 用 strip-only 模式编译,业务代码不需要 emit
  • “Always ask before removing functionality or code that appears intentional.”
  • “Multiple pi sessions may be running in this cwd at the same time, each modifying different files. Git operations that touch unstaged, staged, or untracked files outside your own changes will stomp on other sessions’ work.” —— 多 Agent 并行的工作树隔离

最后那条配套的操作规范:

  • git add <path1> <path2>(never git add -A / git add .)
  • 永远 git status 确认只 stage 自己的文件
  • 永远不 git reset --hard / checkout . / clean -fd / stash / commit --no-verify
  • rebase 冲突只能解自己改过的文件

这不是 AGENTS.md 给你”建议”——是 AGENTS.md 假设你和”另一个 agent”在同一棵 worktree 里抢文件,互相 stomp 会污染双方的产出。配套的还有:

  • “Never modify packages/ai/src/models.generated.ts directly; update packages/ai/scripts/generate-models.ts instead, then regenerate.”
  • “If you create or modify a test file, run it and iterate on test or implementation until it passes.”
  • “If you need PR file contents, fetch/read them into temporary files or use git show <ref>:<path> without switching branches.”

8.4 Sessions 公开化 + 训练数据回灌#

README 末尾推荐把 OSS 会话通过 badlogic/pi-share-hf ↗ 上传 HuggingFace:

Public OSS session data helps improve models, prompts, tools, and evaluations using real development workflows.

Mario 自己的 badlogicgames/pi-mono 数据集长期挂在 HuggingFace。这既是个人产出数据,也是”在野生场景里训练下一代 coding agent”的训练素材来源——作者把自己的狗粮喂回来,形成闭环。


九、值得抄的工程实践#

把 Pi 当”工程教材”读,有八条直接可抄:

  1. “什么不做”是设计文档的一部分。六个 No 写得明明白白,每条配替代品——把”被否决的功能”公开出来,比假装”我们什么都有”诚实得多,也免得用户从错误入口试。
  2. 跨 provider 的 thinking 互转用 tagged text。不同家的 reasoning schema 不能直接互换,转成 <thinking>...</thinking> 的 tagged text 是个简单又稳妥的兜底——以后任何想支持多 LLM 的 Agent 都用得上。
  3. JSONL 会话树 + in-place branching。比”每次 fork 都复制整份 JSON”优雅一个量级,也天然兼容 git log 风格的会话回放。
  4. compaction 用全新 routing session id + 关闭 prompt cache。摘要是一次性任务,没必要污染主对话的 prompt cache——这是只有亲手跑过才会发现的细节。
  5. 扩展机制取代内置功能 + 内置命令 /deslop。把”AI 过度工程化”直接做成可触发的子命令,是对抗代码腐烂的最高效工具。
  6. Skill 用渐进披露 + 跨 harness 兼容。只把描述塞进 system prompt,正文按需加载——~/.claude/skills 直接一行加进 settings 就能用,把”Skill 标准化”的生态价值拉满。
  7. npm 依赖当 reviewed code。save-exact + min-release-age=2 + shrinkwrap + --ignore-scripts + 签名校验——比”小心挑依赖”靠谱得多。
  8. 36 KB 的设计交接文档。把”为什么这样设计 + 否决掉哪些方案 + 决策范围”全部公开在仓库根,对 Agent 和人类一样友好。

十、局限与注意点#

  • 仍在快速迭代:仓库还在 0.x,wire protocol 一路打到 v8,agent core 仍在拆分独立服务、SQLite session backend 单独成包——今天能用的接口明天可能改。
  • 新人极度不友好:自动关闭 + lgtm + You must understand your code——对没耐心的 contributor 几乎是劝退条款。想贡献就先研究一段时间,再用 lgtmi 换发言权。
  • 没有内置权限系统:“No permission popups” 是有意为之——意味着 pi 默认按启动它的用户权限运行所有命令。生产部署必须用 Gondolin / Docker / OpenShell(packages/coding-agent/docs/containerization.md 列了三种模式)。
  • 单一维护者风险:设计风格非常”个人”,要承接大量个人决策成本;fork 之后需要投入长期人力。
  • MCP 反向但有代价:把”MCP 不做”当卖点,意味着想用某个 MCP server 时要么自己写 Skill、要么等第三方扩展——生态扩张速度可能慢于 MCP 阵营。
  • 依赖 Node ≥ 22 + git + tmux(部分流程):对极简部署场景是个门槛。
  • 窗口 2 KB:如果你想”无 AI 自动跑一遍”,脚本化体验还在 print/json 模式,没做到 deterministic replay。

十一、总结#

Pi 的表面是”又一个终端编码 Agent”,拆开看是四样东西的合体:

  1. 一个极简、可扩展的 Agent harness——把”什么不做”当设计原则,把”该怎么做”留给扩展和 Skill;
  2. 一套跨 provider 的 LLM 运行时——39 个 provider 共享 3 套 wire 协议,跨模型切换连 thinking 都不丢;
  3. 一个 JSONL 树状会话系统——in-place branching、compaction、branch summarization 三件套齐全;
  4. 一套工业级 npm 供应链 + 文档即评测 + 反 AI 味的工程文化。

它赌的方向很清晰:Agent 不是被设计出来的,是被社区长出来的。你装哪个 Pi 包,就得到哪种 Agent;你想加新功能,不用 fork Pi 自己写扩展;你想让 Pi 用 MCP,自己加 examples/extensions/custom-provider-xxx.ts。Mario 把”可扩展性”做成了产品形态,把”治理”做成了贡献门槛——比 HyperFrames 的”教材式”更激进、更开放。

如果你的团队正在选”Agent CLI 工具 / Agent harness 框架 / LLM 跨 provider 路由”,Pi 值得完整读一遍——第三章的跨 provider 设计、第四章的会话树、第五章的扩展机制、第八章的工程文化都值回票价。Mario 的两篇博客(What if you don’t need MCP ↗ 和 Pi Coding Agent ↗)是更短的入门。

参考链接#

深入解析 Pi Agent Harness:极简到不教 Agent 做事的编码代理
https://wth2jhl.online/blog/pi-agent-harness-deep-dive/
Author 小吴同学
Published at September 8, 2026