# OpenLogi Three-Process Architecture: How It Separates GUI, Agent, and Overlay Functions

> OpenLogi's three-process architecture separates GUI, agent, and overlay functions via tarpc socket for privilege separation, crash isolation, and low-latency interaction.

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

---

**OpenLogi isolates user interface, hardware control, and on-screen overlay rendering into three distinct processes that communicate exclusively through a local tarpc socket, ensuring privilege separation, crash isolation, and low-latency interaction.**

AprilNEA/OpenLogi implements a strict separation of concerns through its three-process architecture, isolating high-level user interactions, low-level device I/O, and visual feedback into independent binaries. This design prevents the configuration interface from interfering with hardware communication while maintaining responsive overlay graphics. The architecture relies on a versioned IPC contract defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) and documented in [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md) to coordinate state across the system.

## The Three Core Processes

OpenLogi divides responsibilities across three standalone binaries that run as separate operating system processes.

### GUI Process (openlogi-desktop)

The **GUI process** presents the configuration interface, per-application profiles, and user settings. Implemented in `crates/openlogi-desktop` with its core logic in [`crates/openlogi-desktop/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/lib.rs), this process runs as a pure GPUI client and never communicates directly with HID devices.

All requests for device state, DPI adjustments, or Actions Ring activation flow through the tarpc socket to the Agent process. This isolation ensures that user interface crashes cannot destabilize hardware communication.

### Agent Process (openlogi-agent)

The **Agent process** serves as the privileged daemon that owns input hooks, enumerates Logitech HID++ devices, and performs all hardware reads and writes. The binary entry point resides in [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs), while the core logic lives in [`crates/openlogi-agent-core/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/lib.rs).

This process is the sole component that opens `/dev/hidraw` on Linux, accesses Unifying and Bolt receivers, and manages macOS input monitoring permissions. It maintains central state for profiles and DPI cycles, receiving commands from both the GUI and Overlay via the IPC channel and replying with status updates or event notifications.

### Overlay Process (openlogi-overlay)

The **Overlay process** draws the cursor-centered **Actions Ring** that appears when a profile hotkey activates. Located in `crates/openlogi-overlay` with its entry point at [`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs), this lightweight binary runs as a sibling process to the GUI (not a child process).

It imports the shared `openlogi-ui` crate for rendering but never performs device I/O. The Overlay subscribes to the IPC channel to receive current profile data and action icons, then reports user selections back to the Agent for execution.

## Inter-Process Communication Layer

All three processes communicate through a local socket IPC layer implemented with **tarpc**. The shared contract lives in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) and defines the request/response protocol used across the system.

The contract is versioned and append-only, guaranteeing backward compatibility as the protocol evolves. Processes connect to a local socket (such as `unix:/tmp/openlogi.sock` on Linux) to exchange messages.

## Implementation Examples

### Connecting the GUI to the Agent

In [`crates/openlogi-desktop/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/lib.rs), the GUI creates a tarpc client to communicate with the Agent daemon:

```rust
use openlogi_ipc::ipc::{Client, Request};
use tarpc::client::Config;

// Create a tarpc client that talks to the local socket the agent listens on.
let client = Config::default()
    .protocol(tarpc::protocol::Bincode::default())
    .connect("unix:/tmp/openlogi.sock")
    .await?
    .into_client();

// Ask the agent for the current device list.
let devices = client.request(Request::ListDevices).await?;
println!("Connected devices: {:?}", devices);

```

### Overlay Subscription and Rendering

The Overlay process subscribes to profile updates and renders the Actions Ring without device interaction:

```rust
use openlogi_ipc::ipc::{Client, Request, Response};

fn start_overlay() {
    // The overlay process is a separate binary; here we just spawn it.
    std::process::Command::new("openlogi-overlay")
        .arg("--socket")
        .arg("/tmp/openlogi.sock")
        .spawn()
        .expect("failed to start overlay");
}

// In the overlay (running the same IPC client):
async fn run_overlay(client: Client) {
    // Subscribe to profile updates.
    let mut updates = client.subscribe(Request::SubscribeProfile).await?;

    while let Some(profile) = updates.next().await {
        // Render the ring with the icons from `profile`.
        render_actions_ring(profile);
    }
}

// When the user picks an action, the overlay sends it back:
client.request(Request::ExecuteAction { action_id }).await?;

```

### Agent Request Dispatch

In [`crates/openlogi-agent-core/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/lib.rs), the Agent handles incoming requests from both GUI and Overlay:

```rust
// Inside the agent's request dispatcher:
match request {
    Request::ExecuteAction { action_id } => {
        // Perform the HID++ command associated with the action.
        device.write_action(action_id)?;
        Ok(Response::Ack)
    }
    // other request variants …
}

```

## Architectural Benefits

The **OpenLogi three-process architecture** delivers specific advantages for system stability and security.

### Safety and Stability

Because the GUI runs independently from the Agent, the configuration interface can crash or restart without jeopardizing low-level HID communication. The Agent continues running as a background daemon, maintaining device connections and state regardless of GUI lifecycle events.

### Privilege Isolation

On macOS, the Agent must hold the "Input Monitoring" permission to access HID devices. The GUI and Overlay run with normal user privileges and do not require this entitlement. This separation follows the principle of least privilege, limiting elevated permissions to the single process that requires hardware access.

### Performance Optimization

The Overlay operates as a minimal process dedicated solely to graphics rendering, keeping latency low for the Actions Ring interaction. By offloading visual feedback to a separate binary, the system prevents UI-heavy operations in the GUI from interfering with time-sensitive overlay rendering.

## Summary

- OpenLogi divides functionality across three independent processes: the GUI (`openlogi-desktop`), the privileged Agent (`openlogi-agent`), and the visual Overlay (`openlogi-overlay`).
- All inter-process communication occurs through a versioned tarpc contract defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs).
- The Agent is the sole process accessing `/dev/hidraw` and HID++ devices, isolating hardware permissions from the user interface.
- Crash isolation ensures GUI failures do not interrupt device communication handled by the Agent.
- The Overlay runs as a lightweight sibling process for low-latency Actions Ring rendering without device I/O overhead.

## Frequently Asked Questions

### Why does OpenLogi use three separate processes instead of threads?

OpenLogi uses process isolation rather than threads to enforce privilege boundaries and crash containment. The Agent requires elevated permissions for hardware access on macOS and Linux, while the GUI and Overlay should run with standard user privileges. Additionally, if the GUI crashes, the Agent continues managing devices independently because it operates in a separate memory space.

### How do the GUI and Overlay communicate with the Agent?

Both processes connect to the Agent through a local socket using **tarpc**, a RPC framework for Rust. The IPC contract in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) defines the message types, ensuring type-safe communication across process boundaries. The socket path (such as `/tmp/openlogi.sock`) is shared at startup, allowing each process to establish its client connection.

### What happens if the Agent process crashes while devices are connected?

If the Agent crashes, all HID device connections terminate because the Agent is the sole process managing `/dev/hidraw` descriptors and HID++ protocol state. The GUI detects the disconnect through the IPC layer and can display a reconnection dialog. However, the Overlay will also lose its subscription channel and stop rendering until the Agent restarts and restores the IPC socket.

### Can the Overlay run without the GUI active?

Yes, the Overlay runs as a sibling process rather than a child process of the GUI. It can connect directly to the Agent's IPC socket independently. This design allows the Actions Ring to appear when hotkeys trigger, even if the configuration window is closed, because the Agent maintains the active profile state and can launch or signal the Overlay directly.