How Pi Web Handles Session Compaction Events and State Synchronization

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, 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, 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:

// 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:

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:

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.

Displaying Summary Statistics

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

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 rebroadcasts all compaction events to ensure consistent state across clients.
  • Version-agnostic handling: 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 renders post-compaction statistics using the structured data defined in 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 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) that contains fields like summary, tokensBefore, and compression metrics. The lib/compaction-summary.ts component consumes this entry to render the final statistics to the user.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →