How Pi-Web Handles Running State Polling and Reconciliation During Visibility Changes
Pi-Web keeps the UI synchronized with backend AgentSessions by combining 15-second polling intervals with visibilitychange and online event listeners that trigger immediate state reconciliation whenever users return to backgrounded tabs or recover from network loss.
Pi-Web implements a robust running state polling and reconciliation system to maintain consistency between the client interface and server-side agent processes. When browser tabs are hidden or network connectivity drops, the application risks missing critical Server-Sent Events (SSE) that update streaming status. The reconciliation architecture in hooks/useAgentSession.ts eliminates this drift by proactively querying the server whenever environmental conditions change.
The Three-Layer Reconciliation Architecture
The system employs a "reconciliation net" consisting of periodic polling, visibility detection, and network recovery handlers. Together, these mechanisms ensure that components/ChatWindow.tsx never displays stale streaming bubbles after the underlying agent has finished processing.
Periodic Background Polling
When agentRunning is true, a useEffect hook establishes a baseline polling mechanism using setInterval. Every AGENT_STATE_RECONCILE_MS (15 seconds), the system invokes reconcileAgentState to fetch the current status from app/api/agent/[id]/route.ts. This interval provides a safety net during active sessions, ensuring the client does not diverge from the server truth even when the tab remains continuously visible.
// Inside hooks/useAgentSession.ts
useEffect(() => {
if (!agentRunning) return;
const reconcile = () => {
const sid = sessionIdRef.current;
if (sid) void reconcileAgentState(sid);
};
// Run every 15 seconds
const interval = setInterval(reconcile, AGENT_STATE_RECONCILE_MS);
// Run immediately when the tab becomes visible again
const onVisible = () => {
if (document.visibilityState === "visible") reconcile();
};
document.addEventListener("visibilitychange", onVisible);
// Run when the browser goes online
window.addEventListener("online", reconcile);
return () => {
clearInterval(interval);
document.removeEventListener("visibilitychange", onVisible);
window.removeEventListener("online", reconcile);
};
}, [agentRunning, reconcileAgentState]);
Visibility Change Detection
The system registers a document.addEventListener("visibilitychange", onVisible) handler that checks document.visibilityState whenever the user switches tabs or minimizes the browser. Upon detecting the "visible" state, the handler immediately executes reconcileAgentState without waiting for the next 15-second polling cycle. This catches SSE events that were dropped while the tab was backgrounded, preventing the UI from displaying a "streaming" indicator for an agent that has already completed its task.
Network Recovery Handling
Pi-Web also listens for window.addEventListener("online", reconcile) events. When the browser detects restored connectivity after an interruption, it forces an immediate call to reconcileAgentState. This ensures that temporary disconnections do not leave lib/rpc-manager.ts state references out of sync with the actual AgentSession status on the server.
The reconcileAgentState Implementation
The core reconciliation logic resides in the reconcileAgentState function, which queries /api/agent/${encodeURIComponent(sid)} to retrieve the AgentStateResponse. It compares the server-returned running flag and detailed state object (including isStreaming, isPromptRunning, and isCompacting flags) against the client's current references.
If the server reports that the agent is idle while the client still believes streaming is active, the function invokes finishPromptWithoutStream to cleanly close the UI state:
// Core reconciliation logic (simplified)
async function reconcileAgentState(sid: string) {
const res = await fetch(`/api/agent/${encodeURIComponent(sid)}`);
if (!res.ok) return;
const { running, state } = await res.json();
// If server says idle but client still thinks streaming, finish prompt
if (!running || !(state?.isStreaming || state?.isPromptRunning || state?.isCompacting)) {
await finishPromptWithoutStream(sid, promptRunIdRef.current);
}
}
Preventing State Drift
This running state polling and reconciliation strategy specifically addresses the risk of state divergence caused by browser throttling of background tabs. When a user returns to a tab after an extended absence, the visibility-change trigger ensures Pi-Web immediately contacts the /api/agent/<sid> endpoint rather than waiting for the next poll. By comparing the server's authoritative state against local refs and calling finishPromptWithoutStream when discrepancies are detected, the system guarantees that components/ChatWindow.tsx cannot remain stuck in a loading state.
Summary
- 15-second polling via
setIntervalprovides baseline state synchronization during active agent sessions. visibilitychangeevent listener triggers immediatereconcileAgentStatecalls when users return to backgrounded tabs.onlineevent listener forces reconciliation upon network recovery to handle missed SSE events during outages.reconcileAgentStateinhooks/useAgentSession.tsqueries the agent endpoint and invokesfinishPromptWithoutStreamto resolve stale streaming states.- This three-layer approach ensures the UI never displays incorrect "streaming" status when agents have completed while the tab was hidden.
Frequently Asked Questions
How often does Pi-Web poll the running agent state?
The system polls every 15 seconds via setInterval when agentRunning is true, as defined by the AGENT_STATE_RECONCILE_MS constant. This interval provides a balance between real-time accuracy and server load, acting as a fallback when visibility or network events do not trigger earlier reconciliation.
What happens when a user switches back to a backgrounded tab?
When the visibilitychange event fires and document.visibilityState equals "visible", Pi-Web immediately executes reconcileAgentState without waiting for the next polling interval. This fetches the current status from /api/agent/<sid> and corrects any UI state that drifted due to missed SSE events while the tab was inactive.
How does Pi-Web handle network interruptions?
The application listens for the online event on the window object. When connectivity is restored, it forces an immediate reconciliation by querying the server endpoint, ensuring the client state resynchronizes with the backend AgentSession before the user interacts with the interface again.
What endpoint does the reconciliation logic query?
The reconcileAgentState function queries /api/agent/<sid> (implemented in app/api/agent/[id]/route.ts), which returns an AgentStateResponse containing the running boolean and detailed state flags. The client uses this response to determine whether to invoke finishPromptWithoutStream and close stale streaming indicators.
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 →