# How OpenLogi’s GUI and Overlay Communicate with the Agent: IPC Architecture Explained

> Understand OpenLogi's IPC architecture. Learn how the GUI and overlay communicate with the agent via tarpc RPCs for state observation and device commands.

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

---

**Both the desktop GUI and Actions‑Ring overlay connect to the hardware‑isolated agent over a cross‑platform IPC transport using tarpc RPCs, declaring their client role before observing state or issuing device commands.**

OpenLogi isolates all hardware access inside the **agent** (`openlogi-agent`), while the desktop GUI (`openlogi-desktop`) and the Actions‑Ring overlay (`openlogi-overlay`) run as separate processes. According to the AprilNEA/OpenLogi source code, these components communicate exclusively through the `openlogi-ipc` crate, which provides a unified, asynchronous transport layer that works identically on macOS, Linux, and Windows.

## The Cross‑Platform IPC Transport Layer

All communication traverses a **local‑socket abstraction** implemented in [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs). On Unix systems this resolves to a Unix‑domain socket; on Windows it uses a named pipe. The transport carries **tarpc** RPCs framed with length‑delimited prefixes and serialized via **bincode**, providing a TCP‑like stream that is fully platform‑agnostic.

This design keeps the agent single‑instance while allowing multiple distinct UI surfaces to connect concurrently. The transport handles connection multiplexing, ensuring that the GUI and overlay can issue commands and receive state updates over independent logical channels without blocking each other.

## Handshake and Role Declaration

Before any data flows, every client must complete a two‑step handshake defined in the protocol contract. First, the client calls `protocol_version()` to verify wire compatibility against the constant `PROTOCOL_VERSION` (currently **29**). A mismatch causes an immediate abort; the GUI will attempt to restart the agent, while the overlay exits cleanly.

Second, the client declares its identity via `declare_client()`, passing either `ClientKind::Gui` or `ClientKind::Overlay`. This declaration tells the agent which state observations to activate. For example, declaring `ClientKind::Gui` arms the full device observation pipeline, whereas `ClientKind::Overlay` enables the Actions‑Ring invocation stream.

In [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs), the GUI spawns a dedicated Tokio runtime thread to run this handshake:

```rust
let connection = openlogi_ipc::client::connect().await?;
connection.client
    .declare_client(context::current(), ClientKind::Gui)
    .await?;

```

Similarly, the overlay performs this declaration in [`crates/openlogi-overlay/src/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/agent.rs) before entering its main event loops.

## GUI‑to‑Agent Communication: The Observe Loop

Once connected, the GUI enters a long‑running **observe loop** (`observe_loop` in [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs)). It repeatedly calls the RPC method `observe(last_generation)`, which blocks until the agent’s observable state changes or a hold timeout expires. When the call returns, the agent sends a `GuiUpdate::Snapshot` containing the latest device states.

The IPC thread forwards these snapshots to the GPUI frontend over an `mpsc` channel, decoupling the async RPC runtime from the UI thread. Device‑specific commands—such as `set_dpi`, `set_lighting`, or `pair_device`—travel in the opposite direction through the same `AgentClient` handle. The GUI fires these commands and forgets the RPC result; any errors or confirmations flow back through the next `observe` response as `GuiUpdate` variants.

```rust
// Inside the GUI client thread
let ctx = context::current();
let obs = client.observe(ctx, last_generation).await?;
// obs contains GuiUpdate::Snapshot or similar variants

```

## Overlay‑to‑Agent Communication: Dual‑Task Architecture

The overlay uses a different observation strategy optimised for transient UI interactions. In [`crates/openlogi-overlay/src/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/agent.rs), the `spawn_ipc()` function establishes the connection and then spawns two concurrent Tokio tasks:

1. **`poll_invocations`** – Calls `observe_action_ring(last_generation)` to block until the agent reports a new Actions‑Ring invocation. When data arrives, it pushes the result onto an `mpsc::UnboundedReceiver` consumed by the overlay UI.

2. **`send_commands`** – Listens on an internal channel for user input events (`Hover`, `Activate`, `Cancel`), coalesces rapid successive commands, and forwards them to the agent via `action_ring_hover()`, `action_ring_activate()`, and `action_ring_cancel()`.

This separation ensures that user input remains responsive even while the observation RPC is blocked waiting for agent state changes.

```rust
// Overlay connection setup
let client = openlogi_ipc::client::connect().await?;
client.client
    .declare_client(context::current(), ClientKind::Overlay)
    .await?;

// Watching for ring invocations
let obs = client.observe_action_ring(ctx, last_generation).await?;
if let Some(invocation) = obs.invocation {
    // Forward to overlay UI
}

```

## The Protocol Contract and RPC Service Definition

All RPC methods are defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) within the tarpc `Agent` service trait. This file serves as the source of truth for the wire protocol, enumerating every observable state variant and command type. The `PROTOCOL_VERSION` constant guards against schema drift: if a client connects to an agent built with a different version, the initial `protocol_version()` check fails, preventing undefined behaviour from mismatched message formats.

## Summary

- OpenLogi isolates hardware access in the `openlogi-agent` process; the GUI and overlay are strictly client processes.
- Communication occurs over **tarpc** RPCs framed with length‑delimited bincode, transported via Unix‑domain sockets or named pipes through `openlogi_ipc::client::connect()`.
- Clients must declare their role (`ClientKind::Gui` or `ClientKind::Overlay`) after verifying `PROTOCOL_VERSION` (currently 29).
- The GUI uses a blocking `observe()` loop in [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs) to receive `GuiUpdate::Snapshot` states.
- The overlay runs dual concurrent tasks in [`crates/openlogi-overlay/src/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/agent.rs) to poll invocations via `observe_action_ring()` and send commands via `action_ring_hover()` and related methods.
- All traffic is asynchronous and multiplexed over a single local‑socket connection, ensuring low latency without blocking the UI thread.

## Frequently Asked Questions

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

OpenLogi uses a custom transport built on **tarpc** over local sockets. The implementation in [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs) maps to Unix‑domain sockets on macOS and Linux, and named pipes on Windows. All messages use length‑delimited framing with bincode serialization, providing a cross‑platform, TCP‑like stream without network overhead.

### How does the GUI receive real‑time updates from the agent?

The GUI spawns a dedicated Tokio thread that enters an `observe_loop`. It repeatedly calls the RPC method `observe(last_generation)`, which blocks until the agent’s state changes. When the call returns with a `GuiUpdate::Snapshot`, the thread sends the data to the GPUI frontend over an `mpsc` channel, ensuring the UI thread never blocks on I/O.

### What happens if the GUI and agent have different protocol versions?

When a client connects, it first calls `protocol_version()` defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). If the returned version does not match the client’s expected `PROTOCOL_VERSION` (currently 29), the client aborts the connection. The GUI process will attempt to restart the agent to resolve the mismatch, while the overlay process exits immediately to prevent undefined behaviour.

### How does the overlay differ from the GUI in agent communication?

While both use the same underlying `openlogi_ipc` client, the overlay declares `ClientKind::Overlay` and uses specialized RPC methods. It runs two concurrent tasks: one blocking on `observe_action_ring()` to receive Actions‑Ring invocations, and another listening for UI events to send `action_ring_hover()`, `action_ring_activate()`, or `action_ring_cancel()` commands. This dual‑task model allows the overlay to handle transient, high‑frequency input events separately from state observation.