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

> Discover how OpenLogi's IPC architecture enables seamless communication between GUI, agent, and overlay processes using tarpc and bincode for efficient data exchange and synchronization.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: architecture
- Published: 2026-09-05

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

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

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) | RPC service definition, `AgentSnapshot`, `Command`, `GuiUpdate`, protocol version |
| [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md) | Human-readable append-only contract and version policy |
| [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs) | GUI client, long-poll loop, `SpawnReflex`, command handling |
| [`crates/openlogi-overlay/src/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/agent.rs) | Thin overlay client for snapshots and light commands |
| [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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.