Tauri Desktop Shell vs Core Rust Crate Separation in OpenHuman
OpenHuman splits its Tauri desktop shell from its core Rust crate using a JSON-RPC bridge, allowing the UI to run as a thin host while the core implements all business logic headlessly.
The OpenHuman project by TinyHumansAI follows a clean architectural pattern common in modern Rust applications: the user-facing desktop interface and the application logic live in entirely separate layers. This separation enables headless deployments, independent updates, and strict security boundaries.
Architectural Overview
OpenHuman divides responsibilities between two distinct layers:
| Layer | Responsibility |
|---|---|
| Tauri Desktop Shell | Hosts React UI, manages window lifecycle, spawns and monitors core process, provides RPC bridge |
| Core Rust Crate | Implements all domains (agents, memory, tools, security), exposes JSON-RPC server, runs headless |
The Tauri shell in app/src-tauri/ never directly accesses core internals. Instead, it launches the core as a subprocess and communicates exclusively through a JSON-RPC channel.
The Tauri Desktop Shell Layer
The shell's primary job is to provide a native desktop window for the React frontend while acting as a dumb pipe to the core.
Key Source Files
app/src-tauri/tauri.conf.json– Tauri configuration and build settingsapp/src-tauri/src/lib.rs– Tauri command definitions exposed to the frontendapp/src-tauri/src/core_process.rs– Core process spawning and lifecycle management
Core Process Spawning
The shell starts the core via the start_core_process command defined in app/src-tauri/src/lib.rs:
// app/src-tauri/src/lib.rs
#[tauri::command]
pub async fn start_core_process(state: State<'_, AppState>) -> Result<(), String> {
// Spawn the core as an async task
let core_handle = core_process::CoreProcessHandle::spawn().await?;
state.set_core_handle(core_handle);
Ok(())
}
This async command creates a CoreProcessHandle that runs the core binary as a managed subprocess. The shell then reads a bearer token from a temporary file (via core_rpc_token) to authenticate subsequent RPC calls.
RPC Bridging
The shell exposes a thin JSON-RPC relay to the UI:
// app/src/lib/coreRpcClient.ts
export async function coreRpc<T>(method: string, params: any): Promise<T> {
// Tauri-provided invoke bridges to the Rust command
return invoke('core_rpc', { method, params });
}
The corresponding Rust implementation in app/src-tauri/src/core_rpc.rs forwards these calls to the running core process over HTTP.
The Core Rust Crate Layer
The core crate at the repository root contains all business logic in pure Rust, with no Tauri dependencies. It can run headlessly via CLI, Docker, or cloud deployment.
Key Source Files
src/lib.rs– Crate entry point; buildsCoreBuilderand publishes RPC serversrc/core/mod.rs– Core runtime, dispatcher, and subsystem registrationsrc/core/jsonrpc.rs– JSON-RPC request handlingsrc/embed/mod.rs– PublicHarness::builderAPI for host configuration
JSON-RPC Server Implementation
The core exposes its functionality through a typed JSON-RPC server in src/core/jsonrpc.rs:
// src/core/jsonrpc.rs
pub async fn handle_rpc(req: RpcRequest) -> Result<RpcResponse, RpcError> {
match req.method.as_str() {
"tools.run_tool" => tools::run_tool(req.params).await,
// … other methods
_ => Err(RpcError::MethodNotFound),
}
}
The core implements domains including agents, memory, tools, and security. The Harness runs conversation turns, the Embed layer exposes typed APIs, and the Runtime manages background services.
Event Broadcasting Back to UI
Core-generated events flow back to the desktop UI through Socket.IO bridging:
// src/core/socketio.rs
pub fn broadcast_attention(event: AttentionEvent) {
// The Tauri shell subscribes to this event name
emit_to_all("runtime:attention", serde_json::to_value(event).unwrap());
}
The shell listens for these events through Tauri's event system (window.emit) and forwards them to the React frontend.
How the Layers Communicate
The Tauri desktop shell and core Rust crate establish communication through a multi-step handshake:
- Process Launch – Shell calls
start_core_process, which spawns the core binary - Authentication – Core writes bearer token to temp file; shell reads via
core_rpc_token - Request Flow – UI →
core_rpcTauri command →relay_http_rpc→ core's/rpcendpoint - Event Flow – Core emits via Socket.IO → Tauri event system → React frontend
This HTTP-over-localhost approach keeps boundaries explicit. The shell's core_rpc implementation maintains an allow-list of safe methods (primarily tools.* namespaced calls), preventing untrusted UI code from accessing sensitive core internals.
Benefits of the Separation
This Tauri shell and core crate split delivers concrete engineering advantages:
- Isolation – React/TypeScript UI code never directly touches core internals; only RPC methods are exposed
- Modularity – Same core runs headless without any UI dependencies
- Security – Shell controls RPC surface area through explicit method allow-listing
- Upgradability – Core binary updates independently; shell simply reloads new binary
- Testability – Core logic tested without heavy desktop automation
Summary
- OpenHuman's Tauri desktop shell in
app/src-tauri/hosts the React UI and manages the core lifecycle without containing business logic - The core Rust crate at the repository root implements all domains and exposes a JSON-RPC server via
src/core/jsonrpc.rs - Communication uses HTTP JSON-RPC with bearer token authentication, not shared memory or direct linking
- The
core_rpcbridge in the shell filters exposed methods for security - Core events return to the UI through Socket.IO bridging to Tauri's event system
- This architecture enables headless deployments and independent versioning of shell and core
Frequently Asked Questions
Can the OpenHuman core run without the Tauri desktop shell?
Yes. The core Rust crate is designed for headless operation. It exposes a public API through src/embed/mod.rs with Harness::builder() that any host can use. The core runs in CLI mode, Docker containers, or cloud environments without any Tauri or desktop dependencies.
How does the Tauri shell authenticate with the core process?
The core writes a bearer token to a temporary file upon startup. The Tauri shell reads this token through the core_rpc_token command before making any JSON-RPC calls. This prevents unauthorized processes from accessing the core's RPC endpoint even on localhost.
What prevents malicious UI code from accessing dangerous core methods?
The shell's core_rpc implementation in app/src-tauri/src/core_rpc.rs maintains an explicit allow-list of permitted RPC methods, primarily restricted to the tools.* namespace. The shell rejects any method calls outside this whitelist before they reach the core, enforcing security at the boundary layer.
Why use JSON-RPC over HTTP instead of Tauri's native IPC?
HTTP JSON-RPC provides language-agnostic access and enables the core to run as a separate process (or even on a remote host). This decouples the core's internal architecture from Tauri's IPC mechanisms, allowing the same communication pattern to work for CLI, Docker, and cloud deployments without code 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 →