# How Real-Time Updates Are Delivered in Pi Web: Server-Sent Events Architecture Explained

> Discover how Pi Web delivers real-time updates using Server-Sent Events SSE architecture. Learn about persistent HTTP streams and JSON lines for agent activity.

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

---

**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`](https://github.com/agegr/pi-web/blob/main/app/api/agent/running/events/route.ts) subscribes to the global `subscribeRunningSessions` registry exported from [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
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:

```typescript
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 through [`app/api/agent/running/events/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/agent/running/events/route.ts).
- **Backend**: [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) manages `AgentSessionWrapper` instances and exposes `onEvent` callbacks that SSE routes forward to clients.
- **Client**: The `useAgentSession` hook in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) opens an `EventSource`, 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`](https://github.com/agegr/pi-web/blob/main/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.