Stage 11

Runtime Overhaul

2026-03-23 — 2026-04-09 v0.62.0 → v0.66.1 136 commits 11 contributors
agent-runtime devex llm-providers tui cli

Zeitgeist

The 136 commits between v0.62.0 and v0.66.1 (March 23 to April 9, 2026) are the fewest of any stage, but they contain some of the most architecturally significant changes in the project's history. This was a stage of consolidation and formalization: the ad-hoc session management code was replaced by a closure-based runtime API, built-in tools were promoted to first-class ToolDefinition objects, a defineTool helper eliminated manual type casting, the agent state API was simplified, and testing infrastructure matured with the faux provider and characterization test suites. The project was preparing for a world where pi would be embedded in other applications, not just run as a standalone CLI.

The mood was introspective. After the community-driven expansion of Stages 9 and 10, the core maintainer spent this stage paying down architectural debt and drawing cleaner boundaries. The removal of session_switch and session_fork events in favor of a unified session_start with event.reason is characteristic: rather than proliferating event types for each session lifecycle transition, the runtime collapsed them into a single event with discriminated payloads. This pattern -- fewer concepts, more composition -- runs through every major change in this stage.

The contributor count narrowed back to the core team plus a handful of regulars (@aliou, @ferologics, @mrexodia, @xu0o0, @w-winter, @kaofelix). This was not a decline in community interest but a natural consequence of the changes being deep architectural refactors that required intimate knowledge of pi's internals. The stage ends with v0.66.1, which hides the "Earendil" startup announcement behind the /dementedelves Easter egg command -- a lighthearted note at the end of a serious refactoring sprint.

Key Developments

The Session Runtime API

The single largest change was createAgentSessionRuntime() and the AgentSessionRuntime type, introduced in v0.65.0. Previously, session switching (via /new, /resume, /fork, or import) was handled by methods on AgentSession itself, which meant the session object had to know how to replace itself. The new runtime takes a CreateAgentSessionRuntimeFactory closure that closes over process-global fixed inputs (agent directory, startup options) and recreates cwd-bound services and session config for every session switch. Startup, /new, /resume, /fork, and JSONL import all use the same factory. The old session-replacement methods were removed from AgentSession, and the previous AgentSessionRuntimeHost interface was replaced with the simpler closure-based AgentSessionRuntime (v0.65.0 refactor). SDK consumers now call runtime.newSession() and runtime.switchSession() instead of session.newSession().

defineTool and Built-in Tool Definitions

The defineTool() helper (#2746) solved a long-standing TypeScript ergonomics problem: when defining a custom tool with TypeBox parameters, the type of params in the execute callback was inferred as unknown unless you added manual casts. defineTool() uses generic inference to propagate the TypeBox schema type through to the callback parameters. Simultaneously, built-in tools (read, write, edit, bash, grep, find, ls) were converted from hardcoded implementations to standard ToolDefinition objects (readToolDefinition, createReadToolDefinition(), etc.) in v0.62.0. This meant extensions could now import built-in tool definitions and override just their renderCall or renderResult components, without reimplementing the tool logic. The prepareArguments hook (v0.64.0) let tools normalize raw model arguments before schema validation -- the built-in edit tool used this to silently fold the legacy single-edit oldText/newText schema into the new edits[] array format when resuming old sessions.

Faux Provider and Test Infrastructure

The ModelRegistry.inMemory() factory and the faux provider (v0.64.0) created a complete testing story that did not require real API keys or paid tokens. ModelRegistry lost its public constructor -- SDK callers must now use ModelRegistry.create() for file-backed registries or ModelRegistry.inMemory() for testing. The packages/coding-agent/test/suite/harness.ts test harness used the faux provider to run AgentSession through complete conversation flows. Queue characterization tests (#2719) verified that message queuing, retry, and compaction behaved correctly under concurrent events. Session lifecycle characterization tests (#2766) verified startup, reload, fork, and resume transitions. AGENTS.md was updated to require all new tests use the harness and faux provider.

Agent State API Simplification

The AgentState interface in packages/agent/src/types.ts was simplified in v0.65.0 (#2633): mutable internal state (streaming flags, pending tool calls, error messages) was separated from the public read-only surface. The internal MutableAgentState type uses getters and setters with copy-on-access for tools and messages arrays, preventing accidental mutation through shared references. Subscribed event handlers are now properly awaited (#2633), fixing bugs where state changes from handlers were invisible because they completed after the caller returned.

Hidden Thinking Labels and UI Polish

Extensions gained ctx.ui.setHiddenThinkingLabel() (v0.64.0, #2673) for customizing the collapsed thinking block label in interactive mode. Label timestamps in /tree (#2691 by @w-winter) added a Shift+T toggle that shows smart date formatting on tree entries. The bash tool rendering was updated to show elapsed time at the bottom of the tool block (#2406). Theme export colors were fixed to resolve theme variables correctly (#2707). The TUI render scheduler gained throttling under streaming load (v0.65.2) to prevent render storms. These are the small touches that make the difference between a tool that is usable and one that is pleasant.

Composable Tool Render Middleware

Built-in tools becoming ToolDefinition objects meant that the rendering pipeline became composable. An extension that wants to customize how edit results appear can import createEditToolDefinition(), override renderResult, and register the modified definition. The fallback rendering logic was changed so that rendering happens only when a renderer is not defined for that slot -- if renderCall or renderResult is defined, it must return a Component. This restored the minimal-mode example (#2595) and established a clean middleware pattern.

Philosophy Shifts

AGENTS.md added the most prescriptive testing rules yet: "For packages/coding-agent/test/suite/, use test/suite/harness.ts plus the faux provider. Do not use real provider APIs, real API keys, or paid tokens." Issue-specific regressions were assigned to packages/coding-agent/test/suite/regressions/ with a <issue-number>-<short-slug>.test.ts naming convention. The rule "Do not preserve backward compatibility unless the user explicitly asks for it" was added, reflecting a project that had learned the cost of maintaining compatibility shims: the edit tool's prepareArguments hook was the last compatibility bridge, and future breaking changes would be clean breaks.

The removal of session_switch and session_fork events in favor of session_start with event.reason ("startup" | "reload" | "new" | "resume" | "fork") represents a maturation in the event model. Rather than adding a new event for each lifecycle transition, the runtime uses discriminated union payloads on a single event type. This is the same pattern used by Redux actions, DOM events, and other mature event systems -- and its adoption signals that pi's extension API has moved past the "add a hook for every new feature" phase into a more considered design.

The unified sourceInfo system in v0.62.0 (#1734) attached structured provenance metadata to every resource, command, tool, skill, and prompt template, replacing the scattered extensionPath, location, and source fields. The detailed migration notes in the changelog list every field rename -- a sign that the project takes API consumers seriously.

Looking Forward

The session runtime API is designed for embedding. The CreateAgentSessionRuntimeFactory closure pattern lets a host application control session creation, service initialization, and cwd resolution -- exactly what you need for a VS Code extension, a web app, or a multi-tenant server. The faux provider and characterization tests create a safety net for future refactoring. And the agent state simplification prepares for the kind of observability that production deployments require. Pi at v0.66.1 is no longer just a coding agent; it is an agent framework with opinions about how agent applications should be built.

Key changes

Changed the Earendil announcement from an automatic startup notice to the hidden

Changed the Earendil announcement from an automatic startup notice to the hidden `/dementedelves` slash command.

Earendil startup announcement with bundled inline image rendering and a linked b

Earendil startup announcement with bundled inline image rendering and a linked blog post for April 8 and 9, 2026.

Interactive Anthropic subscription auth warning when Anthropic subscription auth

Interactive Anthropic subscription auth warning when Anthropic subscription auth is active, clarifying that Anthropic third-party usage draws from extra usage and is billed per token.

Session runtime API

`createAgentSessionRuntime()` and `AgentSessionRuntime` provide a closure-based runtime that recreates cwd-bound services and session config on every session switch. Startup, `/new`, `/resume`, `/fork`, and import all use the same creation path. See [docs/sdk.md](docs/sdk.md) and [examples/sdk/13-session-runtime.ts](examples/sdk/13-session-runtime.ts).

From the changelog

**Session runtime API**: `createAgentSessionRuntime()` and `AgentSessionRuntime` provide a closure-based runtime that recreates cwd-bound services and session config on every session switch. Startup, `/new`, `/resume`, `/fork`, and import all use the same creation path. See [docs/sdk.md](docs/sdk.md) and [examples/sdk/13-session-runtime.ts](examples/sdk/13-session-runtime.ts).

Label timestamps in `/tree`

Toggle timestamps on tree entries with `Shift+T`, with smart date formatting and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter))

From the changelog

**Label timestamps in `/tree`**: Toggle timestamps on tree entries with `Shift+T`, with smart date formatting and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter))

packages/coding-agent/src/core/keybindings.ts L13–32
export interface AppKeybindings {
	"app.interrupt": true;
	"app.clear": true;
	"app.exit": true;
	"app.suspend": true;
	"app.thinking.cycle": true;
	"app.model.cycleForward": true;
	"app.model.cycleBackward": true;
	"app.model.select": true;
	"app.tools.expand": true;
	"app.thinking.toggle": true;
	"app.session.toggleNamedFilter": true;
	"app.editor.external": true;
	"app.message.followUp": true;
	"app.message.dequeue": true;
	"app.clipboard.pasteImage": true;
	"app.session.new": true;
	"app.session.tree": true;
	"app.session.fork": true;
	"app.session.resume": true;
definition in keybindings.ts
packages/coding-agent/src/core/session-manager.ts L27–28
export const CURRENT_SESSION_VERSION = 3;
definition in session-manager.ts

`defineTool()` helper

extension-api agent-runtime

Create standalone custom tool definitions with full TypeScript parameter type inference, no manual casts needed ([#2746](https://github.com/badlogic/pi-mono/issues/2746)). See [docs/extensions.md](docs/extensions.md).

From the changelog

**`defineTool()` helper**: Create standalone custom tool definitions with full TypeScript parameter type inference, no manual casts needed ([#2746](https://github.com/badlogic/pi-mono/issues/2746)). See [docs/extensions.md](docs/extensions.md).

packages/coding-agent/examples/extensions/hello.ts L24–26
export default function (pi: ExtensionAPI) {
	pi.registerTool(helloTool);
}
default export in hello.ts
packages/coding-agent/src/core/extensions/index.ts L157–158
	defineTool,
	isBashToolResult,
registration in index.ts

Unified diagnostics

Arg parsing, service creation, session option resolution, and resource loading all return structured diagnostics (`info`/`warning`/`error`) instead of logging or exiting. The app layer decides presentation and exit behavior.

From the changelog

**Unified diagnostics**: Arg parsing, service creation, session option resolution, and resource loading all return structured diagnostics (`info`/`warning`/`error`) instead of logging or exiting. The app layer decides presentation and exit behavior.

Removed extension post-transition events `session_switch` and `session_fork`

Use `session_start` with `event.reason` (`"startup" | "reload" | "new" | "resume" | "fork"`). For `"new"`, `"resume"`, and `"fork"`, `session_start` includes `previousSessionFile`.

From the changelog

Removed extension post-transition events `session_switch` and `session_fork`. Use `session_start` with `event.reason` (`"startup" | "reload" | "new" | "resume" | "fork"`). For `"new"`, `"resume"`, and `"fork"`, `session_start` includes `previousSessionFile`.

Issues closed (10)