# How Serde Enums Are Handled Across the IPC Boundary in OpenLogi

> Learn how OpenLogi handles Serde enums across IPC using bincode variant-index encoding, protocol versioning, and strict append-only rules for guaranteed backward compatibility.

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

---

**OpenLogi serializes Serde enums across its IPC boundary using bincode's variant-index encoding with strict append-only rules, protocol versioning, and golden tests to guarantee backward compatibility.**

OpenLogi's GUI and background agent communicate over a local-socket RPC layer built with **tarpc** and **bincode`. Every Serde enum that crosses this IPC boundary must follow a carefully enforced wire-format contract to prevent silent data corruption between mismatched binaries. This article examines the encoding strategy, versioning mechanism, and testing infrastructure that makes this safe.

## Bincode Encoding of Serde Enums

OpenLogi uses **bincode** to serialize all RPC payloads. For enums, bincode writes the **variant index**—the zero-based position of each variant in the source code order. Critically, bincode does **not** use explicit discriminants like `#[repr(u8)]`. This means the physical layout of your source code directly determines the wire format.

In [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs), enums like `MonitorEvent`, `InventoryHealth`, and `PairingPhase` are defined with standard Serde derives:

```rust
// crates/openlogi-ipc/src/ipc.rs
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MonitorEvent {
    Button { button: String, pressed: bool },
    Scroll { delta_x: f32, delta_y: f32 },
    CaptureInterrupted,
}

```

The `Button` variant encodes as index `0`, `Scroll` as `1`, and `CaptureInterrupted` as `2`. Reordering these variants would instantly break compatibility with existing agents.

## The Append-Only Rule for Enum Variants

To maintain backward compatibility, OpenLogi enforces an **append-only** policy for both enum variants and RPC methods:

- **New variants** must be added **only at the end** of the enum definition
- **Existing variants** must never be removed or reordered
- **Method signatures** in the `Agent` trait follow the same constraint

This rule exists because tarpc generates a request enum from the `Agent` trait. The method order in the trait becomes part of the wire format—changing it shifts every subsequent method's index.

## Protocol Version Checking

Every incompatible change requires bumping `PROTOCOL_VERSION` in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs):

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

```

The GUI checks this constant on connection establishment and rejects connections from agents reporting a different version. This prevents silent misinterpretation of data when enum indices no longer align.

## Golden Tests for Wire Format Stability

OpenLogi protects the serialization contract with **golden tests** located in [`crates/openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/tests/wire_format.rs). These tests serialize known values and assert exact byte sequences. Adding any variant forces regeneration of the golden file, creating an auditable record of every wire-format change.

Run the golden tests with:

```bash
cargo test -p openlogi-ipc --test wire_format

```

When a new variant changes the encoding, the test fails with a diff showing the new byte sequence. Developers must explicitly accept and commit this change, ensuring no accidental format drift.

## Correct Bincode Configuration

The wire format uses `tokio-serde`'s `Bincode::default()`, which applies `bincode::DefaultOptions` with **varint encoding** and **little-endian** byte order. Direct calls to `bincode::serialize` use different fixed options and produce incompatible bytes. All IPC serialization must route through the tokio-serde layer.

## Practical: Adding a New MonitorEvent Variant

Here's the complete workflow for extending an IPC enum:

**1. Append the variant without reordering:**

```rust
// crates/openlogi-ipc/src/ipc.rs
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MonitorEvent {
    Button { button: String, pressed: bool },
    Scroll { delta_x: f32, delta_y: f32 },
    CaptureInterrupted,
    // NEW: Battery level change reported by the driver.
    BatteryLevel { percent: u8 },
}

```

**2. Increment the protocol version:**

```rust
pub const PROTOCOL_VERSION: u32 = 30;  // ← bumped from 29

```

**3. Run and update golden tests:**

```bash
cargo test -p openlogi-ipc --test wire_format

# Review diff, accept new bytes, commit updated file

```

**4. Handle the new variant in client code:**

```rust
use openlogi_ipc::client::AgentClient;
use openlogi_ipc::MonitorEvent;

let events = client.poll_event_monitor().await.unwrap();
for ev in events {
    match ev {
        MonitorEvent::Button { button, pressed } => { /* … */ }
        MonitorEvent::Scroll { delta_x, delta_y } => { /* … */ }
        MonitorEvent::CaptureInterrupted => { /* … */ }
        MonitorEvent::BatteryLevel { percent } => {
            println!("Battery at {} %", percent);
        }
    }
}

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) | Serde-enabled enums (`MonitorEvent`, `InventoryHealth`, `PairingPhase`, `PairingFailure`) and `PROTOCOL_VERSION` constant |
| [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md) | Official wire-format contract documenting append-only rules and versioning policy |
| [`crates/openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/tests/wire_format.rs) | Golden-test suite validating exact bincode byte sequences |
| [`crates/openlogi-ipc/src/client.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/client.rs) | Client-side `AgentClient` wrapper for RPC calls |

## Summary

- **Serde enums** in OpenLogi encode as bincode variant indices, not discriminants
- **Append-only modifications** prevent index shifts that would break existing binaries
- **Protocol versioning** (`PROTOCOL_VERSION`) blocks connections from incompatible agents
- **Golden tests** enforce auditable, committed records of every wire-format change
- **tokio-serde's `Bincode::default()`** must be used; raw `bincode::serialize` produces incompatible output

## Frequently Asked Questions

### What happens if I reorder enum variants in OpenLogi's IPC types?

Reordering changes every variant's bincode index, causing existing agents to deserialize values as wrong variants. The `PROTOCOL_VERSION` check may catch this, but only if you remembered to bump it. The golden tests will fail with unexpected byte sequences, alerting you to the break.

### Why doesn't OpenLogi use `#[repr(u8)]` discriminants for IPC enums?

Bincode ignores explicit discriminants and always serializes the zero-based variant index. Adding `#[repr(u8)]` would create a false sense of stability—the wire format would still change if variants were reordered.

### How do I know if my change requires a protocol version bump?

Any modification that adds, removes, or reorders enum variants or RPC trait methods requires a `PROTOCOL_VERSION` increment. The golden tests serve as the definitive signal: if they fail with a byte diff, you must bump the version and regenerate the golden file.

### Can different OpenLogi versions communicate if they only added new variants?

No—by design. Even purely additive changes increment `PROTOCOL_VERSION`, causing the older peer to reject the connection. This strict policy prevents subtle bugs where an old binary encounters an unknown variant index and either panics or misinterprets data.