# How OpenLogi's GPUI Desktop Application Handles Process Isolation and Agent Polling

> Learn how OpenLogi's GPUI desktop app achieves process isolation and agent polling using local-socket IPC and tarpc for secure communication and state management.

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

---

**OpenLogi enforces strict process isolation by running the GPUI desktop client (`openlogi-desktop`) and the privileged agent (`openlogi-agent`) as separate processes that communicate exclusively over a local-socket IPC channel using tarpc, with the desktop maintaining agent state through a periodic polling loop.**

The OpenLogi project implements a security-conscious architecture for its cross-platform input device manager. According to the OpenLogi source code, the GPUI-based desktop application never interacts directly with hardware or system-level event hooks. Instead, all privileged operations are delegated to a dedicated agent process, with bidirectional synchronization achieved through a robust polling mechanism.

## Process Isolation Architecture

### Agent Ownership of Privileged Operations

The `openlogi-agent` binary owns all system-wide event hooks and direct device I/O. On macOS, this means the agent manages the `CGEventTap`; on Linux, it handles `evdev/uinput`. The desktop client is explicitly prohibited from hardware access.

This design is documented in the source comments for the Add Device window:

```rust
//! Pairing runs in the **agent** (it owns device I/O, so it opens the receiver,
//! not the GUI). This window is a thin state machine that talks to the agent

```

→ [[`crates/openlogi-desktop/src/windows/add_device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/windows/add_device.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/windows/add_device.rs#L3-L9)

### Lightweight Agent Session State

The desktop's global `AppState` contains an `AgentSession` struct that stores only connection metadata and cached snapshots—never device handles:

```rust
pub(super) struct AgentSession {
    link: AgentLink,
    foreground: ForegroundApps,
    last_ready_inventory: Vec<DeviceInventory>,
    // …
}

```

→ [[`crates/openlogi-desktop/src/state/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/agent.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/state/agent.rs#L8-L15)

### Delegated System Prompts

When macOS Accessibility authorization is required, the desktop forwards the request to the agent. The prompt must appear under the agent's binary name; otherwise, the event hook would lack proper authorization:

```rust
pub fn request_accessibility_prompt(&self) {
    self.send_ipc(crate::services::ipc::Command::RequestAccessibilityPrompt);
}

```

→ [[`crates/openlogi-desktop/src/state/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/agent.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/state/agent.rs#L61-L67)

## Agent Polling Mechanism

### Resilient Connection Establishment

At launch, the desktop spawns a background task that implements infinite retry logic until the agent's socket becomes available:

```rust
/// hand, and the poll loop keeps retrying the connection regardless.

```

→ [[`crates/openlogi-desktop/src/services/ipc/launch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc/launch.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/services/ipc/launch.rs#L30-L33)

The connection loop is typically started from [`main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/main.rs):

```rust
tokio::spawn(openlogi_desktop::services::ipc::launch::connect_loop(state.clone()));

```

### On-Demand Monitoring with Implicit Lifecycle

The IPC client does not use per-request timers. Instead, the `observe` method returns a generation number, and the poll call itself drives timing. The agent automatically enables live monitoring when polled and disables it when polling ceases:

```rust
// The poll answered. `pending` is finished, so it is deliberately...
// The first poll enables monitoring agent‑side; the agent auto‑disables it once polls stop.

```

→ [[`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/services/ipc.rs#L123-L130)

### Event Caching and UI Integration

The desktop maintains a bounded buffer of monitor events (capped at 200 entries) within `AppState`:

```rust
pub fn push_monitor_events(&mut self, events: Vec<openlogi_ipc::MonitorEvent>) {
    const MAX: usize = 200;
    self.agent.monitor_events.extend(events);
    let overflow = self.agent.monitor_events.len().saturating_sub(MAX);
    self.agent.monitor_events.drain(..overflow);
}

```

→ [[`crates/openlogi-desktop/src/state/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/agent.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/state/agent.rs#L33-L41)

UI components access this data via `AppState::monitor_events()` for rendering diagnostics views.

### Window-Scoped Polling Intervals

The Settings window's diagnostics pane demonstrates practical polling implementation with ~300ms refresh cycles:

```rust
/// poll task refreshes every ~300ms, so...

```

→ [[`crates/openlogi-desktop/src/windows/settings/diagnostics.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/windows/settings/diagnostics.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-desktop/src/windows/settings/diagnostics.rs#L82-L88)

A typical UI-side poll implementation:

```rust
let poll = cx.spawn(async move |state, cx| {
    loop {
        state.send_ipc(crate::services::ipc::Command::PollStatus);
        cx.sleep(Duration::from_millis(300)).await?;
    }
});

```

## Complete Data Flow

1. **Startup**: `services::ipc::launch::connect_loop` spawns and retries until socket connection succeeds
2. **Handshake**: Agent sends `AgentLink::Ready(status)`; desktop stores in `AppState.agent.link`
3. **Active Polling**: GUI calls `IpcClient::poll_event_monitor` or `poll_status` on timer; agent enables monitoring only during active polls
4. **State Synchronization**: Results merge into `AppState` via `push_monitor_events` and `set_agent_link`; UI refreshes only on actual changes

```rust
// Efficient UI update pattern
if app_state.set_agent_link(new_link) {
    // trigger refresh only on actual change
    cx.update_view();
}

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`crates/openlogi-desktop/src/state/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/agent.rs) | `AgentSession` struct, status caching, monitor event buffering |
| [`crates/openlogi-desktop/src/services/ipc/launch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc/launch.rs) | Resilient connection loop with infinite retry |
| [`crates/openlogi-desktop/src/services/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc.rs) | tarpc client, `Command` enum, poll logic |
| [`crates/openlogi-desktop/src/windows/add_device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/windows/add_device.rs) | Demonstrates agent-side hardware operations |
| [`crates/openlogi-desktop/src/windows/settings/diagnostics.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/windows/settings/diagnostics.rs) | UI polling pattern (~300ms refresh) |
| [`crates/openlogi-desktop/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/runtime.rs) | Async runtime coordination and task lifecycle |
| [`crates/openlogi-agent/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/main.rs) | Privileged agent entry point |

## Summary

- **Strict process boundaries**: Desktop and agent never share memory; all communication traverses tarpc over local socket
- **Agent exclusivity**: Hardware access, event hooks, and system prompts execute only in `openlogi-agent`
- **Resilient connectivity**: Desktop implements infinite retry with backoff until agent socket available
- **Lifecycle-managed monitoring**: Agent automatically enables/disables event monitoring based on poll presence
- **Bounded caching**: Monitor events capped at 200 entries with FIFO eviction
- **Efficient UI updates**: State-driven rendering triggers only on actual data changes

## Frequently Asked Questions

### Why does OpenLogi use process isolation instead of threads?

Process isolation prevents privilege escalation vulnerabilities. The agent requires elevated permissions for `CGEventTap` (macOS) and `evdev` access (Linux). By running these in a separate binary, the GPUI desktop—which processes untrusted user input—cannot directly access hardware or inject events, even if compromised.

### What happens when the agent process crashes?

The desktop's `connect_loop` in [`services/ipc/launch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/services/ipc/launch.rs) implements infinite retry with no backoff ceiling. When the socket disappears, the poll loop continues attempting reconnection, and `AppState` transitions `AgentLink` to a disconnected state. UI components render appropriate offline indicators until the agent restarts and the handshake completes.

### How does the polling mechanism affect battery life?

The agent disables live monitoring automatically when polls cease, eliminating unnecessary CPU wake cycles. The ~300ms polling interval used in diagnostics windows is UI-scoped; background operations use less frequent status checks. This design ensures monitoring runs only when explicitly requested by visible interface elements.

### Can multiple desktop instances connect to one agent?

The current implementation uses a single socket endpoint. While tarpc supports multiple concurrent connections, the agent's internal state model assumes exclusive desktop ownership. Attempting multiple connections would result in contention for `AgentLink` state management and undefined behavior in monitor event routing.