How Real-Time Sync Keeps the UI in Sync with Agent Changes in Agent-Native
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 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, allowing React Query to refresh only the specific data sources that changed.
Step-by-Step Event Flow
-
Component Registration
When
useDbSyncmounts, it executes:const id = Symbol("useDbSync"); const transport = getOrCreateTransport(pollUrl, sseUrl); transport.add(id, { onEvents, pauseWhenHidden, interval, fallbackInterval });The
getOrCreateTransportfunction leverages a module-leveltransportRegistrysingleton to share connections across hooks. -
Transport Initialization
The
SyncTransport.startmethod 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. -
Event Delivery and Processing
- SSE Path: The
onmessagehandler parses JSON, normalizes the payload vianormalizeEventPayload, and extracts version metadata. - Poll Path:
fetchPollJsonexecutes GET requests with?since=<lastVersion>query parameters.
Both paths invoke
applyVersionto update the globalversionRefandfanto distribute events to subscribers. - SSE Path: The
-
Version Tracking and Deduplication
Each subscriber maintains a
subscriberVersionproperty. The transport compares event versions against this value to prevent duplicate processing after reconnections, ensuring exactly-once delivery semantics for UI updates. -
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
onEventcallbacks for custom template logic
- Invokes
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
SyncTransportclass ensures one connection per tab viatransportRegistry, aggregated across alluseDbSynchook 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:
useScreenRefreshKeysupports 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, 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.
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 →