# Development Workflow for Pi-Web: A Complete Guide to Extending the Pi Coding Agent

> Master the pi-web development workflow by extending API routes, session logic, and React components within the Next.js app. Use npm run dev for hot-reloading and preserve agent states.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-08-09

---

**The pi-web development workflow centers on a Next.js application structure where you modify API routes in `app/api/`, extend session logic in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), and iterate on React components in `components/`, using `npm run dev` to hot-reload changes while the global session registry preserves active agent states across refreshes.**

Pi-web is the official web interface for the pi coding agent, built as a Next.js application in the `agegr/pi-web` repository. Understanding the **development workflow for pi-web** requires navigating three distinct layers: the HTTP API surface, the core runtime wrapping the pi SDK, and the React frontend. This guide walks through each layer with specific file paths and implementation patterns extracted from the source code.

## Starting the Development Server

The entry point for local development is the Next.js dev server configured in [`package.json`](https://github.com/agegr/pi-web/blob/main/package.json). Running `npm run dev` binds the application to `127.0.0.1:30141` by default, enabling hot-reload for all TypeScript and React files.

Unlike production builds, you should avoid running `next build` during active development to prevent cluttering the `.next/` directory with stale artifacts. For production releases, use `npm run release`, which handles versioning, building, and publishing in one step.

## The Three-Layer Architecture

The codebase is organized into three functional layers that handle the flow from HTTP request to UI rendering.

### Next.js API Routes (app/api/)

All RPC commands and Server-Sent Events (SSE) stream through `/app/api/agent/[id]/route.ts`. This file handles POST requests for commands and maintains the event stream connection used by the frontend to receive real-time updates from the agent.

### Core Runtime (lib/)

The `lib/` directory contains the business logic that bridges the Next.js server and the pi SDK. The **AgentSessionWrapper** class in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) manages the lifecycle of `AgentSession` instances, implementing commands like `"prompt"`, `"fork"`, `"navigate_tree"`, and `"set_model"`.

It maintains a **global registry** at `globalThis.__piSessions` that survives Next.js hot-reloads, ensuring persistent session state across code changes. The file also exports `hasBusyRpcSessionForCwd()` and `subscribeRunningSessions()` to let the UI query live session status.

### React Components (components/)

The UI layer consists of components like [`SessionSidebar.tsx`](https://github.com/agegr/pi-web/blob/main/SessionSidebar.tsx) and [`ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/ChatWindow.tsx) that render the file explorer and chat interface. The [`useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/useAgentSession.ts) hook orchestrates state management, connecting React components to the SSE streams and RPC endpoints defined in the API layer.

## Extending RPC Commands and Session Logic

Adding functionality typically involves modifying the RPC handler switch statement or adjusting how session context is built.

### Adding a New RPC Command

To implement a new command, modify the `send()` method's switch block in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts). The following example adds a `"reset_context"` command that clears chat history while preserving the session file:

```typescript
// In lib/rpc-manager.ts, inside the send() switch:
case "reset_context": {
  // Clears the current chat history but keeps the session file.
  this.inner.sessionManager.clearHistory();
  invalidateSessionListCache();
  return { success: true };
}

```

### Managing Global Session State and Caching

When mutating commands complete, call `invalidateSessionListCache()` or `invalidateModelsCache()` to ensure the UI reflects current state. These invalidators are defined in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) alongside `listAllSessions()`, which scans the pi session directory and resolves project roots via `resolveProject`.

The `buildSessionContext()` function in the same file converts raw `.jsonl` entries into UI-ready `AgentMessage` objects, handling tool-call normalization and deferred thinking blocks.

## Building Custom Extension UI

Pi-web supports custom UI widgets from extensions through the `AgentSessionWrapper`, which creates a **PlainTextTheme** for rendering and supplies a `createExtensionUiContext()` offering `select`, `confirm`, `input`, `editor`, and `notify` methods.

### Creating Extension Widgets

Extensions receive a UI context that can register widgets streamed back as SSE events:

```typescript
// In an extension's entry point (executed by Pi-Web)
export async function activate(context: ExtensionContext) {
  const ui = context.ui; // Provided by Pi-Web
  ui.setWidget(
    "my-status",
    async (tui, theme) => ({
      render: (width) => [`${theme.bold("Status:")} Ready`.padEnd(width)],
    }),
    { placement: "aboveEditor" },
  );
}

```

### Accessing Session Files from Components

React components can access the underlying session manager to retrieve file paths or metadata. Import `getRpcSession` from the RPC manager to fetch the wrapper instance:

```tsx
import { getRpcSession } from "../lib/rpc-manager";

async function openSessionFile(sessionId: string) {
  const wrapper = getRpcSession(sessionId);
  if (!wrapper) return;
  const path = await wrapper.inner.sessionManager.getSessionFile();
  // Use the path to display the raw JSONL or export HTML.
}

```

## Testing, Linting, and Quality Assurance

Before committing changes, run the quality checks defined in [`package.json`](https://github.com/agegr/pi-web/blob/main/package.json). Execute `npm test` for the test suite, `npm run lint` for style compliance, and `node_modules/.bin/tsc --noEmit` for type checking without emission.

For production releases, use `npm run release`, which handles version bumping, building, and publishing—avoid manual `next build` calls that complicate the workspace with unnecessary artifacts.

## Summary

- Start local development with `npm run dev` on port 30141; avoid `next build` during active coding.
- API routes in `app/api/agent/[id]/route.ts` handle RPC and SSE streaming for real-time UI updates.
- [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) implements the `AgentSessionWrapper` with a global registry (`globalThis.__piSessions`) that persists across hot-reloads.
- [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) manages session caching and context building via `listAllSessions()` and `buildSessionContext()`.
- Invalidate caches using `invalidateSessionListCache()` after mutations to keep the sidebar and model selectors synchronized.
- Extension UI integrates through the wrapper's theme system and headless TUI (`createHeadlessCustomUiTui`).
- Run `npm run release` for production builds instead of manual build commands.

## Frequently Asked Questions

### How do I add a new RPC command to pi-web?

Add a new case to the `send()` method's switch block in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts). Implement the logic using the `this.inner.sessionManager` API, then return a success response. Remember to call `invalidateSessionListCache()` if the command modifies session metadata that appears in the sidebar.

### What is the AgentSessionWrapper and why does it use a global registry?

`AgentSessionWrapper` is a class that encapsulates the pi SDK's `AgentSession` and implements the RPC contract. It stores instances in `globalThis.__piSessions` to survive Next.js hot-reloads, preventing session loss during development while enabling the UI to query running sessions via `hasBusyRpcSessionForCwd()`.

### How does hot-reloading affect active agent sessions?

Next.js hot-reload refreshes the API route modules and React components, but the global registry in `globalThis` persists. This means active `AgentSessionWrapper` instances continue running, though you may need to invalidate caches to see metadata changes in [`SessionSidebar.tsx`](https://github.com/agegr/pi-web/blob/main/SessionSidebar.tsx).

### How do I implement custom extension UI components?

Extensions access the UI context through the `AgentSessionWrapper`, which provides `createExtensionUiContext()`. Use methods like `ui.setWidget()` to register components that render via the `PlainTextTheme` and stream updates through the headless TUI system back to the React frontend via SSE.