How OpenLogi’s IPC Architecture Facilitates Communication Between GUI, Agent, and Overlay Processes

OpenLogi uses a local IPC channel built on tarpc and bincode over local sockets, with an append‑only protocol and explicit version handshake to synchronize the GUI and overlay while isolating all hardware I/O in a long‑running agent process.

The OpenLogi project's inter-process communication (IPC) design solves a critical architectural challenge: how to let a rich desktop GUI and a lightweight cursor overlay communicate with hardware devices without either process touching HID++ directly. According to the OpenLogi source code, this is achieved through the openlogi-ipc crate, which defines a strict RPC contract that all three processes share. This article examines the protocol design, client implementations, and communication patterns that make this architecture reliable and version-safe.

IPC Protocol Design: tarpc, bincode, and Append-Only Contracts

The foundation of OpenLogi's IPC architecture sits in crates/openlogi-ipc/src/ipc.rs. This file declares the shared RPC service used by the GUI, overlay, and agent, including core types like AgentClient, AgentSnapshot, GuiUpdate, and Command.

The wire format uses tarpc (an RPC framework for Rust) layered on bincode for serialization, transmitted over interprocess local sockets. A critical design invariant governs the protocol: the format is append-only. Method order and enum discriminants are never reordered or removed. Any structural change forces a bump of PROTOCOL_VERSION, documented in the human-readable contract at crates/openlogi-ipc/AGENTS.md.

This append-only discipline guarantees binary compatibility across GUI and overlay versions, preventing accidental protocol drift even when components ship on different release cycles.

The GUI Client: Long-Poll Observation and Reflex-Based Agent Launch

The GUI implementation lives in crates/openlogi-desktop/src/services/ipc.rs. It runs on the GPUI thread but spawns a dedicated OS thread that owns a Tokio runtime for async I/O.

Connection Lifecycle and Version Handshake

When IpcClient::spawn initializes, it delegates to openlogi_ipc::client::connect(). The returned AgentClient carries the agent's PROTOCOL_VERSION. The ensure function (lines 53–105) validates this against the GUI's compile-time constant:

  • Match: The client proceeds to declare_client, identifying itself as a GUI client.
  • Mismatch: The client either retries on ConnectFailure::Unreachable or signals GuiUpdate::OutdatedGui for a mandatory restart.

This explicit handshake prevents version skew from corrupting device state.

The Long-Poll Loop

The GUI maintains a single observe RPC call continuously (observe_loop). This call carries the client's latest generation number; the agent responds only when its internal state changes, delivering a full AgentSnapshot.

This push-only model eliminates polling timers and ensures that reconnections automatically receive the complete current state. Because tarpc multiplexes RPC calls, the long-poll remains active during concurrent commands—state updates flow uninterrupted while device operations execute.

Reflex-Based Agent Launch

If the socket is unavailable, SpawnReflex governs agent startup:

  • Immediate launch when the socket has never existed
  • Bounded delay after connection loss (SPAWN_AFTER_LOSS)
  • Retry period between attempts (SPAWN_RETRY_PERIOD)

These rules prevent aggressive respawn loops while enabling fast recovery. The implementation appears around lines 85–114.

// Spawn the IPC client in the GUI (openlogi-desktop)
let ipc_client = openlogi_desktop::services::ipc::spawn(); // returns IpcClient

// Listen for snapshots
tokio::spawn(async move {
    while let Some(update) = ipc_client.updates.recv().await {
        match update {
            openlogi_desktop::services::ipc::GuiUpdate::Snapshot(snapshot) => {
                // Apply the new state to the UI
            }
            openlogi_desktop::services::ipc::GuiUpdate::Unreachable => {
                // Show a banner that the agent is down
            }
            _ => {}
        }
    }
});

The Overlay Client: Minimal Surface, Shared State

The overlay process—responsible for rendering the ring around the cursor—uses the same IPC contract but with a reduced footprint. Its implementation appears in crates/openlogi-overlay/src/agent.rs.

The spawn_ipc() function provides an Ipc handle that supports:

  • Receiving AgentSnapshot updates for rendering
  • Sending light-control commands via OverlayCommand

Because the overlay imports shared types (Identity, RUN_ENV) from openlogi_ipc and uses the same connection logic, it observes exactly the same device state as the GUI. This reuse eliminates duplication and prevents synchronization bugs.

// Overlay asking the agent to change a light
let overlay_ipc = openlogi_overlay::agent::spawn_ipc(); // returns Ipc
let cmd = openlogi_ipc::Command::SetLight(route, LightCommand::Power(true), "logo".into(), 42);
let _ = overlay_ipc.send(cmd);

Command Multiplexing and State Consistency

All device-control requests—SetDpi, SetLight, StartPairing, and others—traverse the same tarpc connection. The handle function in the GUI client processes each Command and reports results via GuiUpdate variants like LightCommandResult.

This unified channel ensures:

  • No race conditions between state observation and command execution
  • Ordered delivery of operations to hardware
  • Consistent failure reporting back to the UI layer
// Send a DPI change from the GUI to the agent
let cmd = openlogi_desktop::services::ipc::Command::SetDpi(route, new_dpi);
let _ = ipc_client.commands.send(cmd);

Core Source Files

Path Purpose
crates/openlogi-ipc/src/ipc.rs RPC service definition, AgentSnapshot, Command, GuiUpdate, protocol version
crates/openlogi-ipc/AGENTS.md Human-readable append-only contract and version policy
crates/openlogi-desktop/src/services/ipc.rs GUI client, long-poll loop, SpawnReflex, command handling
crates/openlogi-overlay/src/agent.rs Thin overlay client for snapshots and light commands
crates/openlogi-agent/src/main.rs Agent server implementing the RPC interface (referenced, not shown)

Summary

  • Hardware isolation: All HID++ I/O lives in the agent process; GUI and overlay are unprivileged clients.
  • Version safety: Append-only protocol with explicit PROTOCOL_VERSION handshake prevents binary mismatches.
  • Efficient synchronization: Push-based long-poll eliminates polling overhead and ensures state consistency.
  • Robust process management: Reflex-based launch rules balance fast recovery with respawn protection.
  • Code reuse: Shared openlogi-ipc crate gives GUI and overlay identical views of device state.

Frequently Asked Questions

What IPC technologies does OpenLogi use?

OpenLogi uses tarpc for RPC framing, bincode for binary serialization, and interprocess local sockets for transport. This stack provides zero-copy-friendly performance without requiring external dependencies like D-Bus or gRPC.

How does OpenLogi prevent protocol version mismatches?

The protocol declares a PROTOCOL_VERSION constant. On connection, clients receive the agent's version and compare it against their compile-time value. A mismatch triggers either retry logic or a hard GuiUpdate::OutdatedGui signal forcing restart. The wire format is append-only—methods and enum variants are only added, never reordered or removed.

Why does the GUI use a long-poll instead of WebSockets or polling?

The observe RPC implements server-sent updates: the agent holds the request open and responds only when AgentSnapshot changes. This achieves lower latency than polling timers, simpler state management than bidirectional WebSockets, and automatic state recovery on reconnection (the agent sends the full current snapshot).

Can the overlay and GUI run different versions?

Yes, within constraints. Both must share a compatible PROTOCOL_VERSION. Because the protocol is append-only, a newer GUI can typically speak to an older agent (it simply won't use newer methods), and an older GUI can speak to a newer agent (unrecognized variants are handled gracefully). Version mismatches that break compatibility trigger explicit errors rather than silent corruption.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →