# How Pi Web Handles Session Compaction Events and State Synchronization

> Discover how Pi Web synchronizes compaction state using a React hook and RPC manager, ensuring UI desynchronization is prevented. Learn about event handling and state mirroring.

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

---

**Pi Web synchronizes compaction state by normalizing legacy and modern compaction events in a React hook, rebroadcasting them through an RPC manager, and unconditionally mirroring the compaction status to prevent UI desynchronization.**

The `agegr/pi-web` repository implements a resilient session management system that handles long-running compaction operations without leaving the UI in an inconsistent state. Understanding how Pi Web manages session compaction events and state synchronization reveals a design pattern that bridges legacy SDK events with modern React state management while ensuring backward compatibility.

## Event Architecture and Broadcasting

Pi Web uses a centralized event bus to distribute compaction status from the Pi SDK to all connected clients. The architecture separates the concerns of receiving raw SDK events and broadcasting them to the UI layer.

### RPC Manager as Event Hub

In [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), the server-side RPC manager intercepts incoming streaming events from the Pi SDK. When the manager receives events with types `"compaction"`, `"compaction_start"`, `"compaction_end"`, or `"set_auto_compaction"`, it rebroadcasts these payloads to all subscribed clients via the SSE (Server-Sent Events) endpoint at `app/api/agent/[id]/events/route.ts`.

This centralized rebroadcasting ensures that every UI component receives identical compaction state updates, regardless of which specific client initiated the operation or when they connected to the session.

## Client-Side State Synchronization

The client-side state management lives in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts), which maintains the `isCompacting` boolean flag and handles the full lifecycle of compaction events.

### Normalizing Legacy and Modern Events

The Pi SDK emits different event names depending on the version. Pi Web handles both legacy (`auto_compaction_start`, `auto_compaction_end`) and modern (`compaction_start`, `compaction_end`) event names within the same switch statement to ensure backward compatibility:

```typescript
// In hooks/useAgentSession.ts (≈ lines 1000‑1015)
case "auto_compaction_start":
case "compaction_start":
  setIsCompacting(true);
  break;
case "auto_compaction_end":
case "compaction_end":
  setIsCompacting(false);
  break;

```

### Unconditional State Mirroring

To prevent the UI from getting stuck in a "compacting" state if an event is dropped, the hook mirrors the compaction state unconditionally. When a `compaction_end` or `auto_compaction_end` event arrives, `setIsCompacting(false)` executes immediately, regardless of the previous state. This defensive programming pattern ensures that a missed network packet cannot permanently disable the "Stop compaction" button or leave progress indicators spinning indefinitely.

### Hydration on Mount

When `useAgentSession` initializes, it fetches the current session state from `/api/agent/[id]`. If the stored state indicates an active compaction (`state.isCompacting === true`), the hook immediately sets the local `isCompacting` flag to `true` before establishing the SSE connection. This synchronization guarantees that a page refresh in the middle of a compaction correctly restores the UI indicators.

## Controlling Compaction

Users can trigger or abort compaction through commands sent via the `sendAgentCommand` function exposed by the hook.

### Initiating Compaction

To start automatic compaction, the client sends a command to the back-end:

```typescript
await sendAgentCommand(sessionId, { 
  type: "set_auto_compaction", 
  enabled: true 
});

```

This command propagates through the RPC manager to the Pi SDK, which then begins the compaction process and emits the corresponding start events.

### Aborting Compaction

When a user clicks "Stop compaction," the hook sends an `abort_compaction` command:

```typescript
await sendAgentCommand(sessionId, { 
  type: "abort_compaction" 
});

```

The hook handles potential errors from this command gracefully, logging them to the console without throwing, which prevents the UI from crashing if the compaction has already completed or the session has disconnected.

## Rendering Compaction Metadata

After compaction completes, the back-end sends a `compaction` entry containing summary statistics. The UI renders this data using the specialized component in [`lib/compaction-summary.ts`](https://github.com/agegr/pi-web/blob/main/lib/compaction-summary.ts).

### Displaying Summary Statistics

The `CompactionSummary` component receives the summary object and formats metrics such as `tokensBefore` and `tokensAfter`:

```tsx
import { CompactionSummary } from '@/lib/compaction-summary';

function Message({ entry }: { entry: any }) {
  return entry.type === 'compaction' ? (
    <CompactionSummary 
      summary={entry.summary} 
      tokensBefore={entry.tokensBefore} 
    />
  ) : (
    <span>{entry.content}</span>
  );
}

```

## Summary

- **Centralized broadcasting**: [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) rebroadcasts all compaction events to ensure consistent state across clients.
- **Version-agnostic handling**: [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) normalizes both legacy (`auto_compaction_*`) and modern (`compaction_*`) event names into a single state flag.
- **Defensive state mirroring**: The client unconditionally sets `isCompacting` to `false` on end events to prevent UI desynchronization.
- **Command-based control**: Users initiate compaction via `set_auto_compaction` and abort via `abort_compaction` commands sent through the session hook.
- **Visual feedback**: [`lib/compaction-summary.ts`](https://github.com/agegr/pi-web/blob/main/lib/compaction-summary.ts) renders post-compaction statistics using the structured data defined in [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts).

## Frequently Asked Questions

### What is the difference between auto_compaction_start and compaction_start?

The `auto_compaction_start` event is the legacy name emitted by older versions of the Pi SDK, while `compaction_start` is the modern equivalent. Pi Web handles both identically in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) to maintain backward compatibility with different SDK versions without requiring users to upgrade.

### How does Pi Web prevent the UI from getting stuck if a compaction_end event is lost?

The hook implements unconditional state mirroring. When any `compaction_end` or `auto_compaction_end` event arrives, the code immediately calls `setIsCompacting(false)` regardless of the current state. This ensures that even if network issues cause event loss, a subsequent end event will always reset the UI indicators.

### Can users abort an ongoing compaction manually?

Yes. The `useAgentSession` hook exposes a `sendAgentCommand` function that accepts an `abort_compaction` command type. When invoked, this sends the abort signal to the back-end RPC manager, which forwards it to the Pi SDK to halt the compaction process. Errors during this operation are logged but do not disrupt the UI state.

### Where does the compaction summary data originate?

The summary data comes from the Pi SDK after compaction completes. The back-end includes this data in a `compaction` entry type (defined in [`lib/types.ts`](https://github.com/agegr/pi-web/blob/main/lib/types.ts)) that contains fields like `summary`, `tokensBefore`, and compression metrics. The [`lib/compaction-summary.ts`](https://github.com/agegr/pi-web/blob/main/lib/compaction-summary.ts) component consumes this entry to render the final statistics to the user.