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
- Startup:
services::ipc::launch::connect_loopspawns and retries until socket connection succeeds - Handshake: Agent sends
AgentLink::Ready(status); desktop stores inAppState.agent.link - Active Polling: GUI calls
IpcClient::poll_event_monitororpoll_statuson timer; agent enables monitoring only during active polls - State Synchronization: Results merge into
AppStateviapush_monitor_eventsandset_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →