How Dual-Socket Sync Works Between SocketService and the Core Transport in OpenHuman
OpenHuman maintains real-time consistency between its WebSocket core and Socket.IO UI layers by routing all events through a shared global event bus that acts as a single source of truth for both transport mechanisms.
The tinyhumansai/openhuman repository implements a sophisticated dual-socket sync pattern to bridge low-level network transport with high-level user interface components. This architecture ensures that events generated by the core transport layer are instantly reflected in the React-based UI while allowing user actions to propagate back to the core deterministically.
Architecture Overview
The dual-socket implementation consists of two independent communication layers that remain synchronized through a centralized event bus:
-
Core transport — A low-level WebSocket implementation located in
src/core/socketio.rsthat handles raw JSON-RPC messages between the Tauri host (or remote HTTP socket) and internal systems. This layer exposes theWebChannelEventtype for internal broadcasting. -
Socket service — A high-level Socket.IO manager defined in
src/openhuman/platform/socket/manager.rsthat serves the React UI and internal components such as webhook routing and voice transcription services.
The Core Transport Layer
The core transport operates as the authoritative communication endpoint for raw network traffic. According to the OpenHuman source code, this layer receives JSON-RPC messages and publishes structured events using the WebChannelEvent enum.
When the core generates state changes—such as tool execution results or turn deltas—it dispatches WebChannelEvent instances onto the global event bus via helper functions in src/openhuman/web_chat/publish_web_channel_event.rs. This decouples the network transport from business logic while maintaining event ordering guarantees.
The Socket Service Layer
The Socket service manages Socket.IO connections for UI clients and external integrations. At application startup, SocketManager::new() initializes a singleton instance stored in a global Arc<SocketManager> accessible through global_socket_manager().
This manager subscribes to the core's internal event bus and translates WebChannelEvent types into Socket.IO protocol payloads. For example, WebChannelEvent::ApprovalRequested maps to the Socket.IO event approval_request, while ArtifactPending becomes artifact_pending as observed in src/openhuman/web_chat/event_bus.rs.
Bidirectional Synchronization Flow
The dual-socket sync mechanism operates bidirectionally through two distinct pathways that share the same global event bus infrastructure.
Core to Socket Service
When the core transport needs to update the UI, it publishes events to the global bus:
- Core handlers call
publish_web_channel_event()with a specificWebChannelEventvariant. - The
SocketManagersubscriber receives the event through its registered callback. - The manager formats the payload for Socket.IO protocol compatibility.
- The event broadcasts to all connected Socket.IO clients.
This path ensures that state changes originating from the Tauri host or backend processes immediately appear in the React interface.
Socket Service to Core
Inbound messages from Socket.IO clients (such as webhook requests or tunnel registrations) flow in reverse:
- The Socket.IO router receives the incoming message.
- Code retrieves the global manager via
global_socket_manager(). - The manager calls
emit_webhook_incoming()or similar methods. - These methods invoke
crate::core::socketio::publish_web_channel_event()to place the event on the core bus. - Core-side handlers process the request as if generated internally.
The file src/openhuman/skills/webhooks/bus.rs demonstrates this pattern for webhook events specifically.
The Dual-Socket Guarantee
Because both layers interface with the identical global event bus rather than maintaining separate queues, the architecture guarantees deterministic ordering. Any event generated by the core—whether turn deltas, tool results, or artifact updates—triggers immediate Socket.IO broadcasts. Conversely, UI-initiated actions instantly become visible to core transport handlers.
This separation maintains clean architectural boundaries: the low-level transport remains decoupled from Socket.IO-specific protocol concerns while the shared bus ensures synchronization without race conditions.
Implementation Examples
Initializing the Global Manager
The singleton pattern ensures both layers reference the same SocketManager:
use crate::openhuman::platform::socket::manager::SocketManager;
use crate::openhuman::platform::socket::set_global_socket_manager;
use std::sync::Arc;
// Run at core startup
let mgr = Arc::new(SocketManager::new());
set_global_socket_manager(mgr);
Publishing Core Events to Socket.IO
Core components emit events that automatically reach Socket.IO clients:
use crate::core::socketio::{WebChannelEvent, publish_web_channel_event};
// Forward an artifact-ready event to all connected UI clients
publish_web_channel_event(WebChannelEvent::ArtifactReady {
artifact_id: "abc123".into(),
status: "ready".into(),
});
Handling Inbound Socket.IO Messages
The router forwards external requests back to the core:
use crate::openhuman::platform::socket::global_socket_manager;
// Inside the Socket.IO message handler
if let Some(mgr) = global_socket_manager() {
// Emit onto the core event bus for processing
mgr.emit_webhook_incoming(request);
}
Subscribing to Core Events
The Socket service registers callbacks during initialization:
use crate::core::socketio::WebChannelEvent;
use crate::openhuman::platform::socket::manager::SocketManager;
SocketManager::subscribe(move |ev: WebChannelEvent| {
// Translate to Socket.IO format and broadcast
manager.broadcast_socket_io(ev);
});
Summary
- OpenHuman uses a global event bus to synchronize its WebSocket core transport with the Socket.IO service layer.
- The core transport in
src/core/socketio.rspublishesWebChannelEventtypes that the SocketManager subscribes to insrc/openhuman/platform/socket/manager.rs. - A singleton manager pattern ensures both layers reference the same state authority through
global_socket_manager(). - Bidirectional routing converts between internal
WebChannelEventrepresentations and Socket.IO protocol messages. - This architecture maintains deterministic ordering while keeping low-level transport concerns decoupled from UI protocol handling.
Frequently Asked Questions
How does the dual-socket sync handle message ordering?
Both the core transport and Socket service publish events to the same global event bus implementation. Because they share this single queue rather than maintaining separate channels, events process in the exact order generated, preventing race conditions between UI updates and core state changes.
What is the role of WebChannelEvent in the synchronization process?
WebChannelEvent serves as the canonical event type exchanged between layers. Defined in the core transport, this enum represents all significant state changes (approvals, artifacts, webhooks) in a format agnostic to the final protocol. The SocketManager translates these internal events into Socket.IO-specific payloads for client consumption.
Why does OpenHuman use two separate socket layers instead of one?
The separation allows the core transport to remain protocol-agnostic and lightweight while the Socket service handles high-level concerns like room management, namespaces, and React integration. This decoupling enables the core to function independently of the UI technology stack while the shared event bus guarantees synchronization.
Where does the global event bus implementation reside?
The global event bus infrastructure spans multiple files: event type definitions live in src/core/socketio.rs, subscription management appears in src/openhuman/web_chat/event_bus.rs, and publishing helpers are implemented in src/openhuman/web_chat/publish_web_channel_event.rs. The webhook-specific bus logic appears in src/openhuman/skills/webhooks/bus.rs.
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 →