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

OpenLogi leverages the tarpc RPC framework over Unix domain sockets to enable type-safe, versioned communication between the central agent process and both the GUI and overlay clients.

The OpenLogi IPC architecture serves as the communication backbone for this Rust-based automation platform, strictly separating hardware handling from user interface rendering. By implementing a request-response protocol with long-lived streaming capabilities, the system ensures that the agent, GUI, and overlay remain synchronized while maintaining independent process boundaries and atomic version compatibility.

Core Foundation: tarpc and Local Socket Transport

At the heart of OpenLogi's IPC layer is tarpc, a Rust-native RPC framework that generates client-side stubs and server-side glue from trait definitions. The transport layer establishes communication through a Unix domain socket located at openlogi.sock, implemented in crates/openlogi-ipc/src/transport.rs.

The agent process acts as the sole server, creating and binding to the socket during startup in crates/openlogi-agent/src/main.rs. Both the GUI (crates/openlogi-desktop/src/main.rs) and overlay (crates/openlogi-overlay/src/main.rs) act as clients, connecting to this socket to initiate RPC sessions. This single-binary, local-socket approach guarantees atomic version compatibility since all components originate from the same build.

Versioned Wire Protocol and Contract Stability

The IPC contract enforces strict version compatibility through the PROTOCOL_VERSION constant, currently set to 29 in crates/openlogi-ipc/src/ipc.rs. When the GUI connects, it immediately calls Agent::protocol_version to verify schema alignment; if the agent's binary is out-of-sync, the client aborts the connection to prevent wire protocol mismatches.

The contract follows an append-only design philosophy, where new RPC methods are added without removing legacy ones. This ensures backward compatibility, allowing older overlay builds to function correctly with newer agent versions.

Communication Patterns and Streaming State

OpenLogi's architecture uses tarpc's strict request-response model for all interactions. To support server-to-client state pushes, the system implements long-lived requests that clients open and the agent holds open until state mutations occur:

  • Agent::observe – Returns the full observable AgentSnapshot whenever state changes, eliminating the need for explicit subscription management
  • next_pairing – Streams pairing session updates (searching, devices found, passkey entry)
  • next_action_ring – Pushes action-ring state changes specifically consumed by the overlay

These streaming-style calls transform the pull-based RPC model into a push-based event system without breaking tarpc's fundamental request-response semantics.

Component Roles and Implementation

The Agent as State Authority

The agent process owns all HID I/O operations, device enumeration, and state mutation logic. It implements the Agent trait defined in crates/openlogi-ipc/src/ipc.rs, exposing strongly typed methods that return structures like AgentStatus (health diagnostics), AgentSnapshot (complete state bundles), and PairingPhase (pairing session progress).

GUI Client Operations

The GUI initializes an AgentClient from crates/openlogi-ipc/src/client.rs to drive configuration workflows:

use openlogi_ipc::client::AgentClient;
use openlogi_ipc::transport::connect;

// Establish connection to the agent's Unix socket
let conn = connect().await?;
let client = AgentClient::new(conn);

// Fetch current health status
let status = client.status().await?;
println!("Agent version: {}", status.agent_version);

The GUI subscribes to state changes through the observe() method, receiving AgentSnapshot instances containing status, inventory, pairing session, and foreground application data. If the socket connection fails, the GUI gracefully falls back to direct hardware enumeration mode.

Overlay as Pure Consumer

The overlay binary is a lightweight IPC client with no direct hardware access. It connects via the same AgentClient but only subscribes to UI-relevant streams:

// Overlay-specific stream consumption
let mut ring_stream = client.observe_action_ring().await?;
while let Some(ring) = ring_stream.next().await {
    render_action_ring(ring);
}

This design keeps the overlay focused exclusively on rendering while remaining completely decoupled from HID handling complexities.

Practical Code Examples

Creating the client connection used by both GUI and overlay:

use openlogi_ipc::client::AgentClient;
use openlogi_ipc::transport::connect;

let conn = connect().await?;
let client = AgentClient::new(conn);

Subscribing to the observable state stream:

let mut stream = client.observe().await?;
while let Some(state) = stream.next().await {
    // state is AgentSnapshot containing full system state
    update_gui(state);
}

Agent-side service implementation:

use openlogi_ipc::ipc::Agent;
use tarpc::server::BaseChannel;

let listener = std::os::unix::net::UnixListener::bind("/tmp/openlogi.sock")?;
for stream in listener.incoming() {
    let transport = BaseChannel::with_defaults(stream);
    transport.execute(my_agent.clone());
}

Summary

OpenLogi's IPC architecture provides a robust foundation for multi-process communication through these key design decisions:

  • Unix domain sockets provide low-latency local transport via openlogi.sock
  • tarpc-generated clients in crates/openlogi-ipc/src/client.rs ensure type-safe RPC calls
  • Long-lived requests enable streaming state updates without breaking request-response semantics
  • Strict versioning via PROTOCOL_VERSION prevents incompatible binary interactions
  • Append-only contracts maintain backward compatibility for overlay clients
  • Clear separation of concerns isolates HID operations in the agent while GUI and overlay focus on presentation

Frequently Asked Questions

How does OpenLogi handle version mismatches between the GUI and agent?

The IPC contract requires the GUI to call Agent::protocol_version immediately upon connection. If the returned version (currently 29 as defined in crates/openlogi-ipc/src/ipc.rs) does not match the client's expected constant, the GUI aborts the connection. This prevents serialization errors and schema mismatches before any substantive RPC calls occur.

What happens when the overlay cannot connect to the agent socket?

Unlike the GUI, which falls back to direct hardware enumeration when the socket is unavailable, the overlay is a pure IPC client with no hardware access capabilities. If openlogi.sock cannot be reached, the overlay simply cannot function. This design choice keeps the overlay lightweight and ensures it never competes with the agent for hardware resources.

Why does OpenLogi use long-lived requests instead of native pub/sub for state updates?

The tarpc framework strictly supports request-response patterns. To achieve server-to-client pushes, OpenLogi implements long-lived requests where the client initiates a call (such as observe()) and the agent deliberately delays the response until state mutations occur. This pattern effectively simulates event streaming while remaining within tarpc's architectural constraints, eliminating the need for complex subscription management or message replay logic.

Where is the RPC contract defined and how is it kept stable?

The service trait and all data structures (including AgentSnapshot, AgentStatus, and PairingPhase) reside in crates/openlogi-ipc/src/ipc.rs. The development team maintains an append-only policy for this contract: new methods may be added, but existing method signatures remain immutable. This approach ensures that older clients, particularly standalone overlay binaries, continue functioning with newer agent releases.

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 →