SSE Reconnection for Page Refresh Mid-Stream in the Chat Window: How It Works
The ChatWindow component preserves streaming continuity during page refreshes through a retry-aware AgentEventConnection helper and a useAgentSession hook with grace-period logic.
When users refresh the page while an AI response is still streaming, the application must seamlessly resume without losing in-progress generation. This article explores how the agegr/pi-web repository implements robust SSE reconnection for page refresh mid-stream using a lightweight connection manager and guarded lifecycle predicates.
Core Architecture: AgentEventConnection and useAgentSession
The reconnection system depends on two coordinated components. AgentEventConnection handles raw EventSource operations and implements passive retry logic, while useAgentSession provides the hook-level context and "maintain" predicates that determine when reconnection should occur.
This separation keeps transport concerns isolated from React lifecycle management. The connection manager never directly accesses component state; instead, it receives configuration callbacks that encapsulate session-specific logic.
Error Handling and Scheduled Retries
When the EventSource emits an error, AgentEventConnection marks the connection as failed and invokes scheduleRetry in lib/agent-event-connection.ts [lines 151-190]:
private scheduleRetry(sessionId: string): void {
if (!this.options.shouldMaintain(sessionId)) return;
const timer = setTimeout(() => {
this.retry = null;
this.maintain(sessionId); // re-creates the EventSource
}, this.options.reconnectDelayMs);
this.retry = { sessionId, timer };
}
The retry only proceeds if shouldMaintain(sessionId) returns true. Any successful connection clears pending retries via stopRetrying [lines 191-197].
Grace-Period Handling for Component Remounting
The critical challenge for SSE reconnection during page refresh is that the component unmounts and remounts, potentially destroying and recreating the connection object. The solution uses a grace-period flag that persists across remounts.
In hooks/useAgentSession.ts, when state.isStreaming === true, the hook sets eventStreamGraceActiveRef.current = true [lines 54-62]. This flag extends the maintenance window even when the component briefly disappears.
The shouldMaintain predicate evaluates three conditions:
shouldMaintain: (sid) =>
sessionHookMountedRef.current &&
sessionIdRef.current === sid &&
(agentRunningRef.current || eventStreamGraceActiveRef.current),
By including eventStreamGraceActiveRef.current, the predicate returns true during the grace period, allowing scheduleRetry to proceed with reconnection.
Reconnection Flow After Page Refresh
The complete SSE reconnection for page refresh mid-stream sequence works as follows:
-
Pre-refresh state: An active
EventSourcestreams events for a given session ID.eventStreamGraceActiveRefis true because streaming is in progress. -
Refresh triggered: The browser tears down the page. The old
EventSourceconnection closes, triggeringsource.onerrorin the dying instance. -
Retry scheduled: The final error handler calls
scheduleRetry, which checksshouldMaintain. Because the grace flag is still true, a timer is set forEVENT_STREAM_RECONNECT_DELAY_MS. -
Post-refresh remount: The new page instance runs
useAgentSession, creating a freshAgentEventConnection. The scheduled retry fires, or the new instance detects the active session and callsmaintain(sessionId). -
Stream resumes: A new
EventSourceopens to/api/agent/{sessionId}/events. The server, tracking the session, returns remaining events from the continuation point.
This behavior is documented in AGENTS.md under "SSE reconnect on page refresh mid-stream".
Configuration Parameters
The hook configures the connection with two timing constants in hooks/useAgentSession.ts [lines 63-65]:
| Parameter | Purpose | Default Context |
|---|---|---|
readinessTimeoutMs |
How long to wait for server readiness before failing | Initial connection |
reconnectDelayMs |
Delay before retry attempt after connection failure | Error recovery |
These values balance responsiveness against server load. Immediate retries risk thundering herds; excessive delays hurt perceived responsiveness.
Hook Initialization Pattern
The useAgentSession hook lazily instantiates AgentEventConnection using a ref to preserve the instance across renders:
const eventConnectionRef = useRef<AgentEventConnection | null>(null);
if (!eventConnectionRef.current) {
eventConnectionRef.current = new AgentEventConnection({
createSource: (sid) => new EventSource(`/api/agent/${encodeURIComponent(sid)}/events`),
onEvent: (e) => handleAgentEventRef.current?.(e as AgentEvent),
shouldMaintain: (sid) =>
sessionHookMountedRef.current &&
sessionIdRef.current === sid &&
(agentRunningRef.current || eventStreamGraceActiveRef.current),
readinessTimeoutMs: EVENT_STREAM_READY_TIMEOUT_MS,
reconnectDelayMs: EVENT_STREAM_RECONNECT_DELAY_MS,
onUnexpectedError: (err) => console.error(err),
});
}
The createSource callback ensures each reconnection uses a fresh EventSource instance, as the standard API does not support reuse after closure.
Key Source Files
| File | Responsibility |
|---|---|
lib/agent-event-connection.ts |
Raw SSE wrapper, error handling, retry scheduling with scheduleRetry and stopRetrying |
hooks/useAgentSession.ts |
Hook lifecycle, grace-period flag management, shouldMaintain predicate definition |
AGENTS.md |
Architectural documentation for SSE reconnection behavior |
Summary
-
AgentEventConnectionprovides transport-agnostic SSE management with configurable retry delays and maintenance predicates. -
useAgentSessionsupplies the "maintain" logic that keeps connections alive during page refreshes through a grace-period flag. -
Grace-period flags bridge component remounts, ensuring scheduled retries execute even when the original connection object is destroyed.
-
Session ID persistence allows the server to resume streaming from the correct continuation point after reconnection.
-
The entire mechanism operates without explicit user action—refreshing the page automatically triggers recovery within
EVENT_STREAM_RECONNECT_DELAY_MS.
Frequently Asked Questions
How does the system know when to retry versus give up?
The shouldMaintain predicate controls this decision. It returns false if the hook is unmounted without the grace flag, the session ID mismatches, or the agent has stopped. Only when all conditions hold will scheduleRetry proceed. This prevents retries for abandoned sessions.
What happens if the server is slow to respond after refresh?
The readinessTimeoutMs parameter places an upper bound on initial connection attempts. If exceeded, the connection fails and enters the standard retry loop. Subsequent retries use reconnectDelayMs to pace attempts.
Is the grace period time-bounded or event-bounded?
According to the AGENTS.md documentation and hooks/useAgentSession.ts implementation, the grace flag is event-bounded—it activates when streaming begins and deactivates once the stream concludes normally. There is no explicit timeout; the flag clears when the final event arrives.
Can multiple tabs streaming the same session interfere with each other?
Each tab maintains its own AgentEventConnection instance with independent EventSource connections. The server treats these as separate consumers of the same session event stream. No client-side coordination prevents duplicate output across tabs; this is by design for simplicity.
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 →