Stage 5

The Extension System

2025-12-13 — 2025-12-25 v0.21.0 → v0.30.0 298 commits 15 contributors
agent-runtime devex cli extension-api llm-providers

Zeitgeist

Between v0.21.0 and v0.30.0 (December 14-25, 2025), pi evolved from a coding agent with hooks into a genuine plugin platform. The 298 commits in this stage introduced custom tools, the programmatic SDK, GitHub Copilot as a provider, project-specific settings, sub-agent orchestration, syntax highlighting, credential refactoring, and a unified settings menu. The extensibility surface grew from "intercept events" to "register tools, commands, shortcuts, and renderers." By Christmas Day, when v0.30.0 shipped, pi had become something closer to an IDE than a CLI.

The driving force behind this stage was the realization that hooks alone were insufficient. Hooks could observe and modify, but users wanted to create. The custom tools system (v0.23.0) let TypeScript modules define entirely new tools the LLM could call, with typed parameters, custom TUI rendering, and session lifecycle awareness. The SDK (v0.26.0) exposed createAgentSession() as a factory function, making pi embeddable in any Node.js application. Together, these two features transformed pi from an end-user product into developer infrastructure.

This was also the stage where pi's community contributions accelerated dramatically. Contributors like @nicobailon, @markusylisiurunen, @aliou, @mitsuhiko, @scutifer, @kim0, and @LukeFost submitted PRs ranging from keyboard protocol support to external editor integration. The AGENTS.md style guide -- "no emojis, no fluff" -- ensured a consistent voice across all these contributions, and the changelog rules prevented anyone from modifying released version sections.

Key Developments

Custom Tools with Session Lifecycle

Version 0.23.0 (e7097d91) introduced the custom tools system. Tools were TypeScript modules in ~/.pi/agent/tools/*/index.ts or .pi/tools/*/index.ts, exporting a factory function that received a ToolAPI object. Unlike hooks, custom tools defined parameters via TypeBox schemas, had execute() methods that returned structured results, and could render custom TUI components. The factory pattern gave tools access to session lifecycle events (onSession) for maintaining state across branches and restarts. Reserved names prevented shadowing built-in tools. The sub-agent example (#215, eb1d08a5) demonstrated the most powerful use case: a tool that spawned a new AgentSession to handle delegated tasks, with chain streaming showing all sub-agent steps.

The Programmatic SDK

Version 0.26.0 (#272, 5482bf3e) introduced createAgentSession() with a philosophy stated in the documentation: "Omit to discover, provide to override." Every option had a sensible default derived from the standard file system layout (~/.pi/agent/), but any option could be overridden for embedding or testing. The SDK came with 12 examples and comprehensive documentation in docs/sdk.md. SessionManager gained static factories (create(), open(), continueRecent(), inMemory(), list()), and SettingsManager followed the same pattern. This was the formalization of pi as a library, not just a CLI.

GitHub Copilot Provider

Commits b66157c6 through c5543f75 added GitHub Copilot as a provider (#191), including OAuth token integration, model enablement via VS Code, and auto-detection of available models from the Copilot /models endpoint. The model generation script (5f590b7c) could now pull model catalogs from Copilot alongside other providers. This was strategically important: Copilot subscriptions provided free access to models like GPT-4o and Claude Sonnet, meaning pi could be zero-cost for developers already paying for GitHub.

Credential Refactoring: AuthStorage and ModelRegistry

Version 0.28.0 (#296) refactored how pi managed credentials. API keys and OAuth tokens moved from scattered locations (environment variables, settings.json, oauth.json) into a unified auth.json via the new AuthStorage class. ModelRegistry combined built-in models with custom models.json entries and resolved API keys through AuthStorage. The refactoring fixed a real bug: OAuth tokens previously lost priority to settings.json API keys (a965b6f1), causing users with unlimited plans to be billed via pay-as-you-go. The new system established a clear priority chain: runtime overrides, then OAuth, then auth.json, then environment variables.

Syntax Highlighting and Visual Polish

The merge of the syntax-highlighting branch (039b3a08) added both code block highlighting in markdown and intra-line diff highlighting for the edit tool (51171873). The implementation used cli-highlight with theme-aware colors matching the TUI's palette. A subsequent fix (d5dde00d) validated language identifiers before highlighting to prevent stderr spam from malformed code fences. Markdown table rendering was also fixed to respect terminal width (#206, c1113dee), and the HTML export gained syntax highlighting and theme support (#274, series of commits by @scutifer).

Session Lifecycle Hooks and Cancellation

Version 0.27.0 (#278) redesigned the session hooks API with before_* variants (before_switch, before_clear, before_branch) that could cancel actions by returning { cancel: true }. The branch event was merged into the session event with reason discrimination. This was driven by the checkpoint hook pattern: git-based hooks needed to stash changes before a branch and restore them after, and the two-phase event model made this possible without race conditions.

Project-Specific Settings and SYSTEM.md

Version 0.26.0 (#276) introduced project-specific settings loaded from <cwd>/.pi/settings.json, with deep merge against global settings. Project settings were read-only (intended for version control). Version 0.29.1 added automatic SYSTEM.md loading (f8b6164e), letting projects replace pi's default system prompt entirely. This completed the customization hierarchy: AGENTS.md for context, skills for knowledge, hooks for behavior, settings for configuration, and SYSTEM.md for the system prompt itself.

Philosophy Shifts

The documentation at v0.30.0 showed a maturing taxonomy of extension points. A table in docs/custom-tools.md laid out when to use each mechanism: AGENTS.md for always-needed context, slash commands for user-triggered prompts, skills for on-demand capability packages, and custom tools for LLM-callable functions. This wasn't just documentation -- it was a design philosophy that each extension point should have one clear purpose. The hooks documentation explicitly noted that tool_call events had no timeout "since they may prompt the user," while other events had a configurable hookTimeout -- a nuanced distinction that showed the system was being designed for real-world use rather than theoretical elegance.

AGENTS.md itself expanded to include rules about changelog attribution (internal changes cite issues, external contributions cite PRs and usernames), package labels for GitHub issues (pkg:agent, pkg:ai, pkg:coding-agent, etc.), and the removal of browser tools reference in favor of direct tmux-based TUI interaction. The coding-agent-specific section was removed from AGENTS.md (a8b58335), with package-specific guidance moving to per-package DEVELOPMENT.md files.

Looking Forward

The proliferation of extension mechanisms (hooks, custom tools, skills, slash commands, settings, SYSTEM.md) created an ergonomic problem that Stage 6 would address by unifying hooks and custom tools into a single "extensions" concept. The SDK's createAgentSession() factory anticipated the ExtensionAPI unification -- both used the same "omit to discover, provide to override" philosophy. The session lifecycle hooks with cancellation support laid the groundwork for the session tree's navigation events. And the sub-agent example proved that pi's runtime could be composed recursively, a pattern that would become central to the project's long-term vision.

Key changes

SessionManager API

The second parameter of `create()`, `continueRecent()`, and `list()` changed from `agentDir` to `sessionDir`. When provided, it specifies the session directory directly (no cwd encoding). When omitted, uses default (`~/.pi/agent/sessions/<encoded-cwd>/`). `open()` no longer takes `agentDir`. ([#313](https://github.com/badlogic/pi-mono/pull/313))

From the changelog

**SessionManager API**: The second parameter of `create()`, `continueRecent()`, and `list()` changed from `agentDir` to `sessionDir`. When provided, it specifies the session directory directly (no cwd encoding). When omitted, uses default (`~/.pi/agent/sessions/<encoded-cwd>/`). `open()` no longer takes `agentDir`. ([#313](https://github.com/badlogic/pi-mono/pull/313))

`--session-dir` flag

Use a custom directory for sessions instead of the default `~/.pi/agent/sessions/<encoded-cwd>/`. Works with `-c` (continue) and `-r` (resume) flags. ([#313](https://github.com/badlogic/pi-mono/pull/313) by [@scutifer](https://github.com/scutifer))

From the changelog

**`--session-dir` flag**: Use a custom directory for sessions instead of the default `~/.pi/agent/sessions/<encoded-cwd>/`. Works with `-c` (continue) and `-r` (resume) flags. ([#313](https://github.com/badlogic/pi-mono/pull/313) by [@scutifer](https://github.com/scutifer))

Reverse model cycling and model selector

Shift+Ctrl+P cycles models backward, Ctrl+L opens model selector (retaining text in editor). ([#315](https://github.com/badlogic/pi-mono/pull/315) by [@mitsuhiko](https://github.com/mitsuhiko))

From the changelog

**Reverse model cycling and model selector**: Shift+Ctrl+P cycles models backward, Ctrl+L opens model selector (retaining text in editor). ([#315](https://github.com/badlogic/pi-mono/pull/315) by [@mitsuhiko](https://github.com/mitsuhiko))

Automatic custom system prompt loading

Pi now auto-loads `SYSTEM.md` files to replace the default system prompt. Project-local `.pi/SYSTEM.md` takes precedence over global `~/.pi/agent/SYSTEM.md`. CLI `--system-prompt` flag overrides both. ([#309](https://github.com/badlogic/pi-mono/issues/309))

From the changelog

**Automatic custom system prompt loading**: Pi now auto-loads `SYSTEM.md` files to replace the default system prompt. Project-local `.pi/SYSTEM.md` takes precedence over global `~/.pi/agent/SYSTEM.md`. CLI `--system-prompt` flag overrides both. ([#309](https://github.com/badlogic/pi-mono/issues/309))

Unified `/settings` command

New settings menu consolidating thinking level, theme, queue mode, auto-compact, show images, hide thinking, and collapse changelog. Replaces individual `/thinking`, `/queue`, `/theme`, `/autocompact`, and `/show-images` commands. ([#310](https://github.com/badlogic/pi-mono/issues/310))

From the changelog

**Unified `/settings` command**: New settings menu consolidating thinking level, theme, queue mode, auto-compact, show images, hide thinking, and collapse changelog. Replaces individual `/thinking`, `/queue`, `/theme`, `/autocompact`, and `/show-images` commands. ([#310](https://github.com/badlogic/pi-mono/issues/310))

Renamed `/clear` to `/new`

The command to start a fresh session is now `/new`. Hook event reasons `before_clear`/`clear` are now `before_new`/`new`. Merry Christmas [@mitsuhiko](https://github.com/mitsuhiko)! ([#305](https://github.com/badlogic/pi-mono/pull/305))

From the changelog

**Renamed `/clear` to `/new`**: The command to start a fresh session is now `/new`. Hook event reasons `before_clear`/`clear` are now `before_new`/`new`. Merry Christmas [@mitsuhiko](https://github.com/mitsuhiko)! ([#305](https://github.com/badlogic/pi-mono/pull/305))

Auto-space before pasted file paths

When pasting a file path (starting with `/`, `~`, or `.`) after a word character, a space is automatically prepended. ([#307](https://github.com/badlogic/pi-mono/pull/307) by [@mitsuhiko](https://github.com/mitsuhiko))

From the changelog

**Auto-space before pasted file paths**: When pasting a file path (starting with `/`, `~`, or `.`) after a word character, a space is automatically prepended. ([#307](https://github.com/badlogic/pi-mono/pull/307) by [@mitsuhiko](https://github.com/mitsuhiko))

packages/tui/src/components/editor.ts L47–50
export interface EditorTheme {
	borderColor: (str: string) => string;
	selectList: SelectListTheme;
}
definition in editor.ts

Word navigation in input fields

Added Ctrl+Left/Right and Alt+Left/Right for word-by-word cursor movement. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))

From the changelog

**Word navigation in input fields**: Added Ctrl+Left/Right and Alt+Left/Right for word-by-word cursor movement. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))

Issues closed (10)