# OpenLogi IPC Wire Format: How Positional Append-Only Design Ensures Binary Compatibility

> Explore OpenLogi's IPC wire format. Discover how its positional and append-only design ensures binary compatibility by leveraging declaration index and enum variant positioning for stable RPC communication.

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

---

**OpenLogi's IPC wire format is positional and append-only because tarpc encodes RPC methods by their declaration index and bincode encodes enum variants by position, making reordering or insertion a breaking change.**

This article examines the wire format design in the [AprilNEA/OpenLogi](https://github.com/AprilNEA/OpenLogi) repository, where the GUI client and agent communicate over a binary protocol built on **tarpc** for RPC and **bincode** for serialization. The positional append-only constraints govern how the `Agent` trait and all crossing enums evolve without breaking backward compatibility.

## Why Method Order Is Part of the Wire Format

The `Agent` service trait in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) uses `#[tarpc::service]` to generate request and response types. Tarpc creates an internal request enum where each variant corresponds to one RPC method, and **bincode encodes the variant index directly**.

This means:

- The **first method always encodes as `0`**, the second as `1`, and so on
- Inserting a method shifts all subsequent indices, corrupting existing clients
- New methods must be **appended only**, never inserted or reordered

The source code documents this invariant explicitly. In [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) lines 41–45, the trait comment states: *"Method order is part of the wire format. New methods must be added at the end."* The `protocol_version()` method must remain first because it's called during handshake before any compatibility checks complete.

```rust
// From crates/openlogi-ipc/src/ipc.rs
#[tarpc::service]
pub trait Agent {
    /// Method order is part of the wire format. New methods must be added at the end.
    /// This method must remain first for the version handshake to work.
    async fn protocol_version() -> u32;
    // ... existing methods in fixed positions ...
    async fn new_method(args: Args) -> Result<Output, Error>; // appended only
}

```

## Serde Enums Are Also Append-Only Across the Boundary

Any enum that serializes over IPC inherits the same positional constraint. Bincode does **not** use explicit `#[repr(u8)]` discriminants—it encodes the compile-time declaration index of each variant.

Public enums like `InventoryHealth`, `PairingPhase`, and `MonitorEvent` all carry source comments enforcing this rule. For example, `InventoryHealth` in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) lines 85–90 notes: *"Variants are append-only."*

```rust
// From crates/openlogi-ipc/src/ipc.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum InventoryHealth {
    /// Variants are append-only.
    Scanning,      // index 0
    Ready,         // index 1
    Unavailable,   // index 2 (appended later)
    // New variants MUST be added here, never reordered
}

```

Reordering `Ready` before `Scanning` would cause a client compiled against the old definition to decode `Scanning` (index 0) as `Ready`, silently corrupting application logic.

## Protocol Version Bumping for Breaking Changes

When the positional contract must change—whether from method reordering, enum reorganization, or semantic changes to existing operations—the `PROTOCOL_VERSION` constant is incremented. This constant lives at line 64 in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) and currently stands at **29**.

The handshake procedure enforces strict equality:

```rust
// Client-side version check during connection establishment
let reported_version = client.protocol_version().await?;
if reported_version != PROTOCOL_VERSION {
    eprintln!(
        "IPC version mismatch: client expects {} but agent reports {}",
        PROTOCOL_VERSION, reported_version
    );
    // Connection aborted—prevents mis-decoding
}

```

This design deliberately avoids runtime negotiation. Since the GUI and agent ship in the same release bundle, version equality guarantees that both sides were compiled with identical trait and enum definitions.

## Enforcement Mechanisms in the Codebase

| Mechanism | Location | Purpose |
|-----------|----------|---------|
| **Source comments** | [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) lines 37–45, 85–90, 87–101, 59–66 | Document invariants for human reviewers |
| **Golden-byte tests** | [`tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/tests/wire_format.rs) | Fail CI if wire format changes without version bump |
| **Bincode configuration** | [`transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/transport.rs) | Ensures `tokio_serde::Bincode::default()` (varint, little-endian) on both sides |
| **AGENTS.md** | Root of `openlogi-ipc` crate | High-level protocol contract for external contributors |

The [`wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/wire_format.rs) test regenerates serialized byte sequences for representative RPC calls and compares them against stored references. Any deviation—whether from method reordering, enum changes, or bincode option differences—causes a test failure, forcing explicit acknowledgment via `PROTOCOL_VERSION` increment.

## Transport Layer: Stable Bincode Configuration

The transport implementation in [`transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/transport.rs) wires `tokio_serde::Bincode` into the tarpc stack. This uses `bincode::DefaultOptions`, which specifies:

- **Varint encoding** for lengths and discriminants
- **Little-endian byte order**
- **Fixed-size integer encoding** for primitives

These defaults are stable across compiler versions and target architectures, ensuring that the same Rust types produce identical byte sequences on macOS, Windows, and Linux agents.

## Practical Impact: Safe Evolution Without Runtime Negotiation

The positional append-only design enables three operational benefits:

1. **Deterministic replay**—Logs and test fixtures capture byte-exact IPC streams that remain parseable across rebuilds
2. **Zero-overhead validation**—A single integer comparison replaces full schema negotiation
3. **Feature-safe deployment**—Adding `async fn new_capability()` at the trait end ships safely; old agents reject unknown methods via tarpc's generated dispatch, while old clients simply never call new methods

## Summary

- **Method position is the wire identifier**—tarpc + bincode encode by declaration index, not name or hash
- **Enums inherit the same constraint**—variant indices are positional, not discriminant values
- **Append-only evolution**—new methods and variants go at the end; never insert or reorder
- **PROTOCOL_VERSION guards breaking changes**—strict equality check prevents version drift
- **Golden tests enforce contract**—unintended wire format changes fail CI in [`wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/wire_format.rs)

## Frequently Asked Questions

### What happens if I reorder methods in the Agent trait?

Your code will compile but existing clients will mis-decode requests. The variant indices shift, so a call to what was method index 5 now dispatches to whatever moved into that position. The golden-byte tests in [`wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/wire_format.rs) catch this before release.

### Why not use `#[repr(u8)]` for stable enum discriminants?

Bincode ignores explicit discriminants and encodes the declaration index. Even with `#[repr(u8)]`, reordering variants changes the serialized bytes. The source comments in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs) explicitly warn against relying on discriminant values for IPC compatibility.

### How do I add a new RPC method safely?

Append it to the end of the `Agent` trait in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs), ensure no existing code breaks, and run `cargo test -p openlogi-ipc --test wire_format`. If the test fails due to changed bytes, increment `PROTOCOL_VERSION` and update the golden fixtures.

### Can I run mismatched GUI and agent versions with different protocol versions?

No—the handshake aborts immediately on version mismatch. This is intentional. Since the wire format embeds positional indices, partial compatibility is impossible; the versions must match exactly for correct decoding.