# OpenLogi IPC Types and Payload Constraints: A Complete Guide

> Explore OpenLogi IPC types and payload constraints. Discover how bincode-serialized structs and append-only enums ensure safe communication between GUI client and agent.

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

---

**OpenLogi uses bincode-serialized structs and append-only enums for all inter-process communication between its GUI client and agent, with strict versioning rules that prevent breaking changes.**

The **OpenLogi** project's IPC protocol defines how the GUI, CLI, and overlay clients communicate with the system agent. Every payload crossing this boundary is defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) and serialized using **bincode**, a compact binary format that encodes enum variant indexes directly. This design choice imposes specific constraints that every contributor must understand when extending the protocol.

## Core IPC Types in OpenLogi

The OpenLogi IPC layer organizes data into three categories: **status reporting**, **pairing workflows**, and **action ring interactions**. Each category uses dedicated structs and enums with documented stability guarantees.

### Agent Status and Health Types

The **`AgentStatus`** struct serves as the primary heartbeat from agent to client. It combines system permissions, version information, and inventory health in a single DTO:

```rust
// Key fields in AgentStatus (from crates/openlogi-ipc/src/ipc.rs)
pub struct AgentStatus {
    pub accessibility_granted: bool,
    pub hook_installed: bool,
    pub launch_at_login: bool,
    pub inventory: InventoryHealth,  // Enum: append-only constraint
    pub protocol_version: u32,       // Must match PROTOCOL_VERSION constant
    pub agent_version: String,
    pub input_monitoring_granted: bool,
    pub hid_open_failures: u32,
}

```

The nested **`InventoryHealth`** enum demonstrates the **append-only rule**:

```rust
pub enum InventoryHealth {
    Scanning,      // index 0
    Ready,         // index 1
    Unavailable,   // index 2
    // New variants MUST be added here, never reordered
}

```

Because bincode encodes `Scanning` as `0`, `Ready` as `1`, and `Unavailable` as `2`, any reordering or removal would desynchronize the client and agent. This constraint is explicitly documented in the source: `/// bincode encodes the variant *index*, so variants are append-only`.

### Snapshot and Observation Types

The **`AgentSnapshot`** struct aggregates complete state to prevent data mismatches:

```rust
pub struct AgentSnapshot {
    pub status: AgentStatus,
    pub inventory: Vec<DeviceInventory>,
    pub standalone: Vec<StandaloneDevice>,
    pub camera_active: bool,
    pub pairing: Option<PairingPhase>,  // None when not pairing
    pub foreground: ForegroundApps,
}

```

The **`Observation`** struct wraps this snapshot with a generation counter for level-triggered hold semantics:

```rust
pub struct Observation {
    pub generation: u64,
    pub snapshot: AgentSnapshot,
}

```

The client passes back `generation` to implement `OBSERVE_HOLD`, allowing the agent to skip updates when no state change occurred.

### Pairing Workflow Types

Pairing uses a state machine represented by **`PairingPhase`** and streamed updates via **`PairingUpdate`**:

| Type | Purpose | Key Variants |
|------|---------|--------------|
| `PairingPhase` | Current pairing state | `Searching`, `Found(Vec<FoundDevice>)`, `Pairing`, `Passkey(PasskeyMethod)`, `Paired { slot: u8 }`, `Failed(PairingFailure)` |
| `PairingUpdate` | Streamed events to GUI | `Searching`, `DeviceFound(FoundDevice)`, `Passkey(PasskeyMethod)`, `Paired { slot }`, `Failed(PairingFailure)` |
| `PairingFailure` | Typed error details | `Hid`, `ReceiverNotFound`, `Register`, `Timeout`, `Device`, `Cancelled`, `ReceiverBusy`, `WatcherUnavailable`, `AgentRestarted`, `ReceiverAccessUnavailable`, `AlreadyActive`, `UnknownDevice`, `NoActiveSession` |
| `PairingCommandError` | Immediate command rejection | `AlreadyActive`, `ReceiverBusy`, `WatcherUnavailable`, `UnknownDevice`, `NoActiveSession` |

Both enums follow the **append-only constraint**. New failure reasons or pairing phases must be added at the end, preserving existing indexes.

### Action Ring and Overlay Types

The overlay receives presentation data only, never executable actions:

```rust
pub struct ActionRingPresentation {
    pub label: String,
    pub literal: String,
    pub icon: Icon,  // Renderable icon identifier
}

pub struct ActionRingInvocation {
    pub session_id: u64,  // Correlates hover/activate/cancel commands
    pub slots: BTreeMap<ActionRingSlot, ActionRingPresentation>,
    pub language: String,
}

pub struct RingObservation {
    pub generation: u64,
    pub invocation: Option<ActionRingInvocation>,  // None when ring hidden
}

```

The `session_id` field ensures that user interactions correlate to the correct invocation, preventing race conditions between ring updates and user input.

### Event and Client Identification Types

The **`MonitorEvent`** enum carries live input events:

```rust
pub enum MonitorEvent {
    Button { button: Button, pressed: bool },
    Scroll { delta_x: i32, delta_y: i32 },
    CaptureInterrupted,  // Emitted when secure input blocks capture
}

```

The **`ClientKind`** enum identifies connecting clients after the version handshake:

```rust
pub enum ClientKind {
    Gui,     // index 0
    Cli,     // index 1
    Overlay, // index 2
}

```

## Critical Constraints on OpenLogi IPC Payloads

### Append-Only Enums (Hard Constraint)

Every enum in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) carries the same stability requirement. The bincode serializer writes variant indexes as raw integers—no field names, no schema versioning. Reordering `InventoryHealth::Ready` to index 0 would cause a client expecting `Scanning` to deserialize it as `Ready` instead.

This pattern appears repeatedly in the source comments between lines 85-98 of [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs), warning that variants are append-only.

### Versioned Protocol Handshake

The constant **`PROTOCOL_VERSION`** (currently `29`) guards connection establishment:

```rust
// From crates/openlogi-ipc/src/ipc.rs lines 64-71
pub const PROTOCOL_VERSION: u32 = 29;

```

Both client and agent verify this value immediately after connecting. A mismatch aborts the handshake rather than risk misinterpreted payloads.

The `Agent` tarpc trait also respects ordering constraints—new methods must append to the end, not insert before existing ones, as method dispatch relies on stable indexes.

### Whole-State Transfer Design

Methods like `snapshot()`, `observe()`, and `observe_action_ring()` return complete state rather than deltas. This enables **connection recovery** without replay history: a reconnecting client simply requests current state and continues from there.

The trade-off is higher bandwidth for simpler client logic and stronger consistency guarantees.

## Working with OpenLogi IPC in Code

### Serializing a Pairing Update

```rust
use openlogi_ipc::ipc::{PairingUpdate, FoundDevice};
use bincode;

// Construct a device discovery event
let payload = PairingUpdate::DeviceFound(FoundDevice {
    address: [0x00, 0x1A, 0x7D, 0x12, 0x34, 0x56],
    name: "MX Master 3S".into(),
});

// Serialize to wire format
let bytes = bincode::serialize(&payload)
    .expect("bincode serialization infallible for IPC types");

// Deserialize on receiving side
let received: PairingUpdate = bincode::deserialize(&bytes)
    .expect("version mismatch caught at handshake");
assert!(matches!(received, PairingUpdate::DeviceFound(_)));

```

### Requesting and Inspecting Agent State

```rust
use openlogi_ipc::client::Connection;
use openlogi_ipc::ipc::AgentClient;

async fn monitor_inventory(mut conn: Connection) -> Result<(), Box<dyn std::error::Error>> {
    let client = AgentClient::new(conn);
    
    // `snapshot()` returns complete AgentSnapshot
    let snap = client.snapshot().await?;
    
    match snap.status.inventory {
        InventoryHealth::Ready => println!("Devices enumerated: {}", snap.inventory.len()),
        InventoryHealth::Scanning => println!("Enumeration in progress..."),
        InventoryHealth::Unavailable => eprintln!("Device access failed"),
    }
    
    // Check for active pairing session
    if let Some(phase) = snap.pairing {
        println!("Pairing active: {:?}", std::mem::discriminant(&phase));
    }
    
    Ok(())
}

```

## Key Source Files

Understanding the OpenLogi IPC system requires familiarity with four primary files:

- **[`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)** – All payload types, the `Agent` service contract, and protocol version constant
- **[`crates/openlogi-ipc/src/client.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/client.rs)** – `AgentClient` implementation and connection management
- **[`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs)** – Bincode framing and low-level I/O
- **[`crates/openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/tests/wire_format.rs)** – Format stability tests across protocol versions

## Summary

- **Bincode serialization** determines all IPC constraints—compact but index-sensitive
- **Append-only enums** prevent breaking changes; new variants extend, never reorder
- **Protocol version constant** (`29`) enforces client-agent compatibility at handshake
- **Whole-state snapshots** simplify recovery but require complete DTO design
- **Typed error enums** (`PairingFailure`, `PairingCommandError`) replace string errors for structured handling

## Frequently Asked Questions

### What happens if I reorder enum variants in the OpenLogi IPC definitions?

Deserialization will silently produce incorrect values. If you swap `InventoryHealth::Scanning` (index 0) with `Ready` (index 1), a client receiving index 0 will interpret it as `Ready` instead of `Scanning`. The version handshake cannot catch this because both sides run the same protocol version. Always append new variants at the end.

### How does OpenLogi handle client-agent version mismatches?

The `PROTOCOL_VERSION` constant (defined in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) lines 64-71) is exchanged immediately after connection. Either side aborts if the values differ. This prevents scenarios where new fields are unknown or enum variants misalign. Upgrade both client and agent together.

### Why does OpenLogi use whole-state snapshots instead of deltas?

Complete snapshots in `AgentSnapshot` and `Observation` eliminate the need for change history and replay logic. A reconnecting client requests current state and resumes operation. This trades bandwidth for reliability, which matches OpenLogi's use case where state size remains modest but connection stability varies.

### Can I add new fields to existing IPC structs?

Yes, with caution. Bincode encodes structs field-by-field in declaration order. Adding a field at the end preserves compatibility if the receiving side ignores trailing data (older clients) or uses default values (new clients reading old data). The safer pattern is creating new method variants in the `Agent` trait, appended after existing ones.