# How OpenLogi GUI, Agent, and Shared Orchestration Tiers Interact: A Deep Dive into the IPC Architecture

> Discover how OpenLogi GUI, Agent, and Shared Orchestration tiers interact using tarpc IPC and observable state. Understand the core of its three-tier architecture.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: deep-dive
- Published: 2026-08-22

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/src/transport.rs). All remote calls flow through the service trait declared in [`openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs), the GUI establishes the tarpc client and queries the orchestration state:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent/src/server.rs) delegates to the orchestrator:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)** — Defines the `Agent` tarpc service trait, `PROTOCOL_VERSION = 23`, and the `Observation` struct that crosses tier boundaries.
- **[`crates/openlogi-agent/src/server.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/orchestrator.rs)** — Contains the canonical state management and implements the `Agent` trait methods that actually mutate or read the `Observation`.
- **[`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs)** — Configures the local-socket transport used for Unix domain socket communication between GUI and agent.
- **[`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/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 `Orchestrator` struct within `openlogi-agent-core`, which is the sole authority for mutating device and configuration state.
- **Updates use long-polling** through the `observe` RPC 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 in [`openlogi-agent/src/server.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent/src/server.rs), and core logic in [`openlogi-agent-core/src/orchestrator.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.