# How OpenLogi's Three-Process Architecture Separates GUI, Agent, and Overlay

> Discover OpenLogi's three-process architecture for seamless GUI, agent, and overlay separation. Learn how this design enhances stability and prevents direct HID I/O for a robust user experience.

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

---

**OpenLogi implements a strict separation of concerns across three distinct binaries—`openlogi-desktop` (GUI), `openlogi-agent` (device controller), and `openlogi-overlay` (cursor ring)—that communicate exclusively through a tarpc-based IPC contract defined in `crates/openlogi-ipc`, ensuring presentation layers never perform direct HID I/O.**

The OpenLogi repository organizes its functionality into separate OS processes to isolate device I/O operations from user interface rendering. This architecture prevents UI crashes from affecting device state and allows the overlay to function independently of the settings application.

## The Three Core Processes

OpenLogi ships three separate binaries that run side-by-side, each with a distinct role and crate location:

### GUI Process (openlogi-desktop)

Located in `crates/openlogi-desktop`, this process renders the settings application using GPUI and gpui-component. **It never communicates directly with devices.** Instead, it acts as a pure IPC client that sends configuration requests to the agent. According to the contract documented in [`crates/openlogi-desktop/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/AGENTS.md), the GUI code intentionally excludes platform-specific HID crates to enforce this boundary.

### Agent Process (openlogi-agent)

Located in `crates/openlogi-agent`, this background server owns the input hook and performs all HID++ I/O operations. It maintains the canonical device state and exposes a tarpc local-socket transport via `openlogi_ipc::server::Server`. All device-specific logic lives in `crates/openlogi-device` and `crates/openlogi-hid`, completely isolated from the GUI and overlay crates.

### Overlay Process (openlogi-overlay)

Located in `crates/openlogi-overlay`, this binary renders the cursor-centered "Actions Ring" interface. Like the GUI, it is a **pure IPC client** that never performs device I/O itself. It receives ring snapshots from the agent via `OverlayCommand` messages and renders them using GPUI windows.

## IPC Communication Between GUI and Agent

The GUI communicates with the agent through the shared IPC contract defined in `crates/openlogi-ipc` and documented in [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md) (repo root). When the GUI boots, it creates an IPC client using `openlogi_ipc::client::Client` to issue requests such as retrieving current device configurations or applying DPI changes.

In [`crates/openlogi-desktop/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/runtime.rs), the GUI coordinates this client and updates the `AppState` only after receiving data from the agent. Because the GUI imports no HID crates, it cannot bypass the agent to touch hardware directly. The agent listens on a local socket and performs the actual HID++ reads/writes, keeping all platform-specific code within `crates/openlogi-hid`.

## Overlay as a Pure IPC Client

The overlay binary launches as a sibling process to the GUI (not a child process) and also instantiates `openlogi_ipc::client::Client` as shown in [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) (lines 31-34). The agent pushes ring snapshots to the overlay via the IPC protocol using `OverlayCommand` structures. The overlay only renders these snapshots; any user interaction (hover, click, cancel) is sent back to the agent for execution.

This design means `crates/openlogi-overlay` does not depend on `crates/openlogi-desktop`, keeping the two presentation processes independent while they share common UI assets.

## Shared UI Assets in openlogi-ui

Both the GUI and overlay import the `openlogi-ui` crate for common presentation elements. Located in `crates/openlogi-ui`, this crate contains shared assets including icons, ring geometry definitions, and locale catalogs. As documented in [`crates/openlogi-ui/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/src/lib.rs) and [`crates/openlogi-ui/Cargo.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/Cargo.toml), adding a new icon or UI component to this crate automatically makes it available to both binaries without creating a dependency between the desktop and overlay processes.

## Process Lifetime and Role Claiming

When the overlay starts, it claims a unique role by acquiring `overlay.lock` to ensure only one overlay instance binds to a particular agent run. This mechanism, implemented in [`crates/openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/session.rs) (lines 128-144), releases the lock when the overlay process drops, allowing a new instance to claim the role. The GUI does not claim this role; it connects to the agent as a standard IPC client without exclusive binding.

## Practical Implementation Example

The following code illustrates how both the GUI and overlay instantiate IPC clients to communicate with the agent:

```rust
// ---------- GUI side (openlogi-desktop) ----------
use openlogi_ipc::client::Client;

// Create the IPC client once at start-up
let client = Client::new()?;

// Ask the agent for the current device state
let device_state = client.get_device_state(device_id).await?;

// Render UI based on `device_state`
// ...

// ---------- Overlay side (openlogi-overlay) ----------
use openlogi_ipc::client::Client;

// The overlay also creates a client
let client = Client::new()?;

// Subscribe to ring updates from the agent
let mut ring_stream = client.subscribe_ring_updates()?;

// Render the ring whenever a new snapshot arrives
while let Some(snapshot) = ring_stream.next().await {
    render_ring(snapshot);
}

```

In [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs) (lines 35-80), this client spawns the overlay window and handles incoming `OverlayCommand` messages, while [`crates/openlogi-desktop/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/AGENTS.md) (lines 40-46) documents how the GUI's runtime coordinates state updates exclusively through this IPC channel.

## Summary

- **Three separate binaries**: `openlogi-desktop` (GUI), `openlogi-agent` (device control), and `openlogi-overlay` (cursor ring) run as independent OS processes.
- **tarpc IPC contract**: Defined in `crates/openlogi-ipc`, this contract enables communication via local sockets using `openlogi_ipc::client::Client` and `openlogi_ipc::server::Server`.
- **Pure client architecture**: Both GUI and overlay are pure IPC clients that never import HID crates or perform device I/O.
- **Single source of truth**: The agent maintains canonical device state and handles all HID++ operations.
- **Shared UI layer**: The `openlogi-ui` crate provides common assets to both presentation processes without creating inter-process dependencies.
- **Role claiming**: The overlay uses `overlay.lock` in [`session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/session.rs) to ensure exclusive binding to the agent instance.

## Frequently Asked Questions

### Why does OpenLogi use three separate processes instead of a single application?

Using three processes isolates device I/O from UI rendering to prevent crashes in the interface from affecting hardware control. The agent runs as a background service that owns all HID++ operations, while the GUI and overlay can restart independently without losing device state or requiring hardware re-initialization.

### How does the overlay process claim exclusive access to the agent?

The overlay claims a role by acquiring `overlay.lock` when starting, as implemented in [`crates/openlogi-overlay/src/session.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/session.rs) (lines 128-144). This lock ensures only one overlay instance communicates with the agent at a time. Dropping the lock (either through normal shutdown or crash) releases the role for subsequent overlay instances.

### What prevents the GUI from directly accessing HID devices?

The GUI crate explicitly excludes HID dependencies. According to [`crates/openlogi-desktop/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/AGENTS.md) (lines 16-22), the GUI never imports `openlogi-hid` or platform-specific HID crates. All hardware access flows through the `openlogi_ipc::client::Client` to the agent, enforcing the architectural boundary at the compiler level.

### How do the GUI and overlay share UI components while remaining separate processes?

Both binaries depend on the `openlogi-ui` crate, which contains shared assets like icons, ring geometry, and locale catalogs. Because this is a library dependency (not a process dependency), changes to [`crates/openlogi-ui/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/src/lib.rs) automatically propagate to both the desktop settings app and the overlay ring without requiring the processes to communicate directly or share memory.