A curated collection of Pi extensions, agent patterns, and workflow tools — built through hands-on exploration of the Pi agent extension API.
Pi is a minimal terminal coding agent. Unlike closed tools, Pi exposes its entire agent loop — input hooks, tool interception, UI control, subagents — as a TypeScript extension API. This repo is what I built while learning that API.
pi -e extensions/providers/AppExtensionProvider.tsAppExtensionProvider boots all extensions with a single command — inspired by Laravel's AppServiceProvider. To add or remove an extension, edit the provider file.
Each extension is standalone and can be loaded on its own:
pi -e extensions/my-footer.ts
pi -e extensions/session-observer.ts
pi -e extensions/blog-writer.tsReplaces Pi's default footer with a two-part display: model name + git branch in accent colour on the left, 10-block context meter on the right.
Concepts: setFooter, theme.fg(), ctx.getContextUsage(), visibleWidth, truncateToWidth
pi -e extensions/my-footer.tsAsks "Which feature are you working on today?" before the session starts. Injects the answer into the system prompt on every turn. Blocks all input until the question is answered. Shows the active feature in the footer status.
Concepts: session_start void pattern, before_agent_start dynamic system prompt, input hook with action: handled/continue, setStatus with theme colours
pi -e extensions/my-gate.tsRequires the agent to call a checkpoint tool and log its intent before every bash command. If checkpoint is skipped, bash is blocked.
Concepts: pi.registerTool(), tool_call interception, parallel tool call timing (tool_call fires before execute), block: true
pi -e extensions/my-checkpoint.tsSilently tracks technical workflow signals throughout a session — tool usage, file read/edit ratios, reprompts, agent dispatch patterns. On demand, runs a subagent evaluator to surface pain points and suggest fixes to agent files.
Writer instructions live in .pi/agents/session-evaluator.md — tune what gets flagged without touching TypeScript.
Commands: /observer evaluate · /observer fix · /observer report · /observer clear · /observer history · /observer help
Concepts: session_start, tool_call + tool_execution_end for timing, createAgentSession subagent evaluator, ctx.ui.confirm() for diff + approve flow, setStatus
pi -e extensions/session-observer.tsPoints at any markdown file, lists its ## sections, and drafts polished blog posts from them using a subagent writer. Opens each draft in Pi's built-in editor for review and inline editing before saving.
Writer instructions live in .pi/agents/blog-writer.md — tune the tone, style, and format without touching TypeScript.
Commands: /blog set <file> · /blog list · /blog draft <topic or number> · /blog drafts · /blog help
Concepts: createAgentSession subagent writer, DefaultResourceLoader with agent file, ctx.ui.editor() for full draft review, markdown section parsing
pi -e extensions/blog-writer.tsOn session shutdown, prompts you to update PROGRESS.md if there was meaningful activity. Opens the file in Pi's built-in editor so you can jot down where you left off before closing. Skips the prompt for empty sessions.
Concepts: session_shutdown, turn_end for activity tracking, ctx.ui.confirm(), ctx.ui.editor()
pi -e extensions/progress-tracker.tsWork-in-progress designs for extensions not yet built.
A silent observer that watches an entire Pi session for workflow signals — course corrections, aborts, tool excess, scope drift — and at session end uses a Sonnet subagent to synthesise actionable agent improvement suggestions.
Why it matters: Self-eval scores the answer. The observer watches your behaviour — reprompts, aborts, corrections — which is the real ground truth for whether the agent is working well.
A framework that brings Laravel's developer experience to Pi — convention over configuration, /make:agent scaffolding wizard, agent anatomy template, session observer integration, and self-eval loop.
Extensions (.ts) contain event wiring, tool registration, and command handlers — no hardcoded prompts.
Subagent instructions live in .pi/agents/<name>.md. This keeps prompts editable without touching TypeScript, and makes tuning behaviour a markdown edit rather than a code change.
| What | Where |
|---|---|
| Tool registration, event hooks, commands | extensions/<name>.ts |
| Subagent system prompts | .pi/agents/<name>.md |
| Skills loaded into main session | .pi/skills/<name>/SKILL.md |
Examples in this repo:
blog-writer.ts→ instructions in.pi/agents/blog-writer.mdsession-observer.ts→ evaluator instructions in.pi/agents/session-evaluator.md
user input
→ input hook (gate/transform/block)
→ before_agent_start (inject system prompt)
→ model thinks
→ tool_call (intercept/block before tool runs)
→ tool executes
→ tool_result (observe after tool completes)
→ model responds
Every stage is hookable. You control what the model sees, what tools it can run, and what the UI shows.
When the model calls multiple tools in one response block, all tool_call events fire before any execute runs. State you need for blocking decisions must be set in tool_call, not execute.
// ❌ Wrong — execute runs too late for blocking decisions
pi.registerTool({ name: "checkpoint", execute: async (_, params) => {
lastCheckpoint = params.intent; // bash tool_call already fired
}});
// ✅ Correct — set state in tool_call
pi.on("tool_call", async (event) => {
if (event.toolName === "checkpoint") {
lastCheckpoint = (event.input as { intent: string }).intent;
}
});Awaiting inside session_start prevents the session from completing startup. Use void to fire async work without blocking:
// ❌ Blocks session startup
pi.on("session_start", async (_event, ctx) => {
feature = await ctx.ui.input("Which feature?", "");
});
// ✅ Fires without blocking
pi.on("session_start", async (_event, ctx) => {
void (async () => {
feature = await ctx.ui.input("Which feature?", "");
ctx.ui.setStatus("gate", ctx.ui.theme.fg("accent", feature));
})();
});pi.registerTool({
name: "my_tool",
label: "My Tool", // required by type
description: "...",
parameters: Type.Object({ ... }),
async execute(_id, params) {
return {
content: [{ type: "text", text: "result" }],
details: {}, // required by type
};
}
});The extensions in this repo cannot be built for Claude Code. Claude Code is a closed binary — its agent loop is not accessible.
| Pi capability | Claude Code |
|---|---|
| Intercept + block any tool call | ❌ |
| Dynamic system prompt per turn | ❌ |
| Custom footer / widgets | ❌ |
| Block user input programmatically | ❌ |
| Spawn subagent sessions | ❌ |
Pi requires you to build what you want. The payoff is complete control over the agent loop.
The mental model from Pi's damage control (tool_call interception → enforce at the handler) maps directly to securing AI features in production SaaS:
The model is an untrusted client. Treat every tool call like an HTTP request — authenticate and authorise at the handler, not at the prompt.
For Laravel developers: the LLM touches your controllers/services. Your Policies, Middleware, and Global Scopes fire as normal. You don't need a new permission system — treat the LLM like any other authenticated HTTP client.
- pi-agent-forge (coming soon) — Laravel-style scaffolding framework for Pi agents
# Install Pi globally
npm install -g @mariozechner/pi-coding-agent
# Install types locally for editor support
npm install @mariozechner/pi-coding-agent
# Run an extension
pi -e extensions/my-footer.ts