# How the Agent-Native Polling Mechanism Syncs UI with SSE and HTTP Fallback

> Learn how Agent-Native synchronizes UI with SSE and HTTP fallback. Discover the polling mechanism managed by the useDbSync hook for real-time updates.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: internals
- Published: 2026-06-28

---

**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`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main/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 `2000`ms, 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:

```tsx
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.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/use-db-sync.ts) to ensure no database changes are missed, regardless of origin.
- **Singleton efficiency**: The `SyncTransport` module-level singleton manages one connection per browser tab, shared across all `useDbSync` instances.
- **Version tracking**: Monotonic versioning via `applyVersion` and per-subscriber cursors prevents duplicate processing and ensures ordered event application.
- **Granular invalidation**: The `invalidateForEvents` function 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.