# How OpenLogi Separates GUI, Agent, and Overlay Components in a Multi-Process Architecture

> Discover how OpenLogi's multi-process architecture separates GUI, agent, and overlay components using IPC for secure and centralized device I/O and efficient UI communication.

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

---

**OpenLogi employs a strict three-process design where the GUI (`openlogi-desktop`), Agent (`openlogi-agent`), and Overlay (`openlogi-overlay`) operate as isolated IPC clients and servers, ensuring device I/O remains centralized in the agent while UI components communicate exclusively through a shared Tarpc contract.**

The AprilNEA/OpenLogi repository implements a Rust-based device management system that splits functionality across three distinct processes. This separation prevents UI crashes from disrupting hardware sessions and allows the background service to persist independently of user-facing applications. Understanding this architecture is essential for developers extending the codebase or troubleshooting process-specific failures.

## The Three-Process Model

OpenLogi's architecture documentation in [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md) (lines 38-44) specifies that three processes ship in every install: the **GUI**, the **Agent**, and the **Overlay**. Each component maintains strict isolation boundaries enforced by crate dependencies and IPC contracts.

### GUI Process (openlogi-desktop)

The GUI process serves as the settings application where users edit TOML configuration files and issue commands. Located in `crates/openlogi-desktop`, this process acts strictly as an IPC client that never accesses hardware directly. All configuration changes travel over a local-socket Tarpc connection to the agent, making the GUI entirely stateless regarding device I/O.

### Agent Process (openlogi-agent)

The agent functions as the always-on background daemon that owns all HID++ device sessions and input hooks. Implemented in [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs), it handles platform-specific event taps (CGEventTap on macOS, evdev/uinput on Linux, WH_MOUSE_LL on Windows) and manages device operations like button remapping and DPI adjustments. As the IPC server defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs), it listens on Unix sockets or Windows named pipes, serving as the single source of truth for hardware state.

### Overlay Process (openlogi-overlay)

The overlay renders the cursor-centered "Actions Ring" UI on demand without depending on the desktop crate. Strict isolation rules in [`.claude/rules/gui.md`](https://github.com/AprilNEA/OpenLogi/blob/main/.claude/rules/gui.md) (lines 11-14) prevent the overlay from importing `openlogi-desktop` or any desktop-only UI code. This minimal process, residing in `crates/openlogi-overlay`, acts as a pure IPC client that draws the ring and forwards user selections back to the agent for execution.

## Inter-Process Communication via Tarpc

All three processes share the Tarpc-based IPC contract defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). The agent initializes the server in its [`main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/main.rs) entry point, while both GUI and overlay instantiate `openlogi_ipc::client::Client` to invoke RPC methods like `set_action` and `get_status`.

Server initialization in the agent:

```rust
// crates/openlogi-agent/src/main.rs
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialise logging, device I/O, etc.
    let ipc_server = openlogi_ipc::server::Server::new();
    // Bind a Unix socket (or Windows pipe) and run the server
    ipc_server.serve().await?;
    Ok(())
}

```

Client usage from the overlay:

```rust
// crates/openlogi-overlay/src/main.rs
use openlogi_ipc::client::Client;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect to the agent's IPC socket
    let mut client = Client::connect().await?;
    // Ask the agent to execute the selected action
    client.set_action(Action::ShowActionsRing).await?;
    Ok(())
}

```

## Process Lifetimes and Spawning Mechanics

Each process follows distinct lifecycle rules enforced by the operating system. On macOS, the agent runs as a **launchd** service specified in [`launch_agent/mod.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/launch_agent/mod.rs), remaining alive regardless of GUI state. Linux installations use a **systemd user unit** (`systemctl --user enable --now openlogi-agent.service`) to maintain the daemon.

The overlay follows an on-demand lifecycle controlled by the GUI. When users trigger the "Show Actions Ring" shortcut, the desktop process spawns the overlay binary via `std::process::Command` and terminates it upon dismissal:

```rust
// crates/openlogi-desktop/src/ui.rs (simplified)
fn show_actions_ring() {
    // Launch the binary as a separate process
    std::process::Command::new("openlogi-overlay")
        .spawn()
        .expect("failed to start overlay");
}

```

This design ensures the GUI can restart without dropping device sessions, while the overlay's lightweight nature allows instant visibility changes without loading the full device stack.

## Summary

- The **agent** (`openlogi-agent`) owns all hardware I/O and runs as a persistent system service via launchd or systemd.
- The **GUI** (`openlogi-desktop`) operates as a stateless IPC client that modifies settings through RPC calls rather than direct device access.
- The **overlay** (`openlogi-overlay`) functions as an isolated, on-demand process that strictly avoids dependencies on the desktop crate.
- All components communicate through the shared `openlogi-ipc` Tarpc contract, with the agent serving as the sole server.

## Frequently Asked Questions

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

Separating components into processes prevents a UI crash from terminating active device sessions and allows the agent to run with different privileges or persist as a system service. This isolation ensures that restarting the settings GUI or dismissing the overlay never interrupts background input handling or hardware polling.

### How does the overlay communicate without importing the desktop crate?

The overlay relies exclusively on the `openlogi-ipc` crate for communication, enforced by GUI rules in [`.claude/rules/gui.md`](https://github.com/AprilNEA/OpenLogi/blob/main/.claude/rules/gui.md) that explicitly forbid importing `openlogi-desktop`. This architectural constraint ensures the overlay remains a lightweight, independent binary that only renders UI and forwards actions to the agent via Tarpc.

### What happens to device I/O when the GUI is closed?

Device I/O continues uninterrupted because the agent runs as a separate system service (launchd on macOS or systemd on Linux). The GUI merely acts as a configuration client; closing it only terminates the settings interface while the background daemon maintains all active device sessions and input hooks.

### Which file defines the RPC methods available to GUI and overlay?

The IPC contract resides in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs), which defines the Tarpc service interface containing methods like `set_action` and `get_status`. Both the agent implementation and client stubs import this shared definition, ensuring type-safe communication across process boundaries.