SSEBroadcaster for Real-Time UI Updates: Architecture and Implementation in Claude-Mem

The SSEBroadcaster in Claude-Mem manages Server-Sent Events (SSE) connections by maintaining a Set of active Express Response objects and broadcasting JSON payloads directly to all connected clients in a single pass, enabling instantaneous UI synchronization without polling overhead.

The Claude-Mem repository implements a lightweight real-time communication layer using the SSEBroadcaster class to push live updates to the viewer UI. This architecture eliminates traditional polling overhead by leveraging Server-Sent Events (SSE) to stream domain-specific events directly from the worker service to connected browsers. Understanding the SSEBroadcaster for real-time UI updates reveals how Claude-Mem maintains synchronized state across multiple viewer clients with minimal latency.

Core Architecture Layers

The SSEBroadcaster operates through three distinct layers that manage client lifecycle, message distribution, and service integration.

Client Management Layer

At the foundation, the SSEBroadcaster maintains a Set<SSEClient> containing active Express Response objects. When a browser connects to the /stream endpoint defined in src/services/worker/http/routes/ViewerRoutes.ts, the broadcaster invokes addClient(res) to register the connection. The implementation attaches a close event listener to automatically trigger removeClient when the browser disconnects, preventing memory leaks. Upon successful registration, the broadcaster immediately transmits a connected event to confirm the SSE stream is active.

Event Broadcasting Layer

The broadcast(event) method in src/services/worker/SSEBroadcaster.ts handles message distribution through a single-pass iteration over the client set. For each event, the method constructs a JSON payload enriched with a server-side timestamp, formats it according to the SSE protocol, and writes directly to each client's response stream using client.write(data). This direct-write approach ensures all connected viewers receive identical snapshots simultaneously without intermediate queuing or batching delays.

Integration Points

The SSEBroadcaster serves as a shared dependency across multiple high-level services:

  • ViewerRoutes: Creates the /stream endpoint and pushes initial initial_load and processing_status events upon client connection.
  • SessionEventBroadcaster: Translates domain actions into SSE events including new_prompt, session_started, observation_queued, and session_completed.
  • WorkerService: Invokes broadcastProcessingStatus() to emit real-time queue depth updates via processing_status events whenever the internal state changes.

Real-Time Data Flow

The SSEBroadcaster facilitates a continuous data pipeline from server to browser:

  1. Client Connection: Browser opens EventSource to GET /stream → ViewerRoutes calls SSEBroadcaster.addClient(res).
  2. Warm-up Payload: Server immediately sends initial_load (project list) and current processing_status.
  3. Event Trigger: Business logic fires (e.g., new prompt arrives) → SessionEventBroadcaster calls SSEBroadcaster.broadcast().
  4. Client Update: All connected browsers receive the event via open EventSource, update React state, and re-render UI instantly.
  5. Disconnection: Browser closes tab → close event triggers SSEBroadcaster.removeClient, cleaning up the response object.

Because the broadcaster writes directly to the raw Response stream, there is no JSON-API wrapper or polling overhead; the UI stays in sync with the worker's internal state with minimal latency.

Implementation Examples

Registering an SSE Client

When a viewer connects to the stream endpoint, the Express response is configured for SSE and registered with the broadcaster:

// In src/services/worker/http/routes/ViewerRoutes.ts
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

this.sseBroadcaster.addClient(res);

Broadcasting Custom Events

Any service can emit real-time updates by invoking the broadcast method with a typed event payload:

// Example: Notifying the UI when an observation is stored
this.sseBroadcaster.broadcast({
  type: 'observation_saved',
  observationId: 'obs-12345',
  project: 'claude-mem',
  timestamp: Date.now()
});

Client-Side Event Consumption

The browser connects to the SSE endpoint and listens for specific event types to update the UI state:

const evtSource = new EventSource('http://localhost:37777/stream');

evtSource.addEventListener('new_prompt', (ev) => {
  const data = JSON.parse(ev.data);
  // Update React state with the new prompt information
  updateUI(data);
});

Broadcasting Processing Status

The worker service automatically emits queue status updates whenever the processing state changes:

// Inside WorkerService.broadcastProcessingStatus()
this.sseBroadcaster.broadcast({
  type: 'processing_status',
  isProcessing: this.isProcessing,
  queueDepth: this.queue.length
});

Summary

  • The SSEBroadcaster maintains a Set of active Express Response objects to manage persistent SSE connections.
  • The broadcast() method writes JSON payloads directly to all connected clients in a single pass, ensuring synchronized real-time updates.
  • Integration points include ViewerRoutes for the /stream endpoint, SessionEventBroadcaster for domain events, and WorkerService for processing status.
  • Automatic cleanup via removeClient prevents memory leaks when browsers disconnect from the SSE stream.

Frequently Asked Questions

How does SSEBroadcaster handle client disconnections?

The SSEBroadcaster attaches a close event listener to each Express Response object upon registration. When a browser closes the connection or navigates away, the close event fires and automatically invokes removeClient to delete the response from the internal Set, preventing memory leaks and stale connections.

What types of events can be broadcast through SSEBroadcaster?

The SSEBroadcaster supports any JSON-serializable event payload. Common event types used in Claude-Mem include connected, initial_load, processing_status, new_prompt, session_started, observation_queued, session_completed, and observation_saved. The type field in the payload allows the client-side EventSource to route events to appropriate handlers.

How does the SSEBroadcaster differ from WebSocket implementations?

Unlike WebSockets, which require bidirectional message framing and protocol negotiation, the SSEBroadcaster leverages HTTP's native Server-Sent Events protocol. This approach uses standard Express Response objects with text/event-stream headers, eliminating the need for separate WebSocket libraries or connection upgrade logic. The architecture is unidirectional (server-to-client), making it ideal for broadcasting state updates without client-to-server messaging overhead.

Where is the SSEBroadcaster instantiated in the Claude-Mem codebase?

The SSEBroadcaster is instantiated as a singleton within the worker service architecture and injected into dependent services. It is created in the service composition layer and passed to ViewerRoutes for the HTTP endpoint handling, SessionEventBroadcaster for domain event translation, and WorkerService for processing status updates. This dependency injection pattern ensures a single source of truth for all SSE communications across the worker service.

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 →