# OpenLogi IPC Protocol Contract: tarpc, bincode, and Local Socket Communication

> Explore OpenLogi's IPC protocol contract: tarpc, bincode, and local sockets ensure stable communication. Learn about its append-only versioning strategy for reliable data transfer.

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

---

**OpenLogi's IPC protocol contract uses a tarpc service serialized with bincode over local sockets, enforcing wire-format stability through an append-only versioning strategy.**

The inter-process communication (IPC) between OpenLogi's background agent and its clients (GUI, CLI, and overlay helper) is governed by a strict contract defined in the `openlogi-ipc` crate. This contract ensures type-safe, versioned communication across all OpenLogi components using Rust-native RPC tooling.

## What Is the OpenLogi IPC Protocol?

The protocol is implemented as a **tarpc** service—the `Agent` trait defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs). All messages are serialized using **bincode**, a compact binary serialization format that preserves Rust data structure layouts exactly. This combination provides zero-overhead RPC calls between processes on the same machine.

The contract lives in the `openlogi-ipc` crate, which serves as the single source of truth for both the agent (server) and all client implementations.

## Transport Layer and Serialization

### Local Socket Transport

Clients connect to the agent via a local Unix/AF_UNIX socket provided by the **`interprocess`** crate. The connection logic resides in [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs), which handles the low-level socket establishment and stream management.

### Request/Response with Long-Polling

Because tarpc is strictly request/response, the protocol simulates "push" notifications through **long-polling**. Methods like `observe`, `observe_action_ring`, and `next_pairing` accept a `generation` number and block until the agent state changes or a timeout expires (`OBSERVE_HOLD`). This allows the agent to stream state updates without requiring a native publish/subscribe mechanism.

## Wire-Format Stability and Versioning

The protocol enforces **append-only** evolution to maintain backward compatibility:

- **Method ordering** is part of the wire format. The first method (`protocol_version`) must remain at index 0, and new methods can only be appended to the trait (see lines 38-45 in [`src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/ipc.rs)).
- **Type variants** are also append-only. Enums like `InventoryHealth`, `PairingPhase`, and `MonitorEvent` must only grow new variants; existing indices are fixed by bincode's serialization.
- **Breaking changes** require bumping `PROTOCOL_VERSION` (currently **30**) and updating the golden-test fixtures in [`tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/tests/wire_format.rs).

This design ensures older binaries detect incompatibility immediately via the `protocol_version` handshake, preventing subtle desynchronization bugs.

## Key Elements of the Contract

### The Agent Trait

The `Agent` trait in [`src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/ipc.rs) declares every available RPC method:

- **`protocol_version()`** – Returns the `PROTOCOL_VERSION` constant for handshake validation.
- **`status()`** and **`inventory()`** – Query current agent state.
- **`set_dpi()`**, **`start_pairing()`** – Control hardware configuration.
- **`declare_client(kind)`** – Registers the client type (`Gui`, `Cli`, or `Overlay`) so the agent can adjust behavior (e.g., disabling macOS dormancy gates for overlays).

### Long-Polling Observation

State synchronization relies on generation-tracked long-polling:

- **`observe(generation)`** – Blocks until the `AgentSnapshot` changes, returning the new generation and full state.
- **`observe_action_ring(generation)`** – Specifically monitors action ring invocations for the overlay helper.
- **`next_pairing(generation)`** – Waits for pairing state machine transitions.

Clients increment the generation counter with each response to maintain consistency.

### Client Type Declaration

After version verification, clients must call **`declare_client`** with their kind:

- **`Gui`** – The main desktop application.
- **`Cli`** – Command-line interface tools.
- **`Overlay`** – The helper process that displays on-screen rings.

This distinction allows the agent to optimize resource usage, such as arming the dormancy gate only for GUI clients.

## Implementation Examples

### Basic Client Connection and Version Check

```rust
use openlogi_ipc::client::AgentClient;
use openlogi_ipc::transport::connect_to_agent;

// Connect to the local Unix socket the agent listens on
let conn = connect_to_agent()?;                      // src/transport.rs
let mut client = AgentClient::new(conn);             // src/client.rs

// Verify protocol compatibility
let version = client.protocol_version().await?;
assert_eq!(version, openlogi_ipc::PROTOCOL_VERSION);

// Query current state
let status = client.status().await?;
let inventory = client.inventory().await?;

```

### Long-Polling State Updates

```rust
// Observe state changes using generation tracking
let mut generation = 0_u64;
loop {
    let observation = client.observe(generation).await?;
    // Generation increases only when state changes
    generation = observation.generation;
    println!("New snapshot: {:?}", observation.snapshot);
}

```

### Overlay Helper Pattern

```rust
use openlogi_ipc::ClientKind;

// Declare as overlay to bypass dormancy gates
client.declare_client(ClientKind::Overlay).await?;

let mut gen = 0_u64;
loop {
    let ring = client.observe_action_ring(gen).await?;
    gen = ring.generation;
    if let Some(inv) = ring.invocation {
        println!("Display ring {} with {} slots", inv.session_id, inv.slots.len());
    }
}

```

## Summary

- OpenLogi uses **tarpc** over **bincode** for IPC, transported via local sockets using the `interprocess` crate.
- The contract is defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) as the `Agent` trait.
- **Wire-format stability** is enforced through append-only evolution of methods and types, with `PROTOCOL_VERSION` (currently 30) guarding against incompatibility.
- Push notifications are implemented via **long-polling** (`observe` methods) rather than native pub/sub.
- Clients must declare their type (`Gui`, `Cli`, or `Overlay`) to influence agent behavior.

## Frequently Asked Questions

### What serialization format does OpenLogi use for IPC?

OpenLogi uses **bincode** for all IPC serialization. This is a compact, binary format that encodes Rust data structures directly, making it ideal for high-performance local communication. The `Agent` trait methods and all parameter types are serialized with bincode before transmission over the local socket.

### How does the agent push updates to clients without native pub/sub?

Because tarpc only supports request/response patterns, OpenLogi implements **long-polling**. Clients call methods like `observe` or `observe_action_ring` with a generation number, and the agent blocks the response until state changes or a timeout occurs. The client then immediately re-polls with the new generation, creating an efficient event stream without maintaining persistent server-side subscriptions.

### What happens if client and agent have incompatible protocol versions?

All clients must call `protocol_version()` immediately after connection. If the returned version does not match the client's expected `PROTOCOL_VERSION` constant (currently 30), the client should disconnect. Because the wire format is positional and append-only, version mismatches indicate potential binary incompatibility that would corrupt deserialization.

### Where is the IPC protocol contract defined in the source code?

The contract is centralized in the `openlogi-ipc` crate. The primary definition is in **[`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)**, which contains the `Agent` trait, all shared types, and the `PROTOCOL_VERSION` constant. Transport logic lives in [`src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/transport.rs), while [`tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/tests/wire_format.rs) contains golden tests that verify the binary representation remains stable across changes.