How Real-Time Updates Are Delivered in Pi Web: Server-Sent Events Architecture Explained
Pi Web pushes live data to the browser via Server-Sent Events (SSE) through persistent HTTP streams that encode agent activity as JSON lines.
The agegr/pi-web repository implements a lightweight, robust mechanism for real-time updates using Server-Sent Events (SSE) rather than WebSockets. This approach streams agent-generated events—including assistant replies, tool calls, and compaction markers—directly to React clients via standard HTTP connections managed by per-session ReadableStream objects.
Per-Session SSE Endpoint
Individual agent sessions broadcast events through a dedicated route that maintains a long-lived connection for each client.
Session Event Stream (/api/agent/[id]/events)
Located in app/api/agent/[id]/events/route.ts, this endpoint handles GET requests by creating a ReadableStream that immediately sends a connection acknowledgment. It registers a listener on the AgentSession instance using session.onEvent, forwarding every event through encode(event) as a JSON-encoded SSE line. A periodic heartbeat (setInterval) prevents connection timeouts, and the stream cleans up automatically when the client aborts the request.
Global Session Monitoring
Beyond individual sessions, Pi Web exposes a broadcast channel for tracking active sessions across the application.
Running Sessions Stream (/api/agent/running/events)
The endpoint in app/api/agent/running/events/route.ts subscribes to the global subscribeRunningSessions registry exported from lib/rpc-manager.ts. It emits "started" or "stopped" events whenever an AgentSession begins or ends, allowing the UI to maintain a live list of running conversations without polling.
Backend Session Infrastructure
The underlying session management lives in lib/rpc-manager.ts, which maintains an AgentSessionWrapper map stored in globalThis.__piSessions. Each wrapper exposes an onEvent(callback) interface that triggers on every agent-generated event. The SSE routes act as thin proxies, subscribing to these callbacks and piping the data to the client’s EventSource.
Additionally, lib/session-reader.ts normalizes session data and resolves session paths for the SSE routes, ensuring consistent event formatting across the pipeline.
Client-Side Event Consumption
On the frontend, the useAgentSession hook in hooks/useAgentSession.ts instantiates a native EventSource pointing to the per-session endpoint. It parses incoming JSON payloads, updates local React state (including messages, isStreaming, and isCompacting), and gracefully reconnects on page visibility changes or network interruptions.
Implementation Examples
You can consume the SSE stream directly in a React component:
import { useEffect } from "react";
export function SessionEvents({ sessionId }: { sessionId: string }) {
useEffect(() => {
const es = new EventSource(`/api/agent/${sessionId}/events`);
es.onmessage = (e) => {
const data = JSON.parse(e.data);
console.log("Agent event:", data);
};
es.onerror = () => es.close();
return () => es.close();
}, [sessionId]);
return null;
}
For production use, leverage the built-in hook to handle state synchronization automatically:
import { useAgentSession } from "@/hooks/useAgentSession";
export function ChatWindow({ sessionId }: { sessionId: string }) {
const {
messages,
isStreaming,
sendMessage,
// …other state helpers
} = useAgentSession(sessionId);
// render `messages` etc.
}
Summary
- Protocol: Pi Web uses Server-Sent Events (SSE) over standard HTTP to deliver real-time updates without WebSocket overhead.
- Endpoints: Individual sessions stream via
app/api/agent/[id]/events/route.ts, while global session state broadcasts throughapp/api/agent/running/events/route.ts. - Backend:
lib/rpc-manager.tsmanagesAgentSessionWrapperinstances and exposesonEventcallbacks that SSE routes forward to clients. - Client: The
useAgentSessionhook inhooks/useAgentSession.tsopens anEventSource, parses JSON events, and syncs React state with automatic reconnection logic.
Frequently Asked Questions
What protocol does Pi Web use for real-time updates?
Pi Web uses Server-Sent Events (SSE), a unidirectional server-push technology over HTTP. This choice eliminates the need for WebSocket handshake overhead while providing automatic reconnection and event IDs natively supported by the browser’s EventSource API.
How does the server keep the SSE connection alive?
Each SSE route implements a periodic heartbeat using setInterval that sends keep-alive messages through the ReadableStream. This prevents proxies or load balancers from terminating idle connections while waiting for agent-generated events.
What happens when a session ends or the client disconnects?
When the client closes the connection or navigates away, the ReadableStream detects the abort signal and triggers cleanup logic to unsubscribe from the AgentSession’s onEvent listener. This prevents memory leaks in the globalThis.__piSessions registry maintained in lib/rpc-manager.ts.
Can I consume the SSE stream without using React?
Yes. The SSE endpoints are standard HTTP resources that return text/event-stream content. Any client capable of parsing SSE—including vanilla JavaScript, Python scripts using requests, or server-to-server consumers—can open an EventSource or equivalent to GET /api/agent/[id]/events and process the JSON-encoded lines.
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 →