In the past three years, I've been using LLMs for assisted coding. If you read this, you probably went through the same evolution: from copying and pasting code into ChatGPT, to Copilot auto-completions (which never worked for me), to Cursor, and finally the new breed of coding agent harnesses like Claude Code, Codex, Amp, Droid, and opencode that became our daily drivers in 2025.
过去三年我一直用 LLM 辅助编程。如果你在读这篇,你大概也走过同样的演化:从往 ChatGPT 里复制粘贴代码,到 Copilot 自动补全(对我从没好用过),到 Cursor,最后是 2025 年成为日常主力的新一代 coding agent harness——Claude Code、Codex、Amp、Droid、opencode。
I preferred Claude Code for most of my work. It was the first thing I tried back in April after using Cursor for a year and a half. Back then, it was much more basic. That fit my workflow perfectly, because I'm a simple boy who likes simple, predictable tools. Over the past few months, Claude Code has turned into a spaceship with 80% of functionality I have no use for. The system prompt and tools also change on every release, which breaks my workflows and changes model behavior. I hate that. Also, it flickers.
我大部分工作偏好 Claude Code。用了一年半 Cursor 之后,四月我第一个试的就是它。那时它基础得多——完美贴合我的工作流,因为我是个喜欢简单、可预期工具的简单男孩。过去几个月,Claude Code 变成了一艘太空船,80% 的功能我用不上;系统提示词和工具还每次发布都改,破坏我的工作流、改变模型行为。我恨这个。还有,它闪屏。
I've also built a bunch of agents over the years, of various complexity. For example, Sitegeist, my little browser-use agent, is essentially a coding agent that lives inside the browser. In all that work, I learned that context engineering is paramount. Exactly controlling what goes into the model's context yields better outputs, especially when it's writing code. Existing harnesses make this extremely hard or impossible by injecting stuff behind your back that isn't even surfaced in the UI.
这些年我也造过一堆复杂度各异的 agent。比如 Sitegeist,我的小浏览器 agent,本质是个住在浏览器里的 coding agent。在所有这些工作里我学到:上下文工程(context engineering)是头等大事。精确控制什么进入模型的上下文才能产出更好的结果——写代码时尤其如此。而现有 harness 让这件事极难或不可能:它们在你背后注入 UI 上根本不显示的东西。
Speaking of surfacing things, I want to inspect every aspect of my interactions with the model. Basically no harness allows that. I also want a cleanly documented session format I can post-process automatically, and a simple way to build alternative UIs on top of the agent core. While some of this is possible with existing harnesses, the APIs smell like organic evolution. These solutions accumulated baggage along the way, which shows in the developer experience. I'm not blaming anyone for this. If tons of people use your shit and you need some sort of backwards compatibility, that's the price you pay.
说到「显示出来」:我想检查我与模型交互的方方面面——基本没有 harness 允许。我还想要一个文档清晰、可以自动后处理的会话格式,以及一个在 agent 内核上搭替代 UI 的简单方式。这些在现有 harness 里有的能做到,但那些 API 闻起来像有机演化——一路积累的包袱全体现在开发者体验上。我不怪谁:一堆人用你的东西、你得保某种向后兼容,这就是代价。
I've also dabbled in self-hosting, both locally and on DataCrunch. While some harnesses like opencode support self-hosted models, it usually doesn't work well. Mostly because they rely on libraries like the Vercel AI SDK, which doesn't play nice with self-hosted models for some reason, specifically when it comes to tool calling.
我也折腾过自托管,本地和 DataCrunch 都试过。opencode 这类 harness 虽然支持自托管模型,但通常不好用——主要因为它们依赖 Vercel AI SDK 这样的库,而它与自托管模型相处不好,工具调用(tool calling)上尤其如此。
So what's an old guy yelling at Claudes going to do? He's going to write his own coding agent harness and give it a name that's entirely un-Google-able, so there will never be any users. Which means there will also never be any issues on the GitHub issue tracker. How hard can it be?
那么,一个对着 Claude 们吼叫的老头能干什么?他要写自己的 coding agent harness,起一个完全搜不到的名字,这样永远不会有用户——也就永远不会有 GitHub issue。能有多难?
To make this work, I needed to build: pi-ai: A unified LLM API with multi-provider support (Anthropic, OpenAI, Google, xAI, Groq, Cerebras, OpenRouter, and any OpenAI-compatible endpoint), streaming, tool calling with TypeBox schemas, thinking/reasoning support, seamless cross-provider context handoffs, and token and cost tracking. pi-agent-core: An agent loop that handles tool execution, validation, and event streaming. pi-tui: A minimal terminal UI framework with differential rendering, synchronized output for (almost) flicker-free updates, and components like editors with autocomplete and markdown rendering. pi-coding-agent: The actual CLI that wires it all together with session management, custom tools, themes, and project context files.
要做成这件事,我需要造四样东西:**pi-ai**——统一 LLM API,多 provider 支持(Anthropic、OpenAI、Google、xAI、Groq、Cerebras、OpenRouter 及任何 OpenAI 兼容端点),流式、TypeBox schema 的工具调用、思考/推理支持、跨 provider 上下文无缝交接、token 与成本追踪;**pi-agent-core**——处理工具执行、校验与事件流的 agent 循环;**pi-tui**——极简终端 UI 框架,差分渲染、同步输出(几乎)不闪屏,带自动补全编辑器与 markdown 渲染等组件;**pi-coding-agent**——把它们串起来的 CLI 本体:会话管理、自定义工具、主题、项目上下文文件。
My philosophy in all of this was: if I don't need it, it won't be built. And I don't need a lot of things.
我在这整个项目里的哲学是:我不需要的就不建。而我不需要的东西很多。
pi-ai and pi-agent-core
I'm not going to bore you with the API specifics of this package. You can read it all in the README.md. Instead, I want to document the problems I ran into while creating a unified LLM API and how I resolved them. I'm not claiming my solutions are the best, but they've been working pretty well throughout various agentic and non-agentic LLM projects.
## pi-ai 与 pi-agent-core
### There. Are. Four. Ligh... APIs · 天下只有四种 API
这个包的 API 细节不啰嗦了,README.md 里都有。我想记录的是造统一 LLM API 时踩到的问题与解法。我不声称我的方案最好,但它们在各种 agentic 与非 agentic 的 LLM 项目里一直工作得不错。
There's really only four APIs you need to speak to talk to pretty much any LLM provider: OpenAI's Completions API, their newer Responses API, Anthropic's Messages API, and Google's Generative AI API.
要跟几乎所有 LLM provider 对话,其实只需要会说四种 API:OpenAI 的 Completions、他们较新的 Responses、Anthropic 的 Messages、Google 的 Generative AI。
They're all similar in features, so building an abstraction on top of them isn't rocket science. There are, of course, provider-specific peculiarities you have to care for. That's especially true for the Completions API, which is spoken by pretty much all providers, but each of them has a different understanding of what this API should do. For example, while OpenAI doesn't support reasoning traces in their Completions API, other providers do in their version of the Completions API. This is also true for inference engines like llama.cpp, Ollama, vLLM, and LM Studio.
它们特性都差不多,在上面建抽象不是火箭科学。当然有 provider 特有的怪癖要伺候——Completions API 尤其如此:几乎所有 provider 都说它,但每家对「这个 API 该干什么」的理解都不一样。比如 OpenAI 的 Completions 不支持推理痕迹,别家的 Completions 却支持;llama.cpp、Ollama、vLLM、LM Studio 这些推理引擎同理。
For example, in openai-completions.ts: Cerebras, xAI, Mistral, and Chutes don't like the store field. Mistral and Chutes use max_tokens instead of max_completion_tokens. Cerebras, xAI, Mistral, and Chutes don't support the developer role for system prompts. Grok models don't like reasoning_effort. Different providers return reasoning content in different fields (reasoning_content vs reasoning).
举 openai-completions.ts 里的实例:Cerebras、xAI、Mistral、Chutes 不喜欢 store 字段;Mistral 和 Chutes 用 max_tokens 而不是 max_completion_tokens;Cerebras、xAI、Mistral、Chutes 不支持系统提示词的 developer 角色;Grok 模型不喜欢 reasoning_effort;不同 provider 把推理内容放在不同字段(reasoning_content 还是 reasoning)。
To ensure all features actually work across the gazillion of providers, pi-ai has a pretty extensive test suite covering image inputs, reasoning traces, tool calling, and other features you'd expect from an LLM API. Tests run across all supported providers and popular models. While this is a good effort, it still won't guarantee that new models and providers will just work out of the box.
为保证所有特性在茫茫多的 provider 上真的能用,pi-ai 有一套相当庞大的测试矩阵:图像输入、推理痕迹、工具调用……你能想到的 LLM API 特性都覆盖,跑在全部受支持 provider 与主流模型上。这已是不错的努力,但仍不能保证新模型新 provider 开箱即用。
Another big difference is how providers report tokens and cache reads/writes. Anthropic has the sanest approach, but generally it's the Wild West. Some report token counts at the start of the SSE stream, others only at the end, making accurate cost tracking impossible if a request is aborted. To add insult to injury, you can't provide a unique ID to later correlate with their billing APIs and figure out which of your users consumed how many tokens. So pi-ai does token and cache tracking on a best-effort basis. Good enough for personal use, but not for accurate billing if you have end users consuming tokens through your service.
另一个大差异是 provider 怎么报 token 数与缓存读写。Anthropic 的做法最 sane,总体上则是西部荒野:有的在 SSE 流开头报 token 计数,有的只在结尾报——请求一旦中止,精确成本追踪就不可能。雪上加霜的是,你没法传一个唯一 ID 去跟它们的账单 API 对账、搞清你的哪个用户花了多少 token。所以 pi-ai 的 token 与缓存追踪是尽力而为(best-effort):个人使用够好,但如果你有终端用户经你的服务消费 token,不够做精确计费。
Special shout out to Google who to this date seem to not support tool call streaming which is extremely Google.
特别鸣谢 Google:至今似乎不支持工具调用流式传输。非常 Google。
pi-ai also works in the browser, which is useful for building web-based interfaces. Some providers make this especially easy by supporting CORS, specifically Anthropic and xAI.
pi-ai 也能跑在浏览器里,对建 Web 界面有用。有些 provider 支持 CORS 让这件事格外容易——特别是 Anthropic 和 xAI。
### Context handoff · 上下文交接 Context handoff between providers was a feature pi-ai was designed for from the start. Since each provider has their own way of tracking tool calls and thinking traces, this can only be a best-effort thing. For example, if you switch from Anthropic to OpenAI mid-session, Anthropic thinking traces are converted to content blocks inside assistant messages, delimited by <thinking></thinking> tags. This may or may not be sensible, because the thinking traces returned by Anthropic and OpenAI don't actually represent what's happening behind the scenes.
跨 provider 的上下文交接是 pi-ai 从第一天就为之设计的特性。每家 provider 跟踪工具调用与思考痕迹的方式都不同,这只能是尽力而为。比如会话中途从 Anthropic 切到 OpenAI,Anthropic 的思考痕迹会转成助手消息里的内容块、用 <thinking></thinking> 标签分隔。这未必明智——因为 Anthropic 和 OpenAI 返回的思考痕迹并不真正代表背后发生的事情。
These providers also insert signed blobs into the event stream that you have to replay on subsequent requests containing the same messages. This also applies when switching models within a provider. It makes for a cumbersome abstraction and transformation pipeline in the background.
这些 provider 还会往事件流里插签名 blob,后续携带相同消息的请求必须回放它们——同一 provider 内换模型也一样。这给后台添了一条笨重的抽象与转换管道。
I'm happy to report that cross-provider context handoff and context serialization/deserialization work pretty well in pi-ai.(代码示例见原文:先 complete 一轮 Claude,把响应推进 context.messages,再换 getModel('openai','gpt-5.1-codex') 继续问「这对吗」,再换 Gemini 问「最初的问题是什么」——GPT 会把 Claude 的思考当作 <thinking> 标签文本看到;整个 context 可 JSON.stringify 序列化,之后 parse 回来还能接着用任何模型继续。)
我很高兴地报告:跨 provider 上下文交接与序列化/反序列化在 pi-ai 里工作得相当好。(代码示例见原文:先 complete 一轮 Claude,把响应推进 context.messages,再换 getModel('openai','gpt-5.1-codex') 继续问「这对吗」,再换 Gemini 问「最初的问题是什么」——GPT 会把 Claude 的思考当作 <thinking> 标签文本看到;整个 context 可 JSON.stringify 序列化,之后 parse 回来还能接着用任何模型继续。)
### We live in a multi-model world · 我们活在多模型世界 Speaking of models, I wanted a typesafe way of specifying them in the getModel call. For that I needed a model registry that I could turn into TypeScript types. I'm parsing data from both OpenRouter and models.dev (created by the opencode folks, thanks for that, it's super useful) into models.generated.ts. This includes token costs and capabilities like image inputs and thinking support.
说到模型:我想要一种类型安全的方式来在 getModel 调用里指定模型。为此需要一个能转成 TypeScript 类型的模型注册表——我把 OpenRouter 和 models.dev(opencode 团队做的,谢了,超有用)的数据解析进 models.generated.ts,含 token 成本与图像输入/思考支持等能力位。
And if I ever need to add a model that's not in the registry, I wanted a type system that makes it easy to create new ones. This is especially useful when working with self-hosted models, new releases that aren't yet on models.dev or OpenRouter, or trying out one of the more obscure LLM providers.(代码示例见原文:手写一个 Model<'openai-completions'> 字面量——id/name/api/provider/baseUrl/reasoning/input/cost/contextWindow/maxTokens 全字段——就能把 Ollama 的 llama-3.1-8b 当一等模型用,apiKey 填 'dummy' 即可。)
如果要加注册表里没有的模型,类型系统也要让新建容易:自托管模型、models.dev/OpenRouter 还没收录的新发布、或某个冷门 provider 都用得上。(代码示例见原文:手写一个 Model<'openai-completions'> 字面量——id/name/api/provider/baseUrl/reasoning/input/cost/contextWindow/maxTokens 全字段——就能把 Ollama 的 llama-3.1-8b 当一等模型用,apiKey 填 'dummy' 即可。)
Many unified LLM APIs completely ignore providing a way to abort requests. This is entirely unacceptable if you want to integrate your LLM into any kind of production system. Many unified LLM APIs also don't return partial results to you, which is kind of ridiculous. pi-ai was designed from the beginning to support aborts throughout the entire pipeline, including tool calls.(代码示例见原文:AbortController 两秒后 abort,stream 的事件循环收到 error 事件 reason='aborted',s.result() 仍返回已生成的部分内容——stopReason='aborted' 时 response.content 就是拿到的部分输出。)
很多统一 LLM API 完全忽略「中止请求」这件事——想把 LLM 集成进任何生产系统,这完全不可接受。很多也不返回部分结果,有点荒谬。pi-ai 从第一天就设计为全管线支持中止,包括工具调用。(代码示例见原文:AbortController 两秒后 abort,stream 的事件循环收到 error 事件 reason='aborted',s.result() 仍返回已生成的部分内容——stopReason='aborted' 时 response.content 就是拿到的部分输出。)
### Structured split tool results · 结构化分流工具结果 Another abstraction I haven't seen in any unified LLM API is splitting tool results into a portion handed to the LLM and a portion for UI display. The LLM portion is generally just text or JSON, which doesn't necessarily contain all the information you'd want to show in a UI. It also sucks hard to parse textual tool outputs and restructure them for display in a UI. pi-ai's tool implementation allows returning both content blocks for the LLM and separate content blocks for UI rendering. Tools can also return attachments like images that get attached in the native format of the respective provider. Tool arguments are automatically validated using TypeBox schemas and AJV, with detailed error messages when validation fails.
另一个我在任何统一 LLM API 里都没见过的抽象:把工具结果分流——给 LLM 的部分与给 UI 显示的部分。LLM 那份通常就是文本或 JSON,未必包含 UI 想展示的全部信息;解析文本工具输出、重组成 UI 展示也极其痛苦。pi-ai 的工具实现允许同时返回「给 LLM 的内容块」与「给 UI 渲染的独立内容块」;工具还能返回图像类附件,按对应 provider 的原生格式附上。工具参数用 TypeBox schema + AJV 自动校验,校验失败给出详细错误信息。
pi-agent-core
The agent loop is the piece that handles tool execution, validation, and event streaming. At the time of writing, it comes in at 418 lines of code.
agent loop 负责工具执行、校验与事件流。写作本文时,它一共 418 行代码。
pi-tui
TUIs come in two flavors: the classic immediate mode, where you redraw everything each frame, and the retained mode, where you only update what changed. pi-tui uses retained mode with differential rendering and synchronized output to achieve (almost) flicker-free updates.
TUI 有两种口味:经典的立即模式(immediate mode,每帧重画一切)与保留模式(retained mode,只更新变化的部分)。pi-tui 用保留模式+差分渲染+同步输出,做到(几乎)不闪屏——这是对前文「Claude Code 闪屏」的直接回应。
pi-coding-agent
### Minimal system prompt · 极简系统提示词 Here's the system prompt: "You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files. Available tools: read / bash / edit / write. Guidelines: Use bash for file operations like ls, grep, find. Use read to examine files before editing. Use edit for precise changes. Use write only for new files or complete rewrites. Be concise. Show file paths clearly. Documentation: Your own documentation is at /path/to/README.md — read it when users ask about features, configuration, or setup."
系提示词全文如下:「你是一名专家级编程助手,通过读文件、执行命令、编辑代码、写新文件来帮用户完成编程任务。可用工具:read / bash / edit / write。准则:文件操作(ls、grep、find)用 bash;编辑前先用 read 查看文件;精确改动用 edit;write 只用于新文件或整体重写。回答简洁。清楚显示文件路径。文档:你自己的文档在 /path/to/README.md——用户问到功能、配置、安装时去读它。」
That's it. The only thing that gets injected at the bottom is your AGENTS.md file. Both the global one that applies to all your sessions and the project-specific one stored in your project directory. This is where you can customize pi to your liking. You can even replace the full system prompt if you want to. Compared to, for example, Claude Code's system prompt, Codex's system prompt, or opencode's model-specific prompts.
就这些。底部唯一注入的是你的 AGENTS.md——全局的(所有会话生效)与项目目录里的项目级各一份。这就是你定制 pi 的地方;想要的话连整个系统提示词都能换掉。对比一下 Claude Code 的系统提示词、Codex 的系统提示词、或 opencode 按模型定制的提示词(长度量级完全不同)。
You might think this is crazy. In all likelihood, the models have some training on their native coding harness. So using the native system prompt or something close to it like opencode would be most ideal. But it turns out that all the frontier models have been RL-trained up the wazoo, so they inherently understand what a coding agent is. There does not appear to be a need for 10,000 tokens of system prompt, as we'll find out later in the benchmark section, and as I've anecdotally found out by exclusively using pi for the past few weeks.
你可能觉得这疯了。模型大概率在自家 coding harness 上有过训练,用原生系统提示词(或 opencode 那种接近原生的)理应最优。但事实证明前沿模型全都被 RL 往死里训过,天生就懂什么是 coding agent——看起来不需要 10,000 token 的系统提示词。基准部分后面会给证据,过去几周我只用 pi 干活的体感也一样。
### Minimal toolset · 极简工具集 Here are the tool definitions: read(读文件内容,支持文本与图片,默认前 2000 行,大文件用 offset/limit)/ write(写文件,自动建父目录)/ edit(精确文本替换,oldText 必须逐字匹配,可一次多个不连续编辑)/ bash(在当前目录执行命令,返回 stdout+stderr,可选超时)。另有只读工具(grep/find/ls)默认关闭,需要限制 agent 时再开。
工具定义如下:**read**(读文件内容,支持文本与图片,默认前 2000 行,大文件用 offset/limit)、**write**(写文件,自动建父目录)、**edit**(精确文本替换,oldText 必须逐字匹配,可一次多个不连续编辑)、**bash**(在当前目录执行命令,返回 stdout+stderr,可选超时)。另有只读工具(grep/find/ls)默认关闭,想限制 agent 不许改动时再启用。
As it turns out, these four tools are all you need for an effective coding agent. Models know how to use bash and have been trained on the read, write, and edit tools with similar input schemas. Compare this to Claude Code's tool definitions or opencode's tool definitions. Notably, Codex's tool definitions are similarly minimal to pi's. pi's system prompt and tool definitions together come in below 1000 tokens.
事实证明,这四个工具就够一个有效率的 coding agent 了。模型会用 bash,也在类似输入 schema 的 read/write/edit 上被训练过。对比 Claude Code 或 opencode 的工具定义——值得注意的是 Codex 的工具定义与 pi 同样极简。**pi 的系统提示词加工具定义,合计不到 1000 token。**
### YOLO by default · 默认 YOLO pi runs in full YOLO mode and assumes you accept that. The trifecta of capabilities that matter for security — reading data, executing code, and network access — cannot be reasonably sandboxed on a local machine without a container. Everything on your machine is accessible to your agent, and prompt injections are unpreventable while these capabilities are in place. Since we cannot solve this trifecta, pi just gives in. Everybody is running in YOLO mode anyways to get any productive work done, so why not make it the default and only option? By default, pi has no web search or fetch tool. However, it can use curl or read files from disk, both of which provide ample surface area for prompt injection attacks. If you're uncomfortable with full access, run pi inside a container or use a different tool if you need (faux) guardrails.
pi 以全 YOLO 模式运行,并默认你接受这一点。安全上要紧的能力三件套——读数据、执行代码、网络访问——在没有容器的情况下无法在本地机器上合理沙箱化:机器上的一切对 agent 可见,只要这三样在,提示注入就防不住。既然解决不了这个三难,pi 干脆投降:反正人人都在 YOLO 模式下干活才能有产出,不如把它设为默认且唯一的模式。pi 默认没有网页搜索或抓取工具,但可以用 curl、可以读磁盘文件——两者都给提示注入提供了充足的攻击面。不放心全权限的话,把 pi 关进容器里跑,或者换一个有(装样子的)护栏的工具。
### No built-in to-dos · 无内置待办 pi does not and will not support built-in to-dos. In my experience, to-do lists generally confuse models more than they help. They add state that the model has to track and update, which introduces more opportunities for things to go wrong. If you need task tracking, make it externally stateful by writing to a file: # TODO.md with checkboxes. The agent can read and update this file as needed. Simple, visible, and under your control.
pi 不会也永远不会支持内置待办。以我的经验,待办清单给模型添的乱多于帮助:它引入模型要跟踪与更新的状态,制造更多出错机会。需要任务跟踪的话,把状态外置到文件:# TODO.md 加复选框。agent 按需读写这个文件——简单、可见、在你掌控中。
### No plan mode · 无计划模式 pi does not and will not have a built-in plan mode. Telling the agent to think through a problem together with you, without modifying files or executing commands, is generally sufficient. If you need persistent planning across sessions, write it to a file: # PLAN.md. The agent can read, update, and reference the plan as it works. Unlike ephemeral planning modes that only exist within a session, file-based plans can be shared across sessions, and can be versioned with your code. Funnily enough, Claude Code now has a Plan Mode that's essentially read-only analysis, and it will eventually write a markdown file to disk. And you can basically not use plan mode without approving a shit ton of command invocations, because without that, planning is basically impossible. The difference with pi is that I have full observability of everything. I get to see which sources the agent actually looked at and which ones it totally missed. I get to see the markdown file immediately. I can edit it collaboratively with the agent. I need observability for planning and I don't get that with Claude Code's plan mode. If you must restrict the agent during planning, you can specify which tools it has access to via the CLI: pi --tools read,grep,find,ls.
pi 不会也永远不会内置计划模式。让 agent 与你一起把问题想清楚(不改文件、不执行命令)通常就够了。需要跨会话的持久计划,写到文件:# PLAN.md。agent 干活时读它、更新它、引用它——不同于只活在会话里的临时计划模式,文件计划可跨会话共享、可随代码版本化。有意思的是,Claude Code 现在也有了个 Plan Mode,本质就是只读分析、最后往磁盘写个 markdown 文件;而且不用批准一大堆命令调用基本没法用它做计划。pi 的差别在于我对一切有完整可观察性:agent 实际看了哪些源、完全漏了哪些,我立刻能看到 markdown 文件、能与 agent 协作编辑它。做计划需要可观察性,Claude Code 的计划模式给不了。真要在规划期限制 agent,CLI 上指定工具白名单即可:pi --tools read,grep,find,ls。
### No MCP support · 不支持 MCP pi does not and will not support MCP. I've written about this extensively, but the TL;DR is: MCP servers are overkill for most use cases, and they come with significant context overhead. Popular MCP servers like Playwright MCP (21 tools, 13.7k tokens) or Chrome DevTools MCP (26 tools, 18k tokens) dump their entire tool descriptions into your context on every session. That's 7-9% of your context window gone before you even start working. Many of these tools you'll never use in a given session. The alternative is simple: build CLI tools with README files. The agent reads the README when it needs the tool, pays the token cost only when necessary (progressive disclosure), and can use bash to invoke the tool. This approach is composable (pipe outputs, chain commands), easy to extend (just add another script), and token-efficient. I maintain a collection of these tools at github.com/badlogic/agent-tools. If you absolutely must use MCP servers, look into Peter Steinberger's mcporter tool that wraps MCP servers as CLI tools.
pi 不支持也永远不会支持 MCP。我详细写过这个,TL;DR:MCP server 对多数用例是杀鸡用牛刀,还带着可观的上下文开销。流行的 MCP server 比如 Playwright MCP(21 个工具,13.7k token)或 Chrome DevTools MCP(26 个工具,18k token)把全部工具描述倒进每次会话的上下文——还没开工,上下文窗口的 7-9% 就没了,而这些工具很多你整个会话都不会用。替代方案很简单:造带 README 的 CLI 工具。agent 需要时才读 README,只在必要时付 token 成本(渐进披露),用 bash 调用工具。这个路子可组合(管道输出、链式命令)、易扩展(再加一个脚本就是)、token 高效。我的这类工具集维护在 github.com/badlogic/agent-tools。实在非用 MCP 不可,看 Peter Steinberger 的 mcporter——把 MCP server 包成 CLI 工具。
### No background bash · 无后台 bash pi's bash tool runs commands synchronously. There's no built-in way to start a dev server, run tests in the background, or interact with a REPL while the command is still running. This is intentional. Background process management adds complexity: you need process tracking, output buffering, cleanup on exit, and ways to send input to running processes. Claude Code handles some of this with their background bash feature, but it has poor observability (a common theme with Claude Code) and forces the agent to track running instances without providing a tool to query them. In earlier Claude Code versions, the agent forgot about all its background processes after context compaction and had no way to query them, so you had to manually kill them. This has since been fixed. Use tmux instead. How's that for observability? The same approach works for long-running dev servers, watching log output, and similar use cases. And if you wanted to, you could hop into that tmux session and co-debug with the agent. There's simply no need for background bash. Claude Code can use tmux too, you know. Bash is all you need.
pi 的 bash 工具同步执行命令。没有内置方式起 dev server、后台跑测试、或在命令还跑着时与 REPL 交互——这是故意的。后台进程管理增加复杂度:要进程跟踪、输出缓冲、退出清理、给运行中进程送输入。Claude Code 的后台 bash 处理了一部分,但可观察性差(Claude Code 的常见主题),还逼 agent 跟踪运行中的实例却不给查询工具——早期版本里 agent 在上下文压缩后忘光自己起的后台进程、也没法查,你得手动杀(后来修了)。用 tmux 就好。这可观察性如何?长跑 dev server、盯日志输出都同理;你甚至可以跳进那个 tmux 会话与 agent 共同调试。根本不需要后台 bash——Claude Code 也是可以用 tmux 的,你知道的。Bash is all you need。
### No sub-agents · 无子代理 pi does not have a dedicated sub-agent tool. When Claude Code needs to do something complex, it often spawns a sub-agent to handle part of the task. You have zero visibility into what that sub-agent does. It's a black box within a black box. Context transfer between agents is also poor. The orchestrating agent decides what initial context to pass to the sub-agent, and you generally have little control over that. If the sub-agent makes a mistake, debugging is painful because you can't see the full conversation.
pi 没有专门的子代理工具。Claude Code 处理复杂任务时常派一个子代理负责一部分。你对那个子代理干的事零可见——黑盒里的黑盒。agent 间上下文传递也很差:编排 agent 决定给子代理传什么初始上下文,你基本没法控制。子代理出错时调试很痛苦,因为你看不到完整对话。
But more importantly: fix your workflow, at least the ones that are all about context gathering. People use sub-agents within a session thinking they're saving context space, which is true. But that's the wrong way to think about sub-agents. Using a sub-agent mid-session for context gathering is a sign you didn't plan ahead. If you need to gather context, do that first in its own session. Create an artifact that you can later use in a fresh session to give your agent all the context it needs without polluting its context window with tool outputs. That artifact can be useful for the next feature too, and you get full observability and steerability, which is important during context gathering. Because despite popular belief, models are still poor at finding all the context needed for implementing a new feature or fixing a bug. I attribute this to models being trained to only read parts of files rather than full files, so they're hesitant to read everything. Which means they miss important context and can't see what they need to properly complete the task.
但更重要的:修你的工作流——至少那些全为收集上下文而设的。人们在会话中途用子代理,以为省了上下文空间——对,但这是思考子代理的错误方式。**会话中途派子代理收集上下文,是你没提前规划的信号。**要收集上下文,先为它单独开一个会话,产出一份 artifact,之后在新会话里把 artifact 喂给 agent——所需的上下文全在,又不用工具输出污染它的上下文窗口。这份 artifact 对下个功能也有用,而且你全程可观察、可掌舵——收集上下文时这很重要。因为与流行观点相反,模型在「找齐实现新功能或修 bug 所需的全部上下文」上仍然很差。我把它归因于模型被训练成只读文件片段而非全文,所以不敢把东西读全——于是漏掉重要上下文,看不到完成任务真正需要什么。
I'm not dismissing sub-agents entirely. There are valid use cases. My most common one is code review: I tell pi to spawn itself as a sub-agent via bash to do a code review, and it gets the outputs.(原文附 slash command 模板:用 pi --print 带上 code review 提示词把自己 spawn 成子代理,还能放进 tmux 全程观察。)
我没有全盘否定子代理——有效用例是有的。我最常用的是 code review:让 pi 经 bash 把自己 spawn 成子代理去审查,拿回输出。(原文附 slash command 模板:用 pi --print 带上 code review 提示词把自己 spawn 成子代理,还能放进 tmux 全程观察。)
Benchmarks · 基准
I make a lot of grandiose claims, but do I have numerical proof that all the contrarian things I say above actually work? I have my lived experience, but that's hard to transport in a blog post and you'd just have to believe me. So I created a Terminal-Bench 2.0 test run for pi with Claude Opus 4.5 and let it compete against Codex, Cursor, Windsurf, and other coding harnesses with their respective native models. Obviously, we all know benchmarks aren't representative of real-world performance, but it's the best I can provide you as a sort of proof that not everything I say is complete bullshit.
我说了很多豪言,但有数字证据证明这些唱反调的主张真的有效吗?我有切身体验,但那很难装进博文里,你只能信我。所以我给 pi 跑了一轮 Terminal-Bench 2.0(配 Claude Opus 4.5),让它对阵 Codex、Cursor、Windsurf 等各自原生模型的 coding harness。当然我们都知道基准不代表真实表现——但这是我能提供的最好证明:我说的不全是一派胡言。
I performed a complete run with five trials per task, which makes the results eligible for submission to the leaderboard. I also started a second run that only runs during CET because I found that error rates (and consequently benchmark results) get worse once PST goes online.
我做了完整跑批:每任务五次试验,结果够格提交排行榜。我还开了第二轮只在 CET 时段跑的——因为我发现一旦 PST 上线,错误率(从而基准成绩)会变差。
Also note the ranking of Terminus 2 on the leaderboard. Terminus 2 is the Terminal-Bench team's own minimal agent that just gives the model a tmux session. The model sends commands as text to tmux and parses the terminal output itself. No fancy tools, no file operations, just raw terminal interaction. And it's holding its own against agents with far more sophisticated tooling and works with a diverse set of models. More evidence that a minimal approach can do just as well.
另外注意排行榜上 Terminus 2 的名次。Terminus 2 是 Terminal-Bench 团队自家的极简 agent:只给模型一个 tmux 会话——模型以文本发命令给 tmux、自己解析终端输出。没有花哨工具、没有文件操作,就是裸终端交互。它在与工具精巧得多的 agent 们同台时照样立得住,还能配多种模型——极简路线同样可行的又一证据。
In summary · 总结
Benchmark results are hilarious, but the real proof is in the pudding. And my pudding is my day-to-day work, where pi has been performing admirably. Twitter is full of context engineering posts and blogs, but I feel like none of the harnesses we currently have actually let you do context engineering. pi is my attempt to build myself a tool where I'm in control as much as possible.
基准结果很欢乐,但真正的证明在布丁里——而我的布丁是我的日常工作,pi 在其中表现出色。Twitter 上全是上下文工程的帖子和博客,但我觉得现有 harness 没有一个真让你做上下文工程。pi 是我为自己造一把尽量由我掌控的工具的尝试。
I'm pretty happy with where pi is. There are a few more features I'd like to add, like compaction or tool result streaming, but I don't think there's much more I'll personally need. Missing compaction hasn't been a problem for me personally. For some reason, I'm able to cram hundreds of exchanges between me and the agent into a single session, which I couldn't do with Claude Code without compaction.
我对 pi 现在的状态相当满意。还有几个功能想加,比如压缩(compaction)或工具结果流式,但我不觉得个人还需要更多。没有压缩对我来说倒一直不是问题——出于某种原因,我能在单个会话里塞进我和 agent 的几百轮交互;这在 Claude Code 里不靠压缩是做不到的。