# How Dual-Socket Sync Works Between SocketService and the Core Transport in OpenHuman

> Discover how OpenHuman's dual-socket sync ensures real-time consistency between Socket.IO UI and WebSocket core via a shared event bus for seamless communication.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: internals
- Published: 2026-08-30

---

**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.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/socketio.rs) that handles raw JSON-RPC messages between the Tauri host (or remote HTTP socket) and internal systems. This layer exposes the `WebChannelEvent` type for internal broadcasting.

- **Socket service** — A high-level Socket.IO manager defined in [`src/openhuman/platform/socket/manager.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/platform/socket/manager.rs) that 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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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:

1. Core handlers call `publish_web_channel_event()` with a specific `WebChannelEvent` variant.
2. The `SocketManager` subscriber receives the event through its registered callback.
3. The manager formats the payload for Socket.IO protocol compatibility.
4. 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:

1. The Socket.IO router receives the incoming message.
2. Code retrieves the global manager via `global_socket_manager()`.
3. The manager calls `emit_webhook_incoming()` or similar methods.
4. These methods invoke `crate::core::socketio::publish_web_channel_event()` to place the event on the core bus.
5. Core-side handlers process the request as if generated internally.

The file [`src/openhuman/skills/webhooks/bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/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`:

```rust
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:

```rust
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:

```rust
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:

```rust
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.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/socketio.rs) publishes `WebChannelEvent` types that the **SocketManager** subscribes to in [`src/openhuman/platform/socket/manager.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/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 `WebChannelEvent` representations 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`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/socketio.rs), subscription management appears in [`src/openhuman/web_chat/event_bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/web_chat/event_bus.rs), and publishing helpers are implemented in [`src/openhuman/web_chat/publish_web_channel_event.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/web_chat/publish_web_channel_event.rs). The webhook-specific bus logic appears in [`src/openhuman/skills/webhooks/bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/webhooks/bus.rs).