# How OpenLogi Handles Inter-Process Communication Between Agent, GUI, and Overlay

> Discover how OpenLogi manages inter-process communication via local sockets and tarpc RPCs, enabling seamless agent, GUI, and overlay interaction. Learn more today.

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

---

**OpenLogi isolates all hardware access in the agent process and uses a cross-platform local socket with tarpc RPCs to let both the desktop GUI and overlay communicate asynchronously after a protocol version handshake and role declaration.**

OpenLogi follows a strict multi-process architecture where the `openlogi-agent` holds exclusive hardware access while the desktop GUI (`openlogi-desktop`) and Actions‑Ring overlay (`openlogi-overlay`) remain isolated client processes. According to the AprilNEA/OpenLogi source code, all inter-process communication flows through the `openlogi-ipc` crate, which provides a platform-agnostic transport layer and versioned RPC protocol that multiplexes state observation and command issuance over a single byte stream.

## Cross-Platform IPC Transport

### Local Socket Implementation

The IPC transport uses **Unix-domain sockets** on macOS and Linux and **named pipes** on Windows. The implementation lives in [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs) and handles length-delimited framing with **bincode** serialization for efficient, compact message encoding.

### tarpc RPC Framework

All communication uses the **tarpc** framework for typed remote procedure calls. The transport exposes a `Client` struct that both the GUI and overlay instantiate via `openlogi_ipc::client::connect()`, creating a persistent, asynchronous connection to the agent’s local socket.

## Connection Handshake and Role Declaration

Before issuing commands, every client must complete a two-step handshake defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs).

First, the client calls **`protocol_version()`** to verify wire compatibility. The current protocol version is defined as **`PROTOCOL_VERSION`** (currently `29`). If the client and agent versions mismatch, the GUI triggers an agent restart while the overlay exits gracefully.

Second, the client declares its role using **`declare_client(context::current(), kind)`** with either **`ClientKind::Gui`** or **`ClientKind::Overlay`**. This declaration tells the agent whether to arm the full device interface or remain dormant for overlay-only operations.

```rust
// Connect and declare the GUI role
let client = openlogi_ipc::client::connect().await?;
client.client
    .declare_client(context::current(), ClientKind::Gui)
    .await?;

```

## GUI Communication Patterns

The desktop GUI spawns a dedicated thread running a Tokio runtime to manage the IPC client, keeping the GPUI main thread responsive.

### State Observation Loop

Inside [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs), the GUI runs an **`observe_loop`** that repeatedly calls **`observe(last_generation)`**. This RPC blocks until the agent’s observable state changes or a hold timeout expires, then returns a **`GuiUpdate::Snapshot`** that is forwarded to the GPUI loop over an `mpsc` channel.

### Device Command Issuance

Device-specific commands—such as setting DPI, changing lighting effects, or initiating pairing—are sent through the same `AgentClient` methods as fire-and-forget RPCs. The agent reports asynchronous results via `GuiUpdate` variants rather than direct RPC responses.

```rust
// Blocking observe call from the GUI
let ctx = context::current();
let obs = client.observe(ctx, last_generation).await?;
// obs contains the new state snapshot

```

## Overlay Communication Patterns

The overlay initializes its IPC runtime in **`spawn_ipc()`** inside [`crates/openlogi-overlay/src/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/agent.rs). After connecting and declaring `ClientKind::Overlay`, it runs two concurrent asynchronous tasks:

- **`poll_invocations`** – calls **`observe_action_ring()`** to retrieve the current Actions‑Ring invocation state and pushes updates onto an `mpsc::UnboundedReceiver` for the overlay UI.
- **`send_commands`** – receives UI events (`Hover`, `Activate`, `Cancel`) and forwards them to the agent via **`action_ring_hover`**, **`action_ring_activate`**, and **`action_ring_cancel`**.

The overlay implements command coalescing and retry logic directly in [`crates/openlogi-overlay/src/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/agent.rs) to handle rapid user inputs without overwhelming the agent.

```rust
// Overlay observing the action ring
let obs = client.observe_action_ring(ctx, last_generation).await?;
if let Some(invocation) = obs.invocation {
    // Push to UI channel
}

```

## Protocol Contract and Versioning

All RPC methods are defined in the **`Agent`** tarpc service located in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). This contract strictly versions the wire format through `PROTOCOL_VERSION`, ensuring that both GUI and overlay clients speak the same language as the agent core.

The protocol is **asynchronous and multiplexed**, allowing the single local socket to carry concurrent request/response pairs and server-pushed state updates without blocking either client.

## Summary

- **Transport**: Cross-platform local sockets (Unix-domain or named pipes) with length-delimited bincode framing implemented in [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs).
- **Handshake**: Clients verify `PROTOCOL_VERSION` (currently 29) and declare their role via `ClientKind::Gui` or `ClientKind::Overlay`.
- **GUI Pattern**: Blocking `observe()` calls in a dedicated thread feed state snapshots to the GPUI loop via `mpsc` channels.
- **Overlay Pattern**: Concurrent `observe_action_ring()` polling and command forwarding via `action_ring_hover`, `action_ring_activate`, and `action_ring_cancel`.
- **Contract**: All RPC definitions live in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) and are consumed through `openlogi_ipc::client::connect()`.

## Frequently Asked Questions

### What transport protocol does OpenLogi use for IPC?

OpenLogi uses a cross-platform local socket transport: Unix-domain sockets on macOS and Linux, and named pipes on Windows. The byte stream is framed with length-delimited headers and serialized using bincode, as implemented in [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs).

### How does the GUI receive state updates from the agent?

The GUI spawns a dedicated Tokio thread that enters a blocking loop calling `observe(last_generation)`. This method blocks until the agent state changes, then returns a `GuiUpdate::Snapshot` that is sent to the main GPUI thread over an `mpsc` channel.

### Can the overlay operate independently of the GUI?

Yes. The overlay establishes its own IPC connection via `openlogi_ipc::client::connect()`, declares `ClientKind::Overlay`, and communicates directly with the agent without requiring the GUI to be running. The agent treats overlay connections as distinct client sessions.

### What happens if the protocol versions mismatch?

If the client and agent report different `PROTOCOL_VERSION` values during the initial handshake, the connection aborts. The GUI implementation triggers an agent restart to force version alignment, while the overlay process exits immediately to prevent undefined behavior.