Skip to content

Your first extension

You’ve spent three chapters getting good mileage out of Pi as it ships: you’ve watched the loop close on real fixes, learned to read the context instead of trusting it blind, written stash’s conventions into .pi/APPEND_SYSTEM.md so you stop re-explaining them, and set up routing so cheap models take the grunt work and a strong one takes the careful work. All of that was configuration - real leverage, but nothing you couldn’t get by editing a file and restarting a session.

This chapter is different. stash has a chore that config can’t reach: 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.

  • Anatomy of an extension - the factory function every extension exports, where Pi looks for them, and how /reload gets 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.