How Pi-Web Compaction SSE Events Work: Old `auto_*` vs New `compaction_*` Event Names

Pi-Web uses Server-Sent Events (SSE) to track session compaction by accepting both legacy auto_compaction_start/end and modern compaction_start/end event names in its RPC manager and React hooks.

The agegr/pi-web repository implements a dual-compatibility system for compaction SSE events that allows the UI to display loading states and manage idle timers regardless of which version of the underlying pi agent is running. This article breaks down exactly how both event naming schemes are handled, where they're defined in the source code, and how the state flows from backend events to UI components.

What Are Compaction SSE Events?

Compaction is the process where the pi agent rewrites a session's .jsonl file to prune old entries and reclaim disk space. Because this operation locks the session file, Pi-Web needs to:

  • Display a "compacting" spinner to indicate background activity
  • Disable user actions that would modify the session during the rewrite
  • Reset idle timers when compaction completes to prevent premature session timeout

The agent emits SSE events when compaction starts and finishes. Over time, the event naming convention changed, requiring Pi-Web to support both schemes.

The Two Event Naming Schemes

Pi Agent Version Start Event End Event
Legacy auto_compaction_start auto_compaction_end
Modern compaction_start compaction_end

Both pairs are accepted simultaneously, ensuring backward and forward compatibility.

Where Event Types Are Defined

The canonical list of compaction-related event types lives in lib/rpc-manager.ts, where two Set declarations control runtime behavior:

// lib/rpc-manager.ts
const RUNNING_STATE_EVENT_TYPES = new Set([
  "agent_start",
  "agent_end",
  "agent_settled",
  "auto_compaction_start",
  "auto_compaction_end",
  "compaction_start",
  "compaction_end",
]);

const IDLE_RESET_EVENT_TYPES = new Set([
  "agent_end",
  "agent_settled",
  "auto_compaction_end",
  "compaction_end",
]);

RUNNING_STATE_EVENT_TYPES — triggers notifyRunningChange() to refresh the sidebar's active sessions list.

IDLE_RESET_EVENT_TYPES — invokes resetIdleTimer() to prevent session timeout after background operations complete.

How useAgentSession.ts Consumes Both Event Names

The useAgentSession hook in hooks/useAgentSession.ts switches on event types using fall-through case statements to handle both naming conventions identically:

// hooks/useAgentSession.ts
case "auto_compaction_start":
case "compaction_start":
  setIsCompacting(true);
  break;

case "auto_compaction_end":
case "auto_compaction_end":
  setIsCompacting(false);
  resetIdleTimer();
  break;

This pattern ensures:

  • isCompacting state becomes true when either start event arrives
  • isCompacting clears and the idle timer resets when either end event arrives

Event Flow from Agent to UI

  1. Agent initiates compaction → emits compaction_start (modern) or auto_compaction_start (legacy)
  2. SSE stream delivers event → rpc-manager.ts matches against RUNNING_STATE_EVENT_TYPES
  3. useAgentSession hook updates → setIsCompacting(true) triggers React re-render
  4. UI components respond → ChatWindow.tsx and others display the compaction spinner
  5. Agent completes compaction → emits corresponding end event
  6. State cleanup occurs → isCompacting clears, idle timer resets via IDLE_RESET_EVENT_TYPES

Manual Compaction Trigger

The "Compact" button bypasses the SSE flow for synchronous feedback. It POSTs to /api/agent/[id]/compact and disables automatically while the request is in-flight:

// Component example using the compact endpoint
const compactSession = async (sessionId: string) => {
  await fetch(`/api/agent/${sessionId}/compact`, { method: "POST" });
  // Button disabled state handled by request lifecycle, no SSE needed
};

React Hook Implementation Pattern

For custom components needing compaction awareness, subscribe to the SSE stream and mirror the case handling from useAgentSession.ts:

useEffect(() => {
  const sse = new EventSource(`/api/agent/${sessionId}/events`);
  
  sse.onmessage = (event) => {
    const data: AgentEvent = JSON.parse(event.data);
    
    switch (data.type) {
      case "compaction_start":
      case "auto_compaction_start":
        setIsCompacting(true);
        break;
      case "compaction_end":
      case "auto_compaction_end":
        setIsCompacting(false);
        resetIdleTimer();
        break;
    }
  };
  
  return () => sse.close();
}, [sessionId]);

Key Source Files Reference

File Purpose
lib/rpc-manager.ts Defines RUNNING_STATE_EVENT_TYPES and IDLE_RESET_EVENT_TYPES with both old and new event names
hooks/useAgentSession.ts SSE consumer implementing the dual case statements for isCompacting state
components/ChatWindow.tsx UI component receiving events via handleAgentEvent to sync local state
AGENTS.md Design documentation explaining the rationale for dual event name support

Summary

  • Dual compatibility: Pi-Web accepts both auto_compaction_* and compaction_* event names for seamless operation across agent versions
  • Centralized definitions: Event type sets in lib/rpc-manager.ts control running-state notifications and idle-timer behavior
  • Hook-based state: useAgentSession.ts uses fall-through case statements to set isCompacting without duplicating logic
  • End-to-end flow: Events propagate from agent → SSE stream → RPC manager → React hook → UI spinner and disabled actions

Frequently Asked Questions

Why does Pi-Web support both old and new compaction event names?

The pi agent SDK changed its event naming convention at some point. Rather than forcing users to upgrade agent and web UI in lockstep, Pi-Web includes both auto_compaction_* and compaction_* in its event type sets. This ensures the UI works correctly regardless of which agent version is connected.

How does the idle timer interact with compaction events?

Only end events reset the idle timer. IDLE_RESET_EVENT_TYPES includes auto_compaction_end and compaction_end but excludes the start events. This prevents the idle timer from being continuously reset while a long-running compaction keeps the session technically "active" but unresponsive to user input.

Start with hooks/useAgentSession.ts for the core state machine, then check components/ChatWindow.tsx for the UI implementation. The handleAgentEvent function in ChatWindow.tsx receives the same events and maintains its own isCompacting state for local UI controls.

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 →