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

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. 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, the GUI spawns a dedicated Tokio runtime thread to run this handshake:

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 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). 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.

// 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, 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.

// 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 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 to receive GuiUpdate::Snapshot states.
  • The overlay runs dual concurrent tasks in 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 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. 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.

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 →