I've been coding with Pi Agent for a while now. What sold me on it is how clean the context is: the system prompt is short, and you're pretty much in control of everything the model sees. The default prompt is only about 2k tokens. Compare that to 10k+ for Codex and 20k+ for Claude Code, and it's a big difference.
Then I kept installing extensions, and that advantage slowly disappeared. Don't get me wrong, Pi's extension ecosystem is great. Todos, asking questions, web search, subagents: someone has built pretty much anything you'd want. The problem is that a lot of extensions come with really long tool descriptions, and those descriptions get sent in full with every single request. Before you've even said anything, thousands or even tens of thousands of tokens are already gone. That costs money, and it also eats into the model's attention for nothing. Even a model with a 1M context window only has a "smart zone" of about 200k to 300k. The longer your tool descriptions, the less room the model has left for actual work.
So I made trimmed-down versions of the extensions I use most. I call them lean versions. At first they were just for me, but I figured other people might find them useful, so I open-sourced them. The idea is simple: all the features and logic come straight from upstream, untouched. I only change what the model sees, meaning the tool descriptions and the parameter schemas.
Here's what that got me:
| Extension | Lean | Upstream | Saved | Reduction |
|---|---|---|---|---|
pi-web-access-lean |
152 | 2,953 | 2,801 | 94.9% |
rpiv-ask-user-question-lean |
215 | 1,258 | 1,043 | 82.9% |
rpiv-todo-lean |
248 | 904 | 656 | 72.6% |
pi-subagents-lean |
268 | 8,540 | 8,272 | 96.9% |
pi-hashline-edit-pro-lean |
537 | 2,040 | 1,503 | 73.7% |
| Total | 1,420 | 15,695 | 14,275 | 91.0% |
Below I'll walk through how I did it, using the actual upstream text for comparison.
Why I felt safe cutting this much
I don't think it's fair to blame extension authors for long prompts. A couple of years ago, models really did need them. Without a "MUST" in there, the model just wouldn't call the tool. If you didn't spell out every step, it would go off and do its own thing.
Models are different now, and Anthropic has said so pretty bluntly in a few pieces this year.
One is Effective context engineering for AI agents. It says models, like people, have an "attention budget," and every token in the context spends a bit of it. So what you're looking for is:
the smallest possible set of high-signal tokens that maximize the likelihood of some desired outcome
In other words, get the job done with the fewest tokens that actually matter. There's also a line near the end that I really like:
We're already seeing that smarter models require less prescriptive engineering.
The smarter the model, the less you need to hold its hand through every step.
The other is Claude's official Prompting best practices. It notes that Claude Opus 4.5 and 4.6 follow system prompts more closely than earlier models. So all the strong language people used to write to stop models from slacking off now makes them overuse tools instead. Their fix:
The fix is to dial back any aggressive language. Where you might have said "CRITICAL: You MUST use this tool when...", you can use more normal prompting like "Use this tool when...".
On Opus 5 they're even more direct. The model already checks its own work, so the "verify before you finish" instruction you carried over from an old prompt just makes it keep checking forever, burning tokens and time. Their advice:
remove these instructions rather than rewriting them.
Don't rewrite them. Just delete them.
So my take is that a lot of extension prompts were written for the previous generation of models. Today, some of that text is outdated, and some of it actually works against how newer models behave. Swapping it for something shorter and clearer doesn't make things worse.
Method 1: Write shorter tool descriptions
This is easier to show than to explain, so let's look at the real text.
todo: 904 → 248
Upstream is juicesharp's rpiv-todo. What it shows the model comes in three parts: a tool description, 8 usage rules, and a description for each parameter. Read them side by side and there's a lot of repetition.
Here's the upstream text next to my lean version. The parts in red are the repeats:
Take the task statuses. They're explained three times: in the tool description, in rule 4, and in the status parameter description. But status is already an enum. The four values are right there in the schema, and the model gets it the first time. It doesn't need to read a prose version three more times.
Same with activeForm: it gets explained three separate times.
Some of the rules read like instructions for an intern who's bound to mess up. Mark a task in_progress before you start, in all-caps BEFORE. Mark it completed as soon as you're done, in all-caps IMMEDIATELY. That's exactly the kind of tone Anthropic says to dial back.
Rule 1 says to use todo for 3+ steps, and I don't really agree with that either. How many steps justify a todo list is a personal call. Hardcode it in the tool and users can't change it. And if someone has their own rule in AGENTS.md, the two end up fighting each other.
Here's everything the model sees in my lean version:
# Tool description
Task tracker. action is required: create|update|list|get|delete|clear;
create needs subject; update/get/delete need id.
# Rules (just one)
Use for multi-step tasks. update needs changed fields; list accepts
status/includeDeleted; clear removes all; create supports blockedBy;
update supports addBlockedBy/removeBlockedBy; keep one in_progress
and complete tasks promptly.
I did four main things.
First, I removed all the parameter descriptions but kept the schema structure. Field names, types, and enums are all still there. Names like subject, owner, and includeDeleted tell you what they're for at a glance.
Second, every point is made once. Statuses, activeForm, and dependencies all get folded into one sentence about which action needs which parameters.
Third, if code can handle it, the prompt doesn't nag about it. The model sometimes forgets to pass action, so the code makes a guess: if there's an id plus fields to change, it's an update; if there's a subject, it's a create. Rather than telling the model over and over to always pass action, I just fill it in when it forgets.
Fourth, decisions like "how many steps before you use this" go back to the user. I changed "3+ steps" to "multi-step tasks," and you set the actual threshold in your own AGENTS.md. Mine says to only use todo for tasks I expect to take 5 or more steps.
ask-user-question: 1,258 → 215
This one is also by juicesharp: rpiv-ask-user-question. It lets the model ask you multiple-choice questions, and it's really handy.
The wildest part is that a single UI detail gets explained five times. The detail: the UI automatically adds a "Type something." row under every question so you can type your own answer, which means the model shouldn't add its own "Other" option. Take a look:
Mixed in there is also a note that while you're typing, this row expands to the full width of the pane so it isn't squeezed into the narrow options column. That's a UI note for humans. Knowing it doesn't change how the model writes questions.
Length and count limits like MAX 60 CHARACTERS are spelled out in prose too, even though the schema already enforces them with maxLength, minItems, and maxItems. If the model goes over, validation rejects it anyway.
At the end of the tool description there's also a long section on how previews render: monospace font, split view, options on the left and preview on the right... Again, that's all for humans.
My lean version:
# Tool description
Ask 1-4 structured questions when a required user decision is unclear.
# Rules (just one)
Each question needs 2-4 options. Put recommendations first with (Recommended);
never add Other or Type something. Use multiSelect only for nonexclusive choices
and preview only for useful single-select visual comparisons.
I kept everything that actually affects how the model asks questions: how many options, put the recommended one first, don't add Other yourself, when to use multi-select, and when to add a preview. Everything else is either already handled by the schema or was never meant for the model in the first place.
Complexity doesn't disappear, it just moves
At this point I was reminded of something from software engineering called Tesler's Law: complexity can't be removed, only moved.
A wordy prompt is really just complexity pushed onto the model. On every request, it has to reread all those rules and remember them. Move that into code, and the author writes it once and never has to think about it again.
Of course, not everything can be moved. I sort it into three buckets:
- If code can decide it, let code handle it. Types, enums, and lengths go in the schema. If a parameter is missing but can be inferred, the code fills it in. If something's wrong, validation rejects it.
- If only the model can decide it, keep it in the prompt, but keep it short. Things like when to use the tool, or putting the recommended option first. Code can't make those calls, so one clear sentence is enough.
-
If it's rarely used, move it out of the way and look it up when needed. That's the
helpop I'll get to below.
One more thing I realized: error messages are prompts that only show up when needed. A rule in the prompt costs you on every request. An error only appears when the model actually gets something wrong, and it shows up at exactly the moment the model needs it. So instead of writing "label must be 60 characters or fewer" in the prompt, just have validation reply "label is over 60 characters, shorten it."
That said, fallbacks need some restraint: only guess when the guess can't be wrong. Like in todo, an id plus fields to change can only mean update. If it's ambiguous, don't guess. A clear error beats a wrong guess that quietly runs.
If you write extensions
Once you've written your tool descriptions, run through these questions:
- Does the same sentence show up once in the tool description, once in the rules, and once in a parameter description?
- Are enums, lengths, or counts already in the schema also written out in prose?
- Is there any UI description that helps humans but not the model?
- Is there any strong language like MUST, NEVER, or all caps?
- Did you hardcode something the user should decide?
- For mistakes the model makes often, could code catch them instead of the prompt repeating warnings?
- Are rarely used tools merged behind one entry point? (Method 2)
- Does the tool list stay the same for the whole session? (Method 3)
Method 2: Merge tools, and don't put rarely used ones front and center
Shorter descriptions save some tokens, but the bigger cost is usually having too many tools. Every extra tool brings its own name, description, and schema.
subagent: 8,540 → 268
tintinweb's pi-subagents registers 4 tools, and SubagentWorkflow alone takes 5,611 tokens. pi-web-access, which I'll get to next, has the same shape:
I merged all 4 into a single subagent tool, with an op parameter to choose between run, result, steer, workflow, and help.
subagent: Run or inspect subagents/workflows through one tool;
use help for advanced parameters.
The most common parameters (the task, a label, which kind of subagent, whether to run it in the background) go right in the schema. Rarely used advanced options get passed in a JSON string. If the model really needs those advanced options, it calls op: help once and gets the full upstream docs.
I didn't touch a single line of the subagent logic. All I changed was the entry point the model sees.
web access: 2,953 → 152
Nico Bailon's pi-web-access also has 4 tools: web_search, source_check, fetch_content, and get_search_content. With all of them enabled, that's 2,953 tokens. I merged them into one web_access tool too, using op for search, fact-checking, fetching pages, and retrieving results, with advanced parameters behind help. It stays loaded the whole time and only costs 152 tokens.
Why I think this is fine
Put simply: keep the stuff you use often in plain sight, and put the rest in the back where you can dig it out when needed. The cost is one extra help call when the model needs an obscure feature. What you get back is those tokens saved on every single request. The less a feature gets used, the better the trade.
Anthropic's Writing effective tools for AI agents makes the same point:
More tools don't always lead to better outcomes.
Too many tools or overlapping tools can also distract agents from pursuing efficient strategies.
And the context engineering article I mentioned earlier puts it even more bluntly:
If a human engineer can't definitively say which tool should be used in a given situation, an AI agent can't be expected to do better.
If a person can't tell which tool to use, don't expect the model to pick the right one.
Method 3: Once the tool list is set, leave it alone
When people hear "don't keep rarely used tools loaded all the time," a common first reaction is: fine, just add them when they're needed.
That's exactly what the pi-web-access author did. Since version 0.31, it only loads a tiny web_enable tool at startup. When the model wants to go online, it calls that first, which then turns on the other 4 tools. That does save tokens at startup, but turning them on usually costs more.
The problem is prompt caching. Most providers cache by prefix match, and tool definitions usually sit at the very start of the request. Change the tool list mid-conversation and the prefix no longer matches, so the whole cached context before it gets billed again at full price. The longer the conversation, the more that one switch costs you.
In Pi 0.87, only providers that explicitly declare support will append new tools later in the conversation. Everywhere else, the tool list at the top of the request gets rewritten. So my lean version simply blocks web_enable and doesn't let upstream switch tools mid-session. web_access stays loaded from start to finish. At 152 tokens, keeping it loaded doesn't hurt.
So if you want to save tokens on rarely used tools, my advice is to use Method 2: merge them into one tool instead of adding and removing tools mid-conversation.
Pi itself has a similar situation. By default, the system prompt includes a section about Pi's own docs (a few hundred tokens), but you only need it when you're asking about Pi itself. Rob Zolkos made pi-slim, which removes that section normally and brings it back with /pi when you want it, without touching the tool list at all. The original no longer works on Pi 0.87.1, so I forked it and fixed it as pi-docs-slim.
That said, weaker models still need more guidance
Everything above is about today's strongest models. If you're using a weaker model or running a local one, more detailed prompts are still the safer bet. Anthropic's article leaves room for this too:
Note that minimal does not necessarily mean short.
Lean doesn't mean as short as possible.
I happen to have an example of this. billion-context-pi is a context management extension that compresses earlier parts of the conversation into summaries and pulls the details back when needed. I'd made a lean prompt version for it too. When the author, ranxianglei, saw it, he merged it straight into the official version. In his words, he "kept 90%" of it, and turned it into a built-in prompt pack called lean. You turn it on with one line of config:
{ "compress": { "promptPack": "lean" } }
He also deliberately kept one extra howToCompress rule in the system prompt. In his words, it "fixed some model compression hallucination issues," which basically means some models make things up when they compress a conversation.
I think that's exactly the right call: strong models get by with the short version, and weaker models still get the few lines they need. Now that the official version exists, I've stopped maintaining my own.
Make your own lean version
I might not have a lean version of the extensions you use. That's fine, because this is a job you can hand to an agent.
My approach is simple: create a new package, install the upstream extension as a dependency, and wrap it. When upstream registers its tools, the wrapper catches them first, swaps in the trimmed descriptions and schemas, and then registers them. When a tool actually runs, it still calls the original upstream code. No upstream code gets copied, and node_modules stays untouched.
Here are two prompts, one for building a lean version and one for maintaining it. Swap in the package names and send them to your agent.
Building a lean version
Make a lean version of the Pi extension <upstream package>, named <upstream package>-lean.
Goal: keep every feature exactly the same as upstream. Only shorten the tool descriptions and schemas the model reads on every request.
Steps:
1. Install upstream as a dependency and pin the version. Don't copy upstream code, and don't modify node_modules.
2. Read upstream first. List every tool it registers: its description, promptSnippet, promptGuidelines, and parameter schema, plus how many characters each one takes.
3. Wrap the pi object passed to upstream in a Proxy. Intercept registerTool, collect the upstream tools, swap in the lean descriptions and schemas, then register them. execute calls upstream directly.
4. Trimming rules:
- Say each thing once.
- Don't repeat types, enums, lengths, or counts in prose if the schema already expresses them.
- Remove parameter descriptions but keep field names, types, and constraints. Only keep a short description when a field name doesn't make its purpose clear.
- Remove UI notes that only matter to humans.
- Don't use MUST, NEVER, or all caps.
- Don't hardcode things the user should decide, like "only use this for N+ steps."
- Keep any rule that affects how the model calls the tool.
5. If upstream has several tools, especially rarely used ones, merge them into one tool: use an op parameter to choose the operation, put common parameters in the schema, and pass rare ones as a JSON string. Add op: help, which returns the full upstream description and parameters for the matching tool.
6. The tool list must not change mid-session. If upstream calls setActiveTools, or has a loader tool that turns on other tools when called, block it.
7. For call mistakes the model makes often, fill in the missing piece in code before validation, but only when the guess can't be wrong. If it's ambiguous, return a clear error.
8. Write tests: every tool or op reaches upstream, help returns the full parameters, the fill-in logic doesn't misfire, and the tool list can't change mid-session.
9. Measure how many tokens the upstream and lean tools each take, and put the numbers in the README. At the top of the README, say which project this is based on and who the original author is, and keep the original license.
10. Write a MAINTENANCE.md covering: the matching upstream version, what was trimmed in each tool, which upstream internals this depends on (tool names, validation order, loader tools, and so on), and how to upgrade.
Before you start building, show me the upstream tool list and how you plan to trim it.
That last line, "show me first," really matters. Agents sometimes get it wrong about which rules can go and which tools should be merged. Checking the plan up front saves a lot more time than redoing the work later.
Keeping up with upstream
The real work with a lean version is maintenance: every time upstream ships a new version, you have to follow along. I pin the upstream version so nothing upgrades automatically, and I go through each update by hand. The process is pretty much always the same, so you can hand it to an agent too:
Sync <lean package> to the latest version of upstream <upstream package>.
1. Read MAINTENANCE.md first to understand the current upstream version and adaptation points.
2. Check the latest upstream version. Compare the code and changelog between the two versions, and list: added or removed tools, changes to parameters and defaults, behavior changes, and whether the internals recorded in MAINTENANCE.md still hold.
3. If only the version number changed and the code didn't, just upgrade the dependency.
4. For new parameters or tools, add them to the lean version using the same trimming rules: common ones go in the schema, rare ones go in help. If upstream changed behavior, update the descriptions so they still match what the tool does.
5. Run the tests, re-measure the tokens, and update the README and MAINTENANCE.md.
6. Summarize the changes for me, and only publish after I confirm.
This is how I maintain my own lean versions. Most upstream updates only change internal logic, so upgrading the dependency is all it takes. When I do have to change the adapter code, it's usually because upstream changed a tool name or parameters.
Thanks to these authors
At the end of the day, my lean versions are just a wrapper around the original extensions. All the real work is done by code the original authors wrote. Thank you to:
- juicesharp: rpiv-todo and rpiv-ask-user-question
- tintinweb: pi-subagents
- Nico Bailon: pi-web-access
- YuGiMob: pi-hashline-edit-pro, along with its predecessor, RimuruW's pi-hashline-edit
- Rob Zolkos: pi-slim
- ranxianglei: billion-context-pi
If you're the author of one of these extensions and you like any of these changes, feel free to take them. billion-context-pi already shows it works: offering the lean version as an option inside the original is totally doable.
My setup
My whole setup lives in my-lean-pi-setup. I handle each part of the context separately:
| Part | What I use | What it does |
|---|---|---|
| Tool descriptions | The 5 lean extensions | What this whole post is about |
| Code editing |
pi-hashline-edit-pro-lean (one of the 5 above) |
Edits by anchor, so no copying old code and no rereading after edits |
| Base prompt | pi-docs-slim |
Removes the Pi docs section included by default |
| Command output | RTK + pi-rtk-optimizer
|
Filters long terminal output |
| Current context | Headroom / noheadroom | Compresses live tool output and conversation |
| Conversation history |
billion-context-pi (official, with lean on) |
Compresses old conversation into summaries and retrieves details when needed |
| Usage | pi-context-view |
Shows how many tokens each part takes |
I want to give pi-hashline-edit-pro a special shout-out here. I haven't said much about it yet, but I use it every day.
It replaces Pi's built-in edit with a set of anchor-based editing tools. When read loads a file, every line gets a 4-letter anchor. To edit code, the model just says "replace everything from this anchor to that anchor with these lines."
The token savings come on the output side. With the original edit tool, the model first has to output the old code (old text) exactly as written, then output the new code (new text). If you're changing a big chunk of code, you have to copy that whole chunk first. hashline uses anchors to find the spot, so the model only gives the start and end anchors and then outputs the new code.
The trade-off is that every line from read now carries an anchor, so input tokens go up a little. But output tokens usually cost several times more than input. Spend a little more on input, save a big chunk of output, and on price it's clearly worth it.
The anchors are also checked. If the file changed in the meantime, it refuses to write instead of editing the wrong spot. And if an edit does go wrong, you can undo it in one step.
Upstream is YuGiMob's pi-hashline-edit-pro. My lean version uses the same approach as the others: anchor validation, batch edits, undo, and everything else come straight from upstream. I only shortened the descriptions of the 5 tools and removed the parameter descriptions, taking it from 2,040 to 537 tokens. The tool descriptions only keep the few things the model gets wrong most often: copy anchors from read output instead of making them up, and replacement_lines is one string per line, with [] meaning delete.
Here's how to install the 5 lean extensions:
pi install npm:@ssk_dev/pi-web-access-lean
pi install npm:@ssk_dev/rpiv-todo-lean
pi install npm:@ssk_dev/rpiv-ask-user-question-lean
pi install npm:@ssk_dev/pi-subagents-lean
pi install npm:pi-hashline-edit-pro-lean
I'd install pi-context-view first and use /context to see where your tokens are going right now. Then switch to the lean versions one at a time and compare before and after.





Top comments (0)