How OpenLogi GUI, Agent, and Shared Orchestration Tiers Interact: A Deep Dive into the IPC Architecture
The OpenLogi system uses a strict three-tier architecture where the desktop GUI communicates with a background agent via tarpc IPC, while all business logic resides in a shared orchestration layer that maintains observable state.
OpenLogi, developed in the AprilNEA/OpenLogi repository, separates concerns across three distinct processes that communicate through a version-checked, request-response protocol. This design ensures the GUI remains a thin client while the agent handles hardware abstraction and the orchestration layer manages complex state transitions for device discovery, DPI changes, and pairing operations.
The Three-Tier Architecture
OpenLogi divides functionality across three crates that run as separate processes but share a single IPC contract defined in crates/openlogi-ipc/src/ipc.rs.
GUI Tier (openlogi-desktop)
The GUI tier lives in the openlogi-desktop crate and functions as a pure client. It creates a tarpc AgentClient that connects to the agent through a local-socket transport defined in openlogi-ipc/src/transport.rs. All remote calls flow through the service trait declared in openlogi-ipc/src/ipc.rs, where PROTOCOL_VERSION = 23 is defined to guard against binary mismatches.
Agent Tier (openlogi-agent)
The agent tier operates as the server process within the openlogi-agent crate. The entry point in openlogi-agent/src/server.rs initializes a tarpc server that implements the Agent service trait. This tier acts as a thin adapter layer, receiving RPC requests and immediately delegating them to the shared orchestration layer for execution.
Shared Orchestration Tier (openlogi-agent-core)
The shared orchestration tier contains all core business logic within openlogi-agent-core. The Orchestrator struct in openlogi-agent-core/src/orchestrator.rs owns the canonical state through the Observation struct. This is the only component that mutates device inventory, DPI settings, pairing state, and Actions Ring configuration. The GUI reads this state exclusively through the observe and snapshot RPC methods, never modifying it directly.
IPC Communication Flow
Interaction between tiers follows a strict request-response pattern enforced by tarpc, with state updates delivered through a long-polling mechanism rather than server-initiated pushes.
Connection and Protocol Verification
When the GUI starts, it establishes a connection through the local socket transport. The first call to Agent::protocol_version validates that both client and server binaries match the expected wire protocol. This check prevents communication errors between mismatched versions of the desktop application and background service.
Reading State with snapshot()
For immediate state inspection, the GUI calls AgentClient::snapshot() over the tarpc transport. The request serializes using bincode and traverses the Unix socket. The agent server in openlogi-agent/src/server.rs deserializes the request and forwards it to openlogi-agent-core::orchestrator::Agent::snapshot, which clones the current Observation containing status, inventory, and camera state.
Long-Polling with observe()
Because tarpc does not support server-initiated pushes, the observe mechanism implements level-triggered updates via long-polling. The GUI holds an Agent::observe request open, and the orchestrator responds immediately when the internal generation counter changes or after the OBSERVE_HOLD timeout of 20 seconds elapses. This design provides reactive UI updates without requiring complex subscription management or bidirectional streaming.
Implementation Examples
The following patterns demonstrate how each tier implements the IPC contract.
GUI Client Implementation
In crates/openlogi-desktop/src/services/ipc.rs, the GUI establishes the tarpc client and queries the orchestration state:
use openlogi_ipc::client::connect_to_agent;
use openlogi_ipc::ipc::AgentClient;
use tarpc::context::Context;
async fn fetch_snapshot() -> Result<(), tarpc::client::RpcError> {
// Connect to the UNIX-socket used by the agent
let (client, _bg) = connect_to_agent().await?;
let ctx = Context::current();
// Request the full Observation state
let observation = client.snapshot(ctx, /* deadline */ None).await?;
// Access device inventory and agent metadata
println!("Agent version: {}", observation.snapshot.status.agent_version);
println!("Devices: {:?}", observation.snapshot.inventory);
Ok(())
}
Agent Server Implementation
The server implementation in openlogi-agent/src/server.rs delegates to the orchestrator:
use openlogi_agent_core::orchestrator::Orchestrator;
use openlogi_ipc::ipc::{Agent, Observation};
#[tarpc::server]
impl Agent for Orchestrator {
async fn snapshot(&self, _: tarpc::context::Context) -> Observation {
// Lock the orchestrator's internal state
let state = self.state.read().await;
// Clone the current observation for transmission
state.observation.clone()
}
// Additional RPC methods: observe, reload_config, etc.
}
Key Source Files
Understanding the interaction requires familiarity with these specific locations in the AprilNEA/OpenLogi codebase:
crates/openlogi-ipc/src/ipc.rs— Defines theAgenttarpc service trait,PROTOCOL_VERSION = 23, and theObservationstruct that crosses tier boundaries.crates/openlogi-agent/src/server.rs— Implements the tarpc server entry point that receives GUI requests and routes them to the orchestrator.crates/openlogi-agent-core/src/orchestrator.rs— Contains the canonical state management and implements theAgenttrait methods that actually mutate or read theObservation.crates/openlogi-ipc/src/transport.rs— Configures the local-socket transport used for Unix domain socket communication between GUI and agent.AGENTS.md— High-level architecture documentation describing tier responsibilities.
Summary
- OpenLogi uses three distinct tiers: a thin GUI client, a lightweight agent server, and a stateful shared orchestration layer.
- Communication occurs via tarpc over local Unix sockets, with bincode serialization and strict protocol version checking (
PROTOCOL_VERSION = 23). - State is centralized in the
Orchestratorstruct withinopenlogi-agent-core, which is the sole authority for mutating device and configuration state. - Updates use long-polling through the
observeRPC method, held open for up to 20 seconds (OBSERVE_HOLD) to provide reactive UI updates without server pushes. - File paths matter: The IPC contract lives in
openlogi-ipc, the server entry inopenlogi-agent/src/server.rs, and core logic inopenlogi-agent-core/src/orchestrator.rs.
Frequently Asked Questions
How does the GUI receive real-time updates without the agent pushing data?
The GUI uses a long-polling mechanism via the observe RPC method. The client holds the request open, and the orchestrator in openlogi-agent-core/src/orchestrator.rs either responds immediately when the state generation changes or returns after a 20-second hold timeout (OBSERVE_HOLD). This creates a level-triggered notification system within tarpc's strict request-response model.
What prevents version mismatches between the GUI and agent binaries?
The PROTOCOL_VERSION constant, defined as 23 in crates/openlogi-ipc/src/ipc.rs, is checked immediately upon connection through the Agent::protocol_version RPC method. If the GUI and agent report different versions, the connection fails early to prevent serialization errors or undefined behavior.
Why is the orchestration logic separate from the agent server?
Separating concerns into openlogi-agent-core allows the shared orchestration tier to maintain complex state (device discovery, HID event handling, DPI calculations) independently of the RPC transport concerns in openlogi-agent. This architecture ensures that hardware abstraction and business logic remain testable and isolated from the tarpc server implementation in openlogi-agent/src/server.rs.
Can multiple GUI instances connect to the same agent simultaneously?
While the underlying tarpc transport supports multiple connections, the Orchestrator in openlogi-agent-core/src/orchestrator.rs maintains a single canonical state accessed through read locks. All connected GUI instances receive identical Observation snapshots through their respective AgentClient connections, though the codebase typically expects a single desktop client per agent instance.
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 →