How the Agent-Native Polling Mechanism Syncs UI with SSE and HTTP Fallback
Agent-Native synchronizes the UI with server-side state through a hybrid transport layer that combines Server-Sent Events for real-time updates with HTTP polling as a resilient fallback, managed by the useDbSync hook.
The polling mechanism in BuilderIO/agent-native ensures that browser-based UIs remain consistent with database mutations initiated by background jobs, serverless functions, or other browser tabs. By implementing a module-level singleton transport that coordinates both push and pull channels, the system eliminates redundant network connections while guaranteeing eventual consistency across distributed processes.
The Dual-Channel Transport Architecture
Agent-Native employs a single transport (SyncTransport) that multiplexes two delivery channels. This design prioritizes speed through SSE while using polling to catch changes that occur outside the browser context.
Server-Sent Events (SSE) for Real-Time Updates
When the first component mounts useDbSync, the transport initiates a persistent connection to /_agent-native/events. As implemented in packages/core/src/client/use-db-sync.ts at lines 106-114, each incoming message triggers eventSource.onmessage, which parses the payload through normalizeEventPayload and broadcasts it to all subscribers via the internal fan method. This provides immediate UI updates for collaborative editing and live agent interactions.
HTTP Polling for Cross-Process Consistency
The fallback mechanism queries /_agent-native/poll?since=<version> at configurable intervals to capture writes performed by non-browser processes. According to the source code at lines 135-149, the poll() method executes fetchPollJson and updates the shared versionRef through applyVersion (lines 126-133), ensuring the since parameter always requests events newer than the last observed state. This guarantees that background jobs or serverless functions trigger UI refreshes even when SSE is unavailable.
Core Implementation in use-db-sync.ts
The synchronization logic resides in packages/core/src/client/use-db-sync.ts, which exports the useDbSync hook and manages the SyncTransport singleton.
Module-Level Singleton Management
The transport is a browser-tab singleton that persists across component lifecycles. When a component mounts, useEffect registers a subscriber with a unique Symbol identifier via transport.add(id, …) (lines 90-96). If this is the first subscriber, SyncTransport.start() (lines 92-100) initializes both the SSE connection and the poll loop. When all components unmount, transport.remove(id) triggers releaseTransport, tearing down connections to prevent stray network traffic (lines 302-311).
Version Coordination and Event Deduplication
Each subscriber maintains an independent cursor (subscriberVersion) to filter duplicate events. Inside the hook's onEvents handler (lines 208-225), incoming batches are filtered against this cursor before processing. The transport layer coordinates global state through applyVersion, which updates the shared versionRef after each successful poll, ensuring monotonic progression across the entire tab.
Targeted Cache Invalidation
When new events arrive, invalidateForEvents (lines 248-266) translates database changes into granular React Query invalidations. The function calls bumpChangeVersion for affected sources—such as ["action"], ["extension"], or ["app-state"]—triggering targeted re-fetches rather than broad cache clears. This minimizes rendering overhead while keeping displayed data authoritative.
Resilient Network Behavior
The transport implements adaptive strategies to balance responsiveness against resource consumption and error conditions.
Adaptive Polling Intervals
The transport dynamically selects the minimum poll interval requested by any active subscriber. If one component specifies interval: 1000 and another accepts the default 2000ms, the transport polls every second to satisfy the fastest requirement. When all subscribers set pauseWhenHidden: true, the transport suspends both SSE and polling while the document is hidden, resuming automatically upon visibility restoration.
Authentication Failure Backoff
To prevent aggressive retry loops against protected endpoints, the transport implements POLL_AUTH_FAILURE_COOLDOWN_MS. Upon receiving a 401 or 403 response from /_agent-native/poll, the system enters a cooldown period before the next request, reducing server load during authentication transitions or permission changes.
Implementation Example
Mount the hook at your application root or within specific components requiring synchronization:
import { useDbSync } from "@agent-native/core/client";
export default function Dashboard() {
// Initialize the shared transport; multiple instances share one connection
useDbSync({
// Optional: ignore events originating from this browser tab
ignoreSource: "my-tab-id",
// Custom handler for every raw event
onEvent: (evt) => console.log("Database change detected:", evt),
// Aggressive polling for this specific view
interval: 1000,
// Keep syncing when user switches tabs
pauseWhenHidden: false,
});
// Render data fetched via useQuery hooks that listen to versioned keys
return <AgentDataView />;
}
Because SyncTransport operates as a singleton, mounting useDbSync in multiple components does not create duplicate SSE connections or poll requests. The hook automatically invalidates relevant React Query caches when the server emits change events, keeping the UI synchronized without manual refetch logic.
Summary
- Hybrid delivery: Agent-Native combines SSE (push) and HTTP polling (pull) in
packages/core/src/client/use-db-sync.tsto ensure no database changes are missed, regardless of origin. - Singleton efficiency: The
SyncTransportmodule-level singleton manages one connection per browser tab, shared across alluseDbSyncinstances. - Version tracking: Monotonic versioning via
applyVersionand per-subscriber cursors prevents duplicate processing and ensures ordered event application. - Granular invalidation: The
invalidateForEventsfunction bumps source-specific change counters, triggering targeted React Query refreshes rather than full-cache resets. - Resilient defaults: Dynamic interval selection, pause-when-hidden support, and authentication failure backoff prevent resource exhaustion and thundering herds.
Frequently Asked Questions
What happens when the SSE connection drops?
The transport automatically falls back to the polling loop defined by fallbackInterval while attempting to reconnect. If SSE remains unavailable, the system continues operating on HTTP polling alone, ensuring UI consistency through repeated queries to /_agent-native/poll with the current since version parameter.
How does Agent-Native prevent duplicate UI updates when both SSE and polling are active?
The transport deduplicates events using a shared versionRef and per-subscriber cursors (subscriberVersion). When either channel delivers an event, the onEvents handler filters out previously processed versions before calling invalidateForEvents, ensuring each database change triggers exactly one UI refresh regardless of delivery channel.
Can multiple components use useDbSync without creating duplicate network requests?
Yes. The SyncTransport is a module-level singleton that registers subscribers using unique Symbol identifiers. Only the first mount initializes the SSE connection and poll loop, and only the final unmount tears them down. All components share the same transport instance while maintaining independent filtering cursors.
How does the polling mechanism handle updates from serverless functions or background jobs?
The HTTP poll endpoint /_agent-native/poll captures all database events since the requested version, regardless of which process initiated the write. When a serverless function or background job commits a transaction, the next poll request from the browser includes those events in the response, triggering invalidateForEvents and updating the UI accordingly.
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 →