# How OpenLogi's Multi-Process Architecture Works: A Deep Dive into the Three-Process Design

> Explore OpenLogi's multi-process architecture. Discover how its three independent processes, GUI, agent, and overlay, use local-socket IPC for hardware access isolation and consistent device state for multiple UI clients.

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

---

**OpenLogi uses a three-process architecture where the GUI, agent, and overlay run as independent binaries that communicate exclusively through a local-socket IPC layer, isolating hardware access in the agent while allowing multiple UI clients to share consistent device state.**

OpenLogi (available at `AprilNEA/OpenLogi`) implements a robust **multi-process architecture** that separates user interface rendering from hardware interaction. By dividing responsibilities across three distinct processes communicating through Inter-Process Communication (IPC), the system achieves crash isolation and allows multiple UI components to access a single source of truth for device state.

## The Three-Process Architecture

OpenLogi splits runtime responsibilities into three independent processes that communicate solely through a local-socket IPC layer. This design confines hardware access to a single authority while enabling flexible UI development.

### GUI (Desktop) Process

The **desktop process** (`crates/openlogi-desktop`) serves as the main GPUI-based user interface. This process does **not** perform any HID or input handling itself; it only renders UI elements, reads configuration, and polls the agent for the current device state. According to the source code in [`crates/openlogi-desktop/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/main.rs), this binary starts the GPUI application and establishes a connection to the agent's socket.

### Agent Process

The **agent process** (`crates/openlogi-agent`) is the long-running background service that owns the input hook and performs all hardware interaction. As implemented in [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs), this binary creates the IPC server, initializes the HID hook, and manages DPI cycles, SmartShift, and the Actions Ring session state. All HID++ reads and writes are confined to this process, making it the sole authority on hardware state.

### Overlay Process

The **overlay process** (`crates/openlogi-overlay`) is a lightweight IPC client that draws the cursor-centered Actions Ring. While it shares the same UI library (`openlogi-ui`) as the desktop, it runs as a separate binary positioned above any window without requiring the heavy desktop process. As noted in the architecture documentation, the overlay is a **sibling of the GUI, not a child**, maintaining an independent lifecycle.

## IPC Communication Layer

The three processes communicate using **tarpc + bincode** over an `interprocess` local socket, with the contract defined in [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md).

### Versioned Wire Format

The IPC protocol uses a **versioned and append-only** wire format to ensure backward compatibility. The service definition lives in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs), where the tarpc service interface defines available methods for querying devices, setting DPI, and managing profiles.

### Connection Pattern

When the desktop starts, it attempts to connect to the agent's socket at `/tmp/openlogi_socket`. If the agent is not running, the desktop **spawns a fresh agent** and waits for the socket to become available. The overlay follows the same pattern, connecting to the same socket as the desktop. Because the socket serves as the single source of truth, both the GUI and overlay maintain a consistent view of device state, DPI settings, SmartShift status, and profile information.

## Process Lifecycle and Startup Sequence

The startup sequence ensures the agent acts as the authority while other processes act as clients:

1. **Desktop startup** (`cargo run -p openlogi-desktop`) checks for the agent socket
2. **Agent spawn** occurs automatically if no socket exists
3. **Overlay launch** connects to the existing socket independently
4. **State synchronization** happens through tarpc method calls

This architecture provides **crash isolation**: a UI panic does not corrupt the device state, and the agent continues serving other clients even if the desktop or overlay restarts.

## Extending the Architecture

Developers can extend or utilize this multi-process design through three primary patterns.

### Implementing New IPC Methods

To add functionality across all processes:

1. Add the function to [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)
2. Update the tarpc service definition
3. Regenerate the client stubs

The change propagates automatically to the desktop, agent, and overlay because they all import the same `openlogi-ipc` crate.

### Using the Mock Agent for Testing

For UI-only development without physical hardware, run the mock agent:

```bash
cargo run -p openlogi-agent --bin openlogi-agent-mock &
cargo run -p openlogi-desktop

```

The mock binary (`openlogi-agent-mock`) serves a scripted inventory over the socket, allowing the desktop or overlay to be exercised without any physical Logitech hardware, as documented in [`crates/openlogi-agent/src/bin/openlogi-agent-mock.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/bin/openlogi-agent-mock.rs).

### Building Custom IPC Clients

Any third-party tool can become an IPC client by linking against `crates/openlogi-ipc` and using the generated tarpc client:

```rust
use openlogi_ipc::ipc::{AgentServiceClient, AgentService};
use tarpc::client::Config;
use interprocess::local_socket::LocalSocketStream;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect to the agent's socket (the same path the desktop uses)
    let stream = LocalSocketStream::connect("/tmp/openlogi_socket")?;
    let client = AgentServiceClient::new(Config::default(), stream).await?;

    // Example: list all attached devices
    let devices = client.list_devices().await?;
    println!("Devices: {:#?}", devices);

    // Example: set DPI for device 0
    client.set_dpi(0, 1200).await?;
    Ok(())
}

```

This allows external scripts to query or modify profiles, DPI, or SmartShift while the agent remains the sole authority on the hardware, as implemented in [`crates/openlogi-device/src/write.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write.rs).

## Summary

- **OpenLogi's multi-process architecture** separates the GUI, agent, and overlay into three independent binaries to isolate hardware access and improve stability.
- **The agent process** (`crates/openlogi-agent`) owns all HID++ communication and serves as the single source of truth via a local socket at `/tmp/openlogi_socket`.
- **IPC communication** uses tarpc with bincode serialization over `interprocess` local sockets, with a versioned, append-only wire format defined in [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md).
- **Crash isolation** ensures that UI process failures do not affect hardware state or other clients.
- **Developer tooling** includes a mock agent (`openlogi-agent-mock`) for hardware-free testing and a reusable IPC client library for third-party integrations.

## Frequently Asked Questions

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

OpenLogi uses three processes to achieve **crash isolation** and privilege separation. By confining HID++ hardware access to the agent process alone, a UI panic in the desktop or overlay cannot corrupt device state or leave hardware in an inconsistent state. Additionally, running the overlay as a separate sibling process allows it to render above any window without requiring the full desktop environment to be running.

### How do the processes communicate with each other?

The processes communicate exclusively through **tarpc + bincode** over a local socket provided by the `interprocess` crate. The socket path is `/tmp/openlogi_socket`, and the protocol is defined in [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md) with a versioned, append-only wire format. Both the desktop and overlay act as clients to the agent's IPC server, polling for device state and sending configuration commands through this interface.

### Can I run the GUI without the agent running first?

Yes. When launching the desktop via `cargo run -p openlogi-desktop`, the GUI automatically checks for the agent socket and **spawns a fresh agent** if one is not already running. The desktop then waits for the socket to become available before proceeding. This ensures that users do not need to manually start the background process, though developers can also run the agent independently if desired.

### How do I develop UI features without physical Logitech hardware?

Use the **mock agent** by running `cargo run -p openlogi-agent --bin openlogi-agent-mock` before starting the desktop. This binary, located at [`crates/openlogi-agent/src/bin/openlogi-agent-mock.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/bin/openlogi-agent-mock.rs), provides a scripted inventory of virtual devices over the IPC socket. The desktop and overlay connect to this mock just as they would the real agent, allowing full UI development and testing without requiring physical Logitech hardware or HID++ access.