OpenLogi IPC Contract: How the GUI, Agent, and Overlay Communicate
OpenLogi’s IPC contract is defined by a single tarpc service trait named Agent located in crates/openlogi-ipc/src/ipc.rs, serving as the exclusive wire format for all communication between the GUI desktop client, background agent, and Actions-Ring overlay.
The AprilNEA/OpenLogi repository centralizes its inter-process communication in the openlogi-ipc crate. This contract establishes a strict, versioned boundary that allows the GUI, agent daemon, and overlay helper to operate as decoupled binaries while maintaining synchronized state through method calls rather than ad-hoc message passing.
Core Architecture and the tarpc Service Trait
The entire contract is implemented as a tarpc service trait called Agent in crates/openlogi-ipc/src/ipc.rs. This trait generates the client and server stubs that handle serialization, transport, and method dispatch across the Unix domain socket.
Because tarpc encodes method calls as enum variants, the order of methods in the Agent trait constitutes the wire format. New methods must be appended to the end of the trait definition to maintain backward compatibility (see comments around lines 40–46). Reordering or inserting methods would break existing clients because the enum index changes.
Protocol Versioning and Handshake
On connection, the GUI verifies binary compatibility through a strict versioning protocol to prevent serialization errors.
protocol_version()returns the constantPROTOCOL_VERSION(currently set to30at line 65)- The GUI aborts immediately if the returned version does not match its compile-time expectation
PROTOCOL_VERSIONis only incremented when breaking changes occur to the types or method signatures (see version history comments at lines 26–50)
This handshake ensures that mismatched GUI and agent binaries cannot connect, preventing undefined behavior from schema mismatches.
State Observation Pattern (Long-Polling)
Instead of using push-based subscriptions or WebSocket streams, the agent implements level-triggered long-polling. Clients initiate requests that block until state changes or a timeout occurs.
observe(since: Generation) -> Observationblocks until any observable state changes, returning a fullAgentSnapshotcontainingAgentStatus, device inventory,PairingPhase, and foreground applications. The GUI relies on this as its primary synchronization mechanism.observe_action_ring(since: Generation) -> RingObservationprovides the overlay withActionRingInvocationdata (slots, labels, icons SVG) when the user triggers the ring or when the state expires.- Both methods respect the
OBSERVE_HOLDtimeout of approximately 20 seconds, after which they return the current state anyway, allowing clients to detect liveness without persistent connections.
This design eliminates the need for subscription management, replay buffers, or complex backpressure logic on the agent side.
Client Declaration and Overlay Specialization
Clients must declare their type to receive appropriate treatment and security permissions.
declare_client(kind: ClientKind)registers the connection as either a GUI, CLI, or Overlay client- The overlay sends
ClientKind::Overlayto identify itself as a non-arming client that should not trigger security-sensitive actions identity() -> Identityreturns a stable token that the overlay uses to detect agent restarts and reset its localGenerationcounter to zero
The overlay’s required method set is minimal: declare_client(ClientKind::Overlay), identity(), and observe_action_ring(). It never performs direct device I/O; all actions are executed by the agent on its behalf.
Key Service Methods and Data Types
The Agent trait exposes methods organized by functional domain:
Status and Inventory
status() -> AgentStatusreturns the current agent stateinventory() -> Vec<DeviceInventory>lists connected peripheralssnapshot() -> AgentSnapshotcombines status, inventory, pairing progress, and foreground applications into a single struct for atomic UI updates
Configuration
reload_config(),set_dpi(),read_dpi(),set_smartshift(),read_smartshift(),set_lighting(),set_light(),set_light_manual_power()- Returns
ConfigReloadErrororWriteErroron failure
Pairing
start_pairing(),pair_device(),cancel_pairing(),next_pairing()(legacy stream)PairingCommandErrormaps toPairingFailurefor error handling- States tracked via
PairingPhaseandPairingUpdate
Event Monitoring
poll_event_monitor()streamsMonitorEventstructs containing live mouse button and scroll events for debugging or macro recording
Actions-Ring Control
next_action_ring()(legacy),action_ring_hover(),action_ring_activate(),action_ring_cancel()- Returns
ActionRingCommandErroron invalid operations
Implementation Reference
| File | Role |
|---|---|
crates/openlogi-ipc/src/ipc.rs |
Defines the Agent trait, all request/response types, ClientKind enum, PROTOCOL_VERSION, and versioning comments |
crates/openlogi-desktop/src/services/ipc.rs |
GUI client implementation that creates AgentClient and drives the observe() loop |
crates/openlogi-overlay/src/main.rs |
Overlay helper that declares ClientKind::Overlay and consumes observe_action_ring() |
Code Examples
Overlay Connection Pattern
use openlogi_ipc::AgentClient;
use openlogi_ipc::{ClientKind, Generation};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = AgentClient::connect("/tmp/openlogi.sock").await?;
// Declare overlay client type
client.declare_client(ClientKind::Overlay).await?;
// Detect agent restarts via stable identity
let _id = client.identity().await?;
// Poll for ring invocations using generation counters
let mut gen: Generation = 0;
loop {
let ring_obs = client.observe_action_ring(gen).await?;
gen = ring_obs.generation;
if let Some(inv) = ring_obs.invocation {
// Render ring using inv.slots, inv.language, etc.
}
}
}
GUI State Synchronization Loop
let client = AgentClient::connect("/tmp/openlogi.sock").await?;
let mut gen: Generation = 0;
loop {
// Block until any state changes (20s max timeout)
let obs = client.observe(gen).await?;
gen = obs.generation;
update_ui(obs.snapshot); // Contains full AgentSnapshot
}
Summary
- The OpenLogi IPC contract is a single tarpc
Agenttrait incrates/openlogi-ipc/src/ipc.rsshared by all components. - Protocol version 30 enforces binary compatibility; the GUI aborts immediately on mismatched agents.
- Long-polling via
observe()andobserve_action_ring()replaces push notifications, usingGenerationcounters to track state changes without subscription management. - The overlay declares
ClientKind::Overlayand usesidentity()to handle restarts, while the GUI relies on comprehensiveAgentSnapshotupdates for its interface.
Frequently Asked Questions
What transport layer does OpenLogi use for IPC?
The agent binds to a Unix domain socket at a path such as /tmp/openlogi.sock. The tarpc library handles framing and serialization over this socket, providing typed async methods for the GUI and overlay clients according to the contract defined in crates/openlogi-ipc/src/ipc.rs.
How does the overlay detect if the agent has restarted?
The overlay calls identity() immediately after connection to receive a stable Identity token. If a subsequent observe_action_ring() call returns an error or the connection drops, the overlay reconnects and compares the new identity. A changed token indicates an agent restart, prompting the overlay to reset its Generation counter to zero and resynchronize state.
Why does OpenLogi use long-polling instead of push notifications?
The observe() and observe_action_ring() methods implement long-polling with a 20-second timeout (OBSERVE_HOLD) rather than server-sent events or WebSocket pushes. This eliminates the need for the agent to maintain subscription state or replay buffers for disconnected clients, simplifying the implementation while still providing near real-time updates through the AgentSnapshot and RingObservation types.
What happens if the GUI and agent have different protocol versions?
When the GUI connects, it calls protocol_version() and compares the result against its compile-time PROTOCOL_VERSION constant (currently 30). If they differ, the GUI aborts the connection immediately. This prevents serialization errors or logic bugs that could arise from mismatched message formats according to the strict contract versioning policy documented in ipc.rs lines 26–50.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →