Course · Pi · Your first extension
Your first extension
Last chapter left the cheap model doing stash’s grunt work for a fraction of what the frontier model billed - and left you typing its instructions fresh, by hand, every time you wanted it to do something more specific than “clean this up.” That’s the gap this chapter closes: instead of retyping tighter instructions into every session, you give Pi a new, permanent move it can just call.
Every chapter so far has spent the context window on purpose instead of by accident: watching a turn run, reading the real prompt instead of trusting a summary, routing work to the model and thinking depth that actually fit it. This chapter spends the window on something new, and the trade is different in kind, not degree - every tool you register earns a line in the system prompt, sent on every single turn for the rest of that extension’s life, whether you use it that session or not. Building a capability is cheap. Owning what it costs forever is the actual chore, and it’s this chapter’s slice of the one thing every chapter in this course is fighting: the window is finite, and defaults - your own defaults, this time - spend it whether you’re watching or not.
stash has a concrete case of exactly that chore, and config can’t reach it: sometimes the readability extractor mangles a saved page - it grabs the cookie-consent banner instead of the article, or chokes on a paywall’s teaser text - and fixing it means re-running the fetch-and-extract pipeline against that one URL. Right now that’s a manual dance: find the entry in the database, remember the internal function name, open a Python shell, call it by hand. You’ve done it four times this week already. It’s exactly the kind of repeatable, well-defined chore that deserves to become a first-class move instead of a thing you re-derive from memory each time.
Config can’t build you a new capability. This is where Pi’s actual differentiator shows up: you don’t wait for the maintainers to ship a “re-extract” feature, and you don’t reach for an MCP server to bridge to some external system - stash doesn’t have one, and the chore lives entirely inside the repo you already have. You write a small TypeScript file, and Pi picks it up.
You’ll build the three extensions below by hand, because seeing the shape once is worth more than reading about it. From the next chapter on, though, you’ll mostly describe the extension you want and let Pi write the file - the point of building these yourself is knowing enough to review and correct what it generates.
What you’ll build
Section titled “What you’ll build”- Anatomy of an extension - the factory function every extension exports, where Pi looks for them, and how
/reloadgets a new one running without restarting your session. - Build a command:
/stash- a user-triggered slash command that surfaces which saved entries need attention, so you stop hand-querying the database to find them. - Build a tool: re-extract a URL - a typed, LLM-callable tool that actually re-runs the extraction pipeline, so the agent can fix a mangled entry without you doing the Python-shell dance.
- Hook into the loop - the lifecycle events that let an extension see and shape what Pi does on every turn, and where MCP actually fits into this picture (a lot narrower than you’d guess).
By the end you’ll have two new moves in your stash harness - one you trigger, one the agent triggers - and you’ll understand the event hooks that everything else in this course’s extensions (permissions, task discipline, subagents) turns out to be built from.
Start with how an extension is put together.