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

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:

//! 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/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:

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/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:

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/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:

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

→ [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:

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:

// 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/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:

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/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:

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

→ [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:

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
// 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 AgentSession struct, status caching, monitor event buffering
crates/openlogi-desktop/src/services/ipc/launch.rs Resilient connection loop with infinite retry
crates/openlogi-desktop/src/services/ipc.rs tarpc client, Command enum, poll logic
crates/openlogi-desktop/src/windows/add_device.rs Demonstrates agent-side hardware operations
crates/openlogi-desktop/src/windows/settings/diagnostics.rs UI polling pattern (~300ms refresh)
crates/openlogi-desktop/src/runtime.rs Async runtime coordination and task lifecycle
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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →