# How OpenLogi's Multi-Process Architecture Works for Developers: A Complete Guide

> Discover how OpenLogi's multi-process architecture empowers developers. Learn about its Desktop GUI, Agent, and Overlay processes and their IPC communication for efficient hardware access.

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

---

**OpenLogi separates runtime responsibilities into three independent processes—the Desktop GUI, the Agent, and the Overlay—that communicate exclusively through a local-socket IPC layer, with all hardware access isolated to a single background process.**

This **multi-process architecture** powers OpenLogi, an open-source Rust application for managing Logitech HID++ devices. By splitting concerns across process boundaries, the codebase achieves crash isolation, flexible UI layering, and clean extension points for developers building custom integrations or alternative interfaces.

---

## The Three Process Roles

OpenLogi's architecture assigns each critical responsibility to its own binary. These processes launch as a coordinated bundle but maintain independent lifecycles.

### Desktop GUI (`crates/openlogi-desktop`)

The **Desktop** is the primary GPUI-based user interface. Its sole responsibilities are rendering UI elements, reading configuration files, and polling the Agent for current device state. Per the source code, it performs **no HID or input handling whatsoever**—it is a pure IPC client.

In [[`crates/openlogi-desktop/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/main.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/main.rs), the desktop attempts to connect to the agent's socket on startup. If the Agent is not running, it **spawns a fresh Agent process** and blocks until the socket becomes available.

### Agent (`crates/openlogi-agent`)

The **Agent** is the long-running background process that owns all hardware interaction. It manages:

- The global input hook
- HID++ read/write operations
- DPI cycling and per-profile settings
- SmartShift state
- The Actions Ring session state

All device communication is confined to this crate. The Agent exposes a tarpc service defined in `crates/openlogi-ipc`, making it the **single source of truth** for device state. Other processes merely observe and request changes through this API.

The entry point in [[`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/main.rs) creates the IPC server and initializes the HID hook.

### Overlay (`crates/openlogi-overlay`)

The **Overlay** is a lightweight IPC client that draws the cursor-centered Actions Ring. It shares `openlogi-ui` with the Desktop but runs as a **separate binary**—specifically so it can position itself above any window without dragging the heavy Desktop process into that layering context.

Critically, the Overlay is a **sibling of the GUI, not a child process**. This independence prevents the Overlay's lifecycle from being tied to the Desktop window, as documented in [AGENTS.md lines 46-53](https://github.com/AprilNEA/OpenLogi/blob/master/AGENTS.md#L46-L53).

---

## IPC Layer: tarpc + bincode over Local Sockets

Communication between all three processes uses **tarpc** with **bincode** serialization over `interprocess` local sockets.

### Wire Protocol and Versioning

The IPC contract lives in [[`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/AGENTS.md). The wire format is:

- **Versioned** — changes are tracked across releases
- **Append-only** — new fields extend without breaking existing clients

This design allows the Desktop and Overlay to evolve independently of the Agent, provided they respect the versioned contract.

### Socket Path and Connection Pattern

All clients connect to the same socket path (default: `/tmp/openlogi_socket`). The Desktop and Overlay follow identical connection logic:

1. Attempt to connect to existing socket
2. If connection fails, spawn the Agent binary
3. Poll until the socket appears, then proceed

Because the socket is the single source of truth, both UI processes see **consistent device state, DPI settings, SmartShift status, and profile information**.

---

## Extending the Architecture: Three Developer Paths

Developers can leverage OpenLogi's **multi-process architecture** through well-defined extension points.

### 1. Implement New IPC Methods

Add functionality that propagates to all clients automatically:

- Define the method in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)
- Update the tarpc service definition
- Regenerate client stubs

Since Desktop, Agent, and Overlay all import the same `openlogi-ipc` crate, changes propagate without manual synchronization across crates.

### 2. Run the Mock Agent for UI-Only Development

For frontend work without physical hardware, use the mock binary:

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

```

The mock serves a scripted inventory over the socket, as noted in [AGENTS.md lines 30-33](https://github.com/AprilNEA/OpenLogi/blob/master/AGENTS.md#L30-L33). This enables rapid iteration on GPUI components without requiring an attached Logitech device.

### 3. Embed a Custom IPC Client

Any third-party tool can become a peer by linking against `openlogi-ipc`:

```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 (same path the desktop uses)
    let stream = LocalSocketStream::connect("/tmp/openlogi_socket")?;
    let client = AgentServiceClient::new(Config::default(), stream).await?;

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

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

```

This pattern lets external scripts query or modify profiles, DPI, or SmartShift while the Agent remains the **sole hardware authority**.

---

## Development Workflow Examples

### Auto-Starting the Agent with the Desktop

```bash

# GUI spawns agent automatically if not running

cargo run -p openlogi-desktop

# Verify socket creation

ls -l /tmp/openlogi_socket

```

### Running Desktop Against Mock Agent

```bash

# Terminal 1: Start mock

cargo run -p openlogi-agent --bin openlogi-agent-mock &

# Terminal 2: Launch desktop (connects to mock)

cargo run -p openlogi-desktop

```

---

## Key Source Files

| Component | File | Purpose |
|-----------|------|---------|
| Architecture overview | [[`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md)](https://github.com/AprilNEA/OpenLogi/blob/master/AGENTS.md#L15-L23) | Three-process design and lifecycle rules |
| IPC contract | [[`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/AGENTS.md) | tarpc service definition and versioning policy |
| Agent entry point | [[`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/main.rs) | IPC server and HID hook initialization |
| Desktop entry point | [[`crates/openlogi-desktop/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/main.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/main.rs) | GPUI app startup and Agent spawning |
| Overlay client | [[`crates/openlogi-overlay/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-overlay/src/main.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-overlay/src/main.rs) | Minimal IPC client for Actions Ring |
| Mock agent | [[`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)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/openlogi-agent-mock.rs) | Scripted inventory for UI testing |
| HID++ writes | [[`crates/openlogi-device/src/write.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-device/src/write.rs) | Hardware access (Agent-only) |

---

## Summary

- **Three processes**: Desktop (UI), Agent (hardware), Overlay (floating UI)—each with isolated responsibilities
- **Single IPC layer**: tarpc + bincode over `interprocess` local sockets, with versioned, append-only wire format
- **Crash isolation**: UI panics cannot corrupt device state; the Agent continues serving other clients
- **Extension points**: new IPC methods, mock agent for testing, custom clients via `openlogi-ipc` crate

---

## Frequently Asked Questions

### Why split the Overlay into a separate process instead of a Desktop window?

The Overlay must render above all other windows, including full-screen applications. Running it as a **sibling process** decouples its layering from the Desktop's window hierarchy. Per [AGENTS.md](https://github.com/AprilNEA/OpenLogi/blob/master/AGENTS.md#L46-L53), this keeps the Overlay lightweight and independent—if the Desktop restarts, the Actions Ring remains visible.

### How does the Agent prevent multiple clients from conflicting?

The Agent serializes requests through a single tarpc service instance. All state mutations (DPI changes, profile switches) flow through this centralized handler, which owns the exclusive HID++ connection. The socket acts as a natural serialization point—clients cannot bypass the Agent's internal consistency checks.

### Can I write a client in a language other than Rust?

Yes, though you must implement the tarpc + bincode protocol. The `openlogi-ipc` crate serves as the reference implementation. Alternative clients should replicate:

1. The socket path discovery (default `/tmp/openlogi_socket`)
2. The bincode serialization format
3. The service version handshake

Consider generating stubs from the tarpc definition in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) rather than reverse-engineering the wire format.

### What happens if the Agent crashes while the Desktop is running?

The Desktop detects the broken connection and **automatically respawns the Agent**, then reconnects. The Overlay follows the same recovery pattern. Device state is reconstructed from persistent storage in the Agent's startup sequence, though any in-flight Actions Ring session is interrupted.