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:
isCompactingstate becomestruewhen either start event arrivesisCompactingclears and the idle timer resets when either end event arrives
Event Flow from Agent to UI
- Agent initiates compaction → emits
compaction_start(modern) orauto_compaction_start(legacy) - SSE stream delivers event →
rpc-manager.tsmatches againstRUNNING_STATE_EVENT_TYPES useAgentSessionhook updates →setIsCompacting(true)triggers React re-render- UI components respond →
ChatWindow.tsxand others display the compaction spinner - Agent completes compaction → emits corresponding end event
- State cleanup occurs →
isCompactingclears, idle timer resets viaIDLE_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_*andcompaction_*event names for seamless operation across agent versions - Centralized definitions: Event type sets in
lib/rpc-manager.tscontrol running-state notifications and idle-timer behavior - Hook-based state:
useAgentSession.tsuses fall-through case statements to setisCompactingwithout 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.
Where should I look to modify compaction-related UI behavior?
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →