# Performance Considerations for Pi-Web: Architecture and Optimization Strategies

> Optimize Pi-Web performance with lazy initialization, global locks, timeouts, and caching strategies. Discover architectural insights and expert tips for efficient operation. Learn more now!

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: performance
- Published: 2026-07-27

---

**Pi-Web minimizes CPU, memory, and I/O overhead through lazy session initialization, global startup locks, idle timeouts, and strategic caching of models and session paths.**

Pi-Web serves as the Next.js-based front-end for the **pi-coding-agent** SDK, designed to remain responsive while managing dozens of simultaneous AI coding sessions. Understanding the performance considerations for Pi-Web requires examining how the codebase handles session lifecycle, concurrency, caching, and resource boundaries to prevent runaway memory usage and reduce filesystem thrashing.

## Session Lifecycle and Memory Management

The `AgentSessionWrapper` class in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) implements strict memory governance to prevent abandoned sessions from consuming resources indefinitely.

### Global Session Registry and Idle Timeouts

Sessions are stored in a global registry (`globalThis.__piSessions`) that survives Next.js hot-reloads but is properly cleaned up on process exit. Each wrapper initializes an **idle timer** via `resetIdleTimer` that automatically destroys a session after 10 minutes of inactivity:

```typescript
// lib/rpc-manager.ts#L67-L75
setTimeout(() => {
  this.destroy();
}, 10 * 60 * 1000); // 10 minute TTL

```

This automatic cleanup prevents memory accumulation from zombie sessions while freeing CPU and file handles for active work.

### Bash-Only Session Persistence

When users execute shell commands without prior assistant messages, Pi-Web immediately persists the session via `persistBashOnlySession` and forces the SDK's internal `flushed` flag to `true`. This eliminates delayed flush operations that would otherwise hold references in memory, ensuring shell-only work writes to disk promptly and keeps session file sizes manageable.

## Concurrency Control and Startup Optimization

Pi-Web guarantees O(1) startup cost for sessions through a global locking mechanism that prevents duplicate initialization.

### Per-Session Startup Locks

The `startRpcSession` function uses a `Map` called `__piStartLocks` to ensure only one initialization promise exists per session ID:

```typescript
// lib/rpc-manager.ts#L107-L110
const inflight = __piStartLocks.get(sessionId);
if (inflight) return inflight; // Reuse existing promise

```

This pattern prevents race conditions where multiple API requests might simultaneously spawn SDK instances, avoiding excess CPU and memory consumption during high-concurrency scenarios.

### Busy Session Detection

The system tracks active operations via `hasBusyRpcSessionForCwd`, preventing overlapping heavy operations in the same working directory. This serialization protects both the filesystem and the underlying LLM context from conflicting concurrent modifications.

## Strategic Caching for Reduced I/O

Pi-Web implements multi-layer caching to minimize filesystem reads and recomputation of expensive metadata.

### Session-Path and Model Caching

The `cacheSessionPath` function stores absolute file paths for session IDs, eliminating repeated disk lookups on subsequent calls. Similarly, `invalidateModelsCache` and `invalidateSessionListCache` are cleared only when necessary—specifically after model changes, session forks, compaction, or bash execution:

```typescript
// lib/rpc-manager.ts#L7-L8
let modelsCache: ModelList | null = null;
let sessionListCache: SessionList | null = null;

```

These caches significantly speed up API endpoints that enumerate available sessions or AI models by serving in-memory data instead of scanning the filesystem.

## Streaming and Real-Time Updates

To maintain UI responsiveness during long-running AI operations, Pi-Web leverages streaming protocols and differential updates.

### Controlled Streaming Architecture

Prompt, steer, and follow-up commands are streamed through the SDK while a `promptRunning` flag tracks execution state. The `notifyRunningChange()` function broadcasts status updates before and after operations, ensuring the UI reflects actual processing state without blocking the event loop:

```typescript
// Streaming pattern from lib/rpc-manager.ts#L30-L40
this.promptRunning = true;
this.notifyRunningChange();
await inner.prompt(...).finally(() => {
  this.promptRunning = false;
  this.notifyRunningChange();
});

```

### Efficient SSE Broadcasting

The **running-status broadcaster** (`subscribeRunningSessions`) computes the set of active session IDs and pushes Server-Sent Events (SSE) only when the snapshot changes, using JSON diffing to detect modifications. This reduces network chatter and prevents unnecessary browser re-renders, particularly critical when managing 50+ simultaneous sessions.

## Tool Loading and Extension Management

Pi-Web optimizes CPU overhead by controlling which tools are loaded into the SDK context.

### Conditional System Prompts

The `withExtensionTools` helper merges user-requested tool names with extension-provided tools without maintaining a hardcoded allow-list. When `toolNames` is empty, the system forces an empty system prompt via `setForceEmptySystemPrompt`, preventing the SDK from loading unnecessary tool implementations and reducing initialization overhead.

## Worktree and File System Boundaries

Resource constraints extend to filesystem access through strict path resolution and allow-listing.

### Cached Worktree Resolution

The [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts) module resolves git worktrees back to the main repository root and caches the result, grouping sessions by project. This bounds filesystem I/O to a specific directory hierarchy rather than allowing scans of arbitrary or massive directory trees. When removing dirty worktrees, the system returns a 409 status with a `dirty` flag, allowing the UI to prompt users before retrying expensive operations.

### File Access Allow-List

All file-serving APIs (`/api/files`, `/api/worktrees`) route through [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts), which restricts roots to known session CWDs, `~/pi-cwd-*` patterns, or explicit `allowFileRoot` calls. This guarantees the server only reads from small, trusted directory sets, preventing accidental recursive scans of large filesystems that would degrade performance.

## Summary

- **Lazy initialization** loads sessions, models, and file lists only when needed, with deliberate cache invalidation.
- **Global locks** (`__piStartLocks`) and idle timers (10-minute TTL) prevent duplicate startups and memory leaks from abandoned sessions.
- **Strategic caching** of session paths and model listings eliminates redundant filesystem reads.
- **Differential SSE updates** broadcast only changed running-session states, minimizing network payload and browser re-renders.
- **Bounded filesystem access** via worktree caching and path allow-lists prevents expensive scans of large directory hierarchies.

## Frequently Asked Questions

### How does Pi-Web prevent memory leaks from abandoned sessions?

Pi-Web wraps each SDK instance in an `AgentSessionWrapper` that registers itself in `globalThis.__piSessions` and starts a 10-minute idle timer via `resetIdleTimer`. If no activity occurs within this window, the wrapper automatically calls `destroy()`, releasing the underlying SDK resources and removing the entry from the global registry to prevent runaway memory usage.

### What caching mechanisms reduce filesystem I/O in Pi-Web?

The system maintains three primary caches: `cacheSessionPath` stores absolute file locations for session IDs, `modelsCache` holds AI model listings, and `sessionListCache` stores session enumerations. These are cleared only through specific invalidation functions (`invalidateModelsCache`, `invalidateSessionListCache`) triggered by mutating operations like model changes or bash execution, ensuring API endpoints serve data from memory rather than disk.

### How does Pi-Web handle concurrent session startups?

The `startRpcSession` function in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) uses a `Map` called `__piStartLocks` to track in-flight initialization promises. When multiple requests target the same session ID, subsequent calls receive the existing promise rather than spawning duplicate SDK instances, guaranteeing O(1) startup cost and preventing race conditions that would waste CPU and memory resources.

### Why does Pi-Web use differential SSE updates instead of polling?

The `subscribeRunningSessions` mechanism computes a snapshot of running session IDs and compares it against the previous state using JSON diffing. Updates are pushed via Server-Sent Events only when the set changes, reducing network overhead and browser re-render cycles compared to polling—critical for maintaining UI performance when managing dozens of active coding sessions.