# How Real-Time Sync Keeps the UI in Sync with Agent Changes in Agent-Native

> Learn how Agent-Native's real-time sync uses SSE and polling to instantly update the UI with agent changes, ensuring seamless consistency with versioned events and targeted cache invalidation.

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

---

**Agent-Native uses a singleton `SyncTransport` that multiplexes Server-Sent Events (SSE) and polling to deliver versioned change events, triggering targeted React Query cache invalidation via per-source version counters to maintain instantaneous UI consistency with agent mutations.**

The **BuilderIO/agent-native** framework implements a sophisticated real-time sync mechanism that ensures UI components immediately reflect database changes triggered by LLM agents. By combining a shared transport layer with fine-grained cache invalidation, the system eliminates stale data while minimizing network overhead. This architecture centers on the `useDbSync` hook and a module-level `SyncTransport` singleton that coordinates event delivery across all components in a browser tab.

## Core Architecture of the Real-Time Sync System

### The useDbSync Hook

Located 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 890-960, the `useDbSync` hook serves as the primary interface for React components. When a component mounts, the hook registers a unique subscriber ID with a shared transport instance and attaches event handlers that trigger cache updates.

### SyncTransport Class

The `SyncTransport` class (defined at line 52 in the same file) manages the underlying network layer. It aggregates multiple subscribers into a single connection, ensuring **only one SSE or polling connection per browser tab** regardless of how many components use the hook. The class handles authentication interceptors, connection lifecycle, and automatic failover between transport methods.

### Version-Based Cache Invalidation

Instead of busting the entire cache, the system uses `bumpChangeVersion` (called at lines 555-558) to increment per-source version counters. These versions integrate with `useChangeVersion` from [`packages/core/src/client/use-change-version.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/use-change-version.ts), allowing React Query to refresh only the specific data sources that changed.

## Step-by-Step Event Flow

1. **Component Registration**

   When `useDbSync` mounts, it executes:

   ```typescript
   const id = Symbol("useDbSync");
   const transport = getOrCreateTransport(pollUrl, sseUrl);
   transport.add(id, { onEvents, pauseWhenHidden, interval, fallbackInterval });
   ```

   The `getOrCreateTransport` function leverages a module-level `transportRegistry` singleton to share connections across hooks.

2. **Transport Initialization**

   The `SyncTransport.start` method installs demo-mode and embed-auth interceptors, then opens an **EventSource** connection to `/_agent-native/events` (SSE) unless the tab is hidden or embed authentication is active. Simultaneously, it initiates a polling loop against `/_agent-native/poll`.

3. **Event Delivery and Processing**

   - **SSE Path**: The `onmessage` handler parses JSON, normalizes the payload via `normalizeEventPayload`, and extracts version metadata.
   - **Poll Path**: `fetchPollJson` executes GET requests with `?since=<lastVersion>` query parameters.
   
   Both paths invoke `applyVersion` to update the global `versionRef` and `fan` to distribute events to subscribers.

4. **Version Tracking and Deduplication**

   Each subscriber maintains a `subscriberVersion` property. The transport compares event versions against this value to prevent duplicate processing after reconnections, ensuring exactly-once delivery semantics for UI updates.

5. **Cache Invalidation**

   For each valid event, the hook calls `invalidateForEvents`, which:
   - Invokes `bumpChangeVersion(source, version)` to update per-source counters
   - Invalidates framework-level React Query keys including `["action"]`, `["extension"]`, and others
   - Executes optional `onEvent` callbacks for custom template logic

## Handling Screen Refreshes and Full Re-mounts

Beyond incremental updates, agents sometimes require complete UI re-renders. The `useScreenRefreshKey` hook (lines 779-842) listens for events with `source === "screen-refresh"` and increments a React key. Templates wrap content areas with `<div key={screenKey}>`, forcing a full component remount and data refetch when the agent triggers navigation or major state changes.

## Reliability and Performance Optimizations

**Dual-Transport Reliability**: When SSE connections drop due to network failures or server errors, the system automatically falls back to polling against `/_agent-native/poll`, ensuring no agent changes are lost.

**Visibility-Aware Resource Management**: If all subscribers specify `pauseWhenHidden: true` and the browser tab becomes inactive, the transport halts both SSE and polling to conserve CPU and network resources.

**Connection Aggregation**: The singleton `transportRegistry` prevents connection explosion when multiple components require sync data, maintaining a single network channel per tab even with dozens of mounted hooks.

## Summary

- **Singleton Transport**: The `SyncTransport` class ensures one connection per tab via `transportRegistry`, aggregated across all `useDbSync` hook instances.
- **Dual-Protocol Delivery**: SSE provides immediate event delivery with polling fallback for reliability.
- **Versioned Caching**: Per-source version counters (`bumpChangeVersion`) enable targeted cache invalidation rather than global cache busting.
- **Framework Integration**: Automatic invalidation of React Query keys like `["action"]` and `["extension"]` keeps UI data synchronized.
- **Screen Refresh Capability**: `useScreenRefreshKey` supports full UI remounts for navigation commands and major state changes.
- **Resource Efficiency**: Pause-when-hidden logic and connection sharing minimize network overhead and battery consumption.

## Frequently Asked Questions

### How does Agent-Native prevent multiple SSE connections when many components need real-time data?

The framework uses a module-level `transportRegistry` singleton that creates exactly one `SyncTransport` instance per browser tab. When multiple components mount `useDbSync`, each registers as a subscriber to the existing transport via `transport.add()`, sharing the underlying EventSource or polling connection rather than spawning new ones.

### What happens to real-time sync when the browser tab is hidden?

If all subscribers specify `pauseWhenHidden: true` in their options, the `SyncTransport` automatically pauses both SSE and polling loops when the document visibility changes to hidden. This conserves resources and reduces unnecessary network traffic, resuming immediately when the user returns to the tab.

### How does the system handle out-of-order events or reconnections?

The transport maintains a global `versionRef` tracking the highest event version seen, while each subscriber stores its own `subscriberVersion`. During `applyVersion`, the system compares incoming event versions against the subscriber's last seen version, processing only newer events and preventing duplicate updates after network reconnections.

### Why does Agent-Native use per-source version counters instead of invalidating the entire cache?

The `bumpChangeVersion` function updates granular counters by source rather than clearing the entire React Query cache. This approach, implemented in [`packages/core/src/client/use-change-version.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/use-change-version.ts), allows components to subscribe only to specific data sources they depend on, reducing network traffic and unnecessary re-renders while keeping the UI synchronized with agent changes.