Make it yours — extensions, skills, and the SDK
The 30-second version: everything Pi "doesn't have" is on purpose — because it gives you the pieces instead. TypeScript extensions modify Pi live while it runs, skills teach it new capabilities on demand, packages bundle and share both, and the SDK lets you embed the whole engine in your own application.
The self-extensible agent
Remember the bet from chapter one: keep the core small, hand users the tools to build whatever's missing. This chapter is the payoff — the five levers Pi gives you:
- Extensions — TypeScript code that changes how Pi behaves
- Skills — markdown playbooks the model reads when relevant
- Prompt templates — reusable prompts behind slash commands
- Themes — terminal looks, because you'll be staring at this all day
- Pi packages — bundles of all of the above, installable from npm or git
The first two do the heavy lifting, so let's spend our time there.
Extensions: modify the plane while flying it
An extension is a TypeScript file Pi loads at startup. It can register new tools, slash commands, keybindings, event listeners, themes — and it can hot-reload: edit the file mid-session and the change applies immediately.
Hot-reload sounds like a convenience feature until you notice what it unlocks: you can ask Pi to improve Pi. "Write me an extension that blocks writes to main branch files" — Pi writes the file, the harness picks it up, and the new rule is already in force. No restart, no rebuild. The extension system is how Pi's own community builds the features Pi declined to ship: plan modes, permission gates, MCP bridges, sandboxing.
A taste of what an extension looks like — registering a tiny custom tool:
import type { PiExtension } from "@earendil-works/pi-coding-agent";
const extension: PiExtension = {
name: "weather",
register(pi) {
pi.registerTool({
name: "weather",
label: "Weather",
description: "Get the current weather for a city",
parameters: { city: { type: "string" } },
execute: async (_id, { city }) => {
const res = await fetch(`https://wttr.in/${city}?format=3`);
return { content: [{ type: "text", text: await res.text() }] };
},
});
},
};
export default extension;
(Real extensions do more — hook events, render custom UI, guard tool calls — but this is the genuine shape: get a registration handle, bolt on what you want.)
Because extensions can also hook the event stream from chapter two, they're the sanctioned place for all the "watch and intercept" logic: audit logging, auto-formatting after edits, notifications on turn end. The core stays clean; your opinions live out here.
Skills: knowledge, on demand
Skills are the gentler lever. Where extensions add capabilities (new tools, new behavior), skills add knowledge — how to do a particular kind of task.
A skill is a directory with a SKILL.md file: a name, a description, and instructions (plus optional helper scripts and reference docs). Pi implements the open Agent Skills standard, which means skills are portable across harnesses — Pi can even load skills from Claude Code's and Codex's directories.
The magic is in the loading model we met in the memory chapter: only each skill's name and description live in the prompt; the full text is read on demand when a task matches. Ten skills cost you a few hundred tokens of index, not tens of thousands of embedded text.
A minimal skill looks like this:
---
name: changelog
description: Generate changelog entries from recent git commits
---
# Changelog generation
1. Run `git log --oneline v{last-tag}..HEAD`
2. Group commits by feature / fix / chore
3. Write entries in the style of CHANGELOG.md
...
Drop it in ~/.pi/agent/skills/changelog/SKILL.md (or your project's .pi/skills/, or ~/.agents/skills/ to share it with other harnesses) and Pi will use it whenever you ask for changelog work.
Packages: share the lot
Everything above — extensions, skills, templates, themes — can be bundled into a pi package and installed like any other dependency:
pi install npm:@someone/pi-extras
pi install git:github.com/someone/pi-goodies
This closes the loop of the ecosystem bet: the features Pi doesn't ship aren't gone — they live in community packages, one command away.
The SDK: Pi as an engine
Last lever, and arguably the most powerful: you don't have to use Pi's terminal at all. The coding-agent package doubles as an SDK — embed the whole engine (loop, tools, sessions, compaction) in your own application:
import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
const modelRuntime = await ModelRuntime.create();
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
modelRuntime,
});
session.subscribe((event) => {
if (event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What files are in the current directory?");
That's a headless agent in about fifteen lines. From there you can build a web UI, a Slack bot, a CI reviewer, a test harness that interrogates agents — anything. There's also an RPC mode (JSON over stdin/stdout) for driving Pi from languages other than TypeScript, and a JSON streaming mode for pipelines.
Notice what this means architecturally: the terminal product and your custom product are built from the same engine. When Pi improves, both improve.
The takeaway
Most tools hand you features. Pi hands you leverage — five sanctioned ways to reshape it, from a markdown file to a full SDK embedding. The maintainers put it best in their own headline: this is a self-extensible coding agent. The nicest thing about that isn't the extensibility — it's that an agent capable of rewriting itself is also the easiest one to truly understand.
And understanding is exactly what this whole site has been about.
Sources & further reading: Extensions · Skills · SDK · Pi packages