How Pi-Web Manages SSE Reconnection When a Page Refresh Occurs During a Stream

Pi-Web detects active agent streams by querying session state on component mount and automatically reestablishes the SSE connection to resume real-time updates without losing conversational context.

Real-time AI interfaces require resilient event streaming that survives browser refreshes and network interruptions. The pi-web repository solves this challenge through a coordinated client-server handshake that restores Server-Sent Events (SSE) connections immediately after a page reload. By synchronizing session state between the React frontend and Next.js API routes, the application seamlessly continues streaming agent responses even when the user refreshes mid-generation.

Detecting Active Streams on Component Mount

When ChatWindow mounts or remounts after a refresh, it immediately issues a GET /api/agent/[id] request to retrieve the current session state from app/api/agent/[id]/route.ts. The server responds with a state object containing the critical isStreaming boolean flag.

If isStreaming is true, the component knows an agent generation is already in progress and triggers the reconnection sequence. This check happens in a useEffect hook that runs once per session ID, ensuring the UI accurately reflects whether it needs to establish a new SSE connection or start fresh.

Restoring the SSE Connection

Upon detecting an active stream, ChatWindow invokes the useAgentSession hook to initiate SSE reconnection. The hook calls startSse(sessionId), which creates a new EventSource instance pointing to GET /api/agent/[id]/events.

This endpoint, defined in app/api/agent/[id]/events/route.ts, streams agent_message, agent_end, and compaction events to the client. By reconnecting to this endpoint, the UI resumes receiving the remaining tokens of the assistant's output exactly where the previous connection left off.

Synchronizing Auxiliary State

The initial state fetch provides more than just the streaming flag. The response includes thinkingLevel (indicating chain-of-thought depth) and isCompacting (showing whether a memory compaction operation is active).

The useAgentSession hook applies these values to local state immediately upon reconnection:

// hooks/useAgentSession.ts
function startSse(id: string) {
  const evtSource = new EventSource(`/api/agent/${id}/events`);
  evtSource.onmessage = e => handleAgentEvent(JSON.parse(e.data));
  // …cleanup logic omitted…
}

This synchronization ensures that visual indicators for thinking depth and compaction status remain accurate even after a hard refresh.

Handling Missed Events and Edge Cases

To prevent state drift during brief disconnections, useAgentSession implements a periodic reconciliation loop that polls GET /api/agent/[id] independently of the SSE stream. The hook also listens for browser visibilitychange and online events to trigger immediate state verification when the tab returns to focus or the network reconnects.

This dual-channel approach—primary SSE streaming with fallback HTTP polling—ensures that no critical agent events are lost if the EventSource connection drops or the page refresh occurs during a high-latency window.

Implementation Details

The reconnection logic centers on two key files. In components/ChatWindow.tsx, the mount effect determines whether to reconnect:

// components/ChatWindow.tsx
useEffect(() => {
  // On mount – fetch the latest session state
  fetch(`/api/agent/${sessionId}`)
    .then(r => r.json())
    .then(state => {
      // If a generation was in progress, reconnect the SSE stream
      if (state.isStreaming) {
        startSse(sessionId);               // opens /api/agent/[id]/events
        setThinkingLevel(state.thinkingLevel);
        setIsCompacting(state.isCompacting);
      }
    });
}, [sessionId]);

The server-side endpoints in app/api/agent/[id]/route.ts and app/api/agent/[id]/events/route.ts work together to maintain the session continuity required for this pattern.

Summary

  • Pi-web detects in-progress streams by checking isStreaming via GET /api/agent/[id] when ChatWindow mounts after a refresh.
  • The useAgentSession hook automatically reconnects to GET /api/agent/[id]/events to resume the SSE feed without user intervention.
  • Auxiliary state—including thinkingLevel and isCompacting—synchronizes immediately upon reconnection to maintain UI consistency.
  • A periodic reconciliation loop and browser event listeners provide resilience against missed events during temporary disconnections.
  • This architecture ensures that page refreshes never abort ongoing agent generations, preserving full conversational context.

Frequently Asked Questions

How does pi-web know when to reconnect the SSE stream after a refresh?

When ChatWindow mounts, it fetches the current session state from GET /api/agent/[id]. If the response contains isStreaming: true, the component immediately invokes startSse() from the useAgentSession hook to reestablish the connection to the events endpoint.

What happens to the agent's response if the user refreshes mid-stream?

The server continues generating the response regardless of client connections. When the page reloads and reconnects via GET /api/agent/[id]/events, the client receives all remaining agent_message events from the point of reconnection forward, allowing the UI to append new content seamlessly.

Does pi-web handle network interruptions differently than manual refreshes?

Yes. While both scenarios use the same startSse reconnection logic, network interruptions additionally trigger the reconciliation loop and browser online event listeners in useAgentSession.ts to verify state consistency before resuming the stream.

What server-side infrastructure is required to support this reconnection pattern?

The server must maintain durable session state—including isStreaming, thinkingLevel, and isCompacting—accessible via app/api/agent/[id]/route.ts, plus a persistent SSE stream endpoint at app/api/agent/[id]/events/route.ts that can handle multiple sequential connections for the same session.

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 →