Stage 7

Agent Maturity

2026-01-05 — 2026-01-13 v0.35.0 → v0.45.0 378 commits 42 contributors
agent-runtime extension-api devex cli llm-providers

Zeitgeist

Between January 5 and 13, 2026, pi crossed a threshold from "promising coding agent" to "extensible platform." The 378 commits in this stage reflect a project that had stabilized its core architecture in the previous restructuring and could now focus on making the extension surface richer, the provider ecosystem broader, and the overall experience more polished. Community contributors began appearing in meaningful numbers -- not just filing issues, but landing PRs that added entire providers, UI features, and extension examples.

The unifying theme was maturation through delegation. Rather than the core team adding every feature directly, pi invested in hooks and APIs that let extensions do the work. The model_select hook, ctx.ui.setWorkingMessage(), ctx.ui.setFooter(), pluggable tool operations for remote execution, and the ability to override built-in tools all emerged here. The agent was learning to get out of its own way. At the same time, the provider landscape exploded: Amazon Bedrock (#494), MiniMax (#656), OpenCode Zen, Vercel AI Gateway (#689), MiniMax China (#725), and improved Gemini CLI support all landed. Pi was no longer just an Anthropic-and-OpenAI tool.

The stage also marks the beginning of pi's identity as a first-class interactive application. The /scoped-models command for enabling and ordering model cycling (#626), the /name command for labeling sessions (#650), fuzzy search in settings (#643), page-up/down navigation in the session selector (#662), and the rename of /branch to /fork (#641) all demonstrate an obsession with the details of daily use. These are not features you build for a demo; they are features you build because you use the tool eight hours a day.

Key Developments

Pluggable Tool Operations and Remote Execution

Pi introduced operation interfaces -- ReadOperations, WriteOperations, EditOperations, BashOperations, and others -- that decouple the built-in tools from local filesystem and shell access (#564). An SSH extension example demonstrated running all tool operations on a remote host. Combined with the --no-tools flag (#557), which disables all built-in tools so extensions can provide replacements, this turned pi into a shell that could operate anywhere. The user_bash event hook let extensions intercept shell commands before execution, enabling sandboxing without replacing the entire bash tool.

The Skills Slash Command Bridge

Skills had existed since Stage 5, but they were passively loaded -- the model had to decide when to invoke them. In v0.43.0, loaded skills were registered as /skill:name commands (#630), giving users an explicit invocation path. Fuzzy matching meant typing /skbra would match /skill:brave-search. This bridged the gap between on-demand agent behavior and user-directed workflows, and the disable-model-invocation frontmatter field later let skill authors opt out of automatic loading entirely.

Provider Ecosystem Explosion

Seven new providers landed in eight days. Amazon Bedrock (#494, experimental) brought AWS-native Claude access with prompt caching. MiniMax (#656) and MiniMax China (#725) opened Chinese model access. Vercel AI Gateway (#689) added a routing layer for multiple backends. OpenCode Zen added another subscription-based path. The supportsUsageInStreaming compat flag (#596) acknowledged that not all OpenAI-compatible endpoints behave identically, beginning a pattern of per-provider compatibility tuning that would grow through later stages.

TUI Overlay Compositing

The ctx.ui.custom() API gained overlay compositing (#558), allowing extensions to render floating panels on top of the main chat. The overlay system supported CSS-like positioning and was immediately marked experimental, but it enabled use cases like the Space Invaders game extension and the DOSBox terminal extension that appeared shortly after. The investment in the TUI's compositing model reveals a vision of pi as more than a chat window.

Extension Lifecycle Refinements

Several quality-of-life improvements solidified the extension authoring experience: async extension factory functions (#513), ctx.shutdown() for graceful teardown (#542), AbortSignal support in UI dialogs (#512), timeout countdowns in confirm/select dialogs (#522), pi.sendUserMessage() for programmatic message injection, and ctx.ui.setEditorComponent() for replacing the input editor. Extension loading errors were finally surfaced to the user (#639) instead of being silently swallowed. The pattern of each release adding one or two extension API methods continued steadily.

Session Naming and Fork Rename

The /name command (#650) solved a real usability problem: when you fork a session multiple times, they all start with the same first message and look identical in the session picker. Named sessions appear with their custom label instead. The rename of /branch to /fork (#641) was a breaking API change across RPC, SDK, extension events, and settings -- but it aligned the terminology with what the operation actually does (copy-on-write, not Git branching). The thoroughness of the rename, touching every surface simultaneously, shows the project's willingness to break APIs for conceptual clarity.

Philosophy Shifts

AGENTS.md gained the rule "Never hardcode key checks with matchesKey(keyData, 'ctrl+x'). All keybindings must be configurable." This was added alongside the DEFAULT_EDITOR_KEYBINDINGS and DEFAULT_APP_KEYBINDINGS objects, making every interactive shortcut remappable. It reflects a shift from "the developer decides the UX" to "the developer provides good defaults that users can override." The PR workflow rules were also added: analyze PRs without pulling locally first, never open PRs directly, work in feature branches and merge only when ready. The community was growing, and the project needed process.

The before_agent_start event changed from returning systemPromptAppend to receiving the full event.systemPrompt and returning a replacement (#575). This seemingly small API change reveals a philosophical shift: extensions are not just appending context, they are taking ownership of the system prompt. Pi was moving from an additive extension model to a fully composable one.

Looking Forward

The pluggable operations interfaces planted the seed for the sandbox extension that would mature in later stages. The custom provider registration API (pi.registerProvider()) appeared in prototype form and would become central to the Extension Renaissance. The session naming infrastructure would be extended with --fork CLI flags and JSONL import/export. And the sheer volume of community contributions -- from @dannote, @tmustier, @aliou, @nicobailon, @Perlence, @mitsuhiko, @ogulcancelik, @ferologics, and many others -- established the contributor pipeline that would define the Ecosystem stage.

Key changes

MiniMax provider support: set `MINIMAX_API_KEY` and use `minimax/MiniMax-M2.1` (

llm-providers

MiniMax provider support: set `MINIMAX_API_KEY` and use `minimax/MiniMax-M2.1` ([#656](https://github.com/badlogic/pi-mono/pull/656) by [@dannote](https://github.com/dannote))

packages/ai/scripts/generate-models.ts L1025–1039
export const MODELS = {
`;

	// Generate provider sections (sorted for deterministic output)
	const sortedProviderIds = Object.keys(providers).sort();
	for (const providerId of sortedProviderIds) {
		const models = providers[providerId];
		output += `\t${JSON.stringify(providerId)}: {\n`;

		const sortedModelIds = Object.keys(models).sort();
		for (const modelId of sortedModelIds) {
			const model = models[modelId];
			output += `\t\t"${model.id}": {\n`;
			output += `\t\t\tid: "${model.id}",\n`;
			output += `\t\t\tname: "${model.name}",\n`;
definition in generate-models.ts
packages/ai/src/models.generated.ts L6–20
export const MODELS = {
	"amazon-bedrock": {
		"anthropic.claude-3-5-haiku-20241022-v1:0": {
			id: "anthropic.claude-3-5-haiku-20241022-v1:0",
			name: "Claude Haiku 3.5",
			api: "bedrock-converse-stream",
			provider: "amazon-bedrock",
			baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
			reasoning: false,
			input: ["text", "image"],
			cost: {
				input: 0.8,
				output: 4,
				cacheRead: 0.08,
				cacheWrite: 1,
definition in models.generated.ts

`/scoped-models`: Alt+Up/Down to reorder enabled models

Order is preserved when saving with Ctrl+S and determines Ctrl+P cycling order. ([#676](https://github.com/badlogic/pi-mono/pull/676) by [@thomasmhr](https://github.com/thomasmhr))

From the changelog

`/scoped-models`: Alt+Up/Down to reorder enabled models. Order is preserved when saving with Ctrl+S and determines Ctrl+P cycling order. ([#676](https://github.com/badlogic/pi-mono/pull/676) by [@thomasmhr](https://github.com/thomasmhr))

Amazon Bedrock provider support (experimental, tested with Anthropic Claude mode

llm-providers cli

Amazon Bedrock provider support (experimental, tested with Anthropic Claude models only) ([#494](https://github.com/badlogic/pi-mono/pull/494) by [@unexge](https://github.com/unexge))

packages/ai/scripts/generate-models.ts L998–1012
export const MODELS = {
`;

	// Generate provider sections (sorted for deterministic output)
	const sortedProviderIds = Object.keys(providers).sort();
	for (const providerId of sortedProviderIds) {
		const models = providers[providerId];
		output += `\t${JSON.stringify(providerId)}: {\n`;

		const sortedModelIds = Object.keys(models).sort();
		for (const modelId of sortedModelIds) {
			const model = models[modelId];
			output += `\t\t"${model.id}": {\n`;
			output += `\t\t\tid: "${model.id}",\n`;
			output += `\t\t\tname: "${model.name}",\n`;
definition in generate-models.ts
packages/ai/src/models.generated.ts L6–20
export const MODELS = {
	"amazon-bedrock": {
		"anthropic.claude-3-5-haiku-20241022-v1:0": {
			id: "anthropic.claude-3-5-haiku-20241022-v1:0",
			name: "Claude Haiku 3.5",
			api: "bedrock-converse-stream",
			provider: "amazon-bedrock",
			baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
			reasoning: false,
			input: ["text", "image"],
			cost: {
				input: 0.8,
				output: 4,
				cacheRead: 0.08,
				cacheWrite: 1,
definition in models.generated.ts

Extension example: `sandbox/` for OS-level bash sandboxing using `@anthropic-ai/

llm-providers extension-api

Extension example: `sandbox/` for OS-level bash sandboxing using `@anthropic-ai/sandbox-runtime` with per-project config ([#673](https://github.com/badlogic/pi-mono/pull/673) by [@dannote](https://github.com/dannote))

packages/coding-agent/examples/extensions/sandbox/index.ts L198–212
export default function (pi: ExtensionAPI) {
	pi.registerFlag("no-sandbox", {
		description: "Disable OS-level sandboxing for bash commands",
		type: "boolean",
		default: false,
	});

	const localCwd = process.cwd();
	const localBash = createBashTool(localCwd);

	let sandboxEnabled = false;
	let sandboxInitialized = false;

	pi.registerTool({
		...localBash,
default export in index.ts

`pi.getAllTools()` now returns `ToolInfo[]` (with `name` and `description`) inst

Extensions that only need names can use `.map(t => t.name)`. ([#648](https://github.com/badlogic/pi-mono/pull/648) by [@carsonfarmer](https://github.com/carsonfarmer))

From the changelog

`pi.getAllTools()` now returns `ToolInfo[]` (with `name` and `description`) instead of `string[]`. Extensions that only need names can use `.map(t => t.name)`. ([#648](https://github.com/badlogic/pi-mono/pull/648) by [@carsonfarmer](https://github.com/carsonfarmer))

Session naming: `/name <name>` command sets a display name shown in the session

Useful for distinguishing forked sessions. Extensions can use `pi.setSessionName()` and `pi.getSessionName()`. ([#650](https://github.com/badlogic/pi-mono/pull/650) by [@scutifer](https://github.com/scutifer))

From the changelog

Session naming: `/name <name>` command sets a display name shown in the session selector instead of the first message. Useful for distinguishing forked sessions. Extensions can use `pi.setSessionName()` and `pi.getSessionName()`. ([#650](https://github.com/badlogic/pi-mono/pull/650) by [@scutifer](https://github.com/scutifer))

Extension example: `notify.ts` for desktop notifications via OSC 777 escape sequ

extension-api

Extension example: `notify.ts` for desktop notifications via OSC 777 escape sequence ([#658](https://github.com/badlogic/pi-mono/pull/658) by [@ferologics](https://github.com/ferologics))

packages/coding-agent/examples/extensions/notify.ts L21–25
export default function (pi: ExtensionAPI) {
	pi.on("agent_end", async () => {
		notify("Pi", "Ready for input");
	});
}
default export in notify.ts

Inline hint for queued messages showing the `Alt+Up` restore shortcut ([#657](ht

Inline hint for queued messages showing the `Alt+Up` restore shortcut ([#657](https://github.com/badlogic/pi-mono/pull/657) by [@tmustier](https://github.com/tmustier))

Issues closed (10)