# How OpenLogi Handles IPC Protocol Versioning: Append-Only Wire Format Explained

> Discover how OpenLogi handles IPC protocol versioning with an append-only wire format. Learn about tarpc, bincode, and backward compatibility for seamless GUI-to-agent communication.

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

---

**OpenLogi ensures backward compatibility across its GUI-to-agent inter-process communication by enforcing an append-only wire format using tarpc and bincode, where strict version constants and golden tests prevent accidental breaking changes.**

The OpenLogi project implements a Rust-based IPC system that connects the GUI client to a background agent process. According to the source code in `AprilNEA/OpenLogi`, this protocol relies on a carefully versioned binary format that prioritizes determinism and early mismatch detection over runtime negotiation.

## The Append-Only Wire Format Strategy

OpenLogi's IPC protocol is built on two core immutability rules that prevent accidental breaking changes: append-only service methods and append-only enums. These constraints ensure that once a method or enum variant enters the protocol, its wire representation never changes.

### Fixed Method Indices and the Handshake

The RPC interface uses **tarpc** to define service methods, but with a critical constraint: method order is permanently fixed. New methods can only be appended after existing ones, and the very first method—`protocol_version`—must always remain at index 0.

This index stability is vital because the "takeover handshake" relies on it. As documented in [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md) (lines 7-9), both peers exchange their compiled `PROTOCOL_VERSION` constants immediately upon connection. If the values do not match exactly, the handshake aborts before any business logic executes.

```rust
// crates/openlogi-ipc/src/ipc.rs (conceptual)
const PROTOCOL_VERSION: u32 = 3;

async fn handshake(peer_version: u32) -> Result<(), HandshakeError> {
    if peer_version != PROTOCOL_VERSION {
        return Err(HandshakeError::VersionMismatch {
            local: PROTOCOL_VERSION,
            remote: peer_version,
        });
    }
    Ok(())
}

```

### Immutable Enum Discriminants

All enums that cross the IPC boundary—such as `DeviceKind` or `Action` defined in [`crates/openlogi-core/src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/device.rs)—follow the same append-only rule. New variants are added exclusively at the end of the declaration list. Because **bincode** uses the declaration order as the discriminant index (not manually assigned values), reordering or inserting variants would corrupt existing serializations.

```rust
// crates/openlogi-core/src/device.rs (example pattern)
#[derive(Serialize, Deserialize)]
pub enum DeviceKind {
    Mouse,      // Index 0
    Keyboard,   // Index 1
    Touchpad,   // Index 2 (new variant appended)
}

```

## Version Negotiation and Enforcement

Version management in OpenLogi is explicit and strict. Every change to the wire format triggers a protocol version bump, and mismatches are detected immediately at connection time rather than during message processing.

### The PROTOCOL_VERSION Constant

Whenever a new RPC method is added or a new enum variant is introduced, the `PROTOCOL_VERSION` constant in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) must be incremented. This constant is compiled into both the client and agent binaries, making version detection a compile-time constant rather than a runtime discovery process (AGENTS.md, lines 14-15).

### Connection Handshake Mechanics

The handshake process follows a strict sequence:

1. The client opens a connection and sends its `PROTOCOL_VERSION`.
2. The agent compares the received value against its local constant using strict equality.
3. If the values differ, the connection is rejected immediately with a `VersionMismatch` error.
4. Only after successful verification does the agent begin dispatching RPC calls.

This strict comparison eliminates ambiguity—partial compatibility is not supported. Either both peers run the exact same protocol version, or they disconnect.

## Testing and Validation

OpenLogi enforces protocol stability through automated testing and rigid serialization constraints. These mechanisms catch accidental format drift before it reaches production.

### Golden Tests for Binary Compatibility

After every version bump, the repository maintains "golden" binary fixtures in [`crates/openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/tests/wire_format.rs). These files capture the exact byte stream expected for each protocol version. Running the test suite validates that the current implementation produces identical bytes:

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

```

If the test fails, the diff reveals the binary discrepancy. Developers must then explicitly update both the golden fixtures and the `PROTOCOL_VERSION` constant, ensuring that wire format changes are deliberate and documented (AGENTS.md, lines 15-18).

### Bincode Configuration Requirements

The protocol uses `tokio-serde`'s `Bincode::default()` configuration, which specifies varint encoding and little-endian byte order. Direct calls to `bincode::serialize` with default options are avoided because they use different serialization parameters that would break cross-version compatibility. All serialization must flow through the `tokio-serde` codec to maintain consistency (AGENTS.md, lines 19-21).

## Practical Implementation Examples

### Adding a New RPC Method

When extending the service interface, append new methods after existing ones without reordering:

```rust
// crates/openlogi-ipc/src/ipc.rs
#[tarpc::service]
pub trait OpenLogi {
    // Existing method – must stay at index 0
    async fn protocol_version() -> u32;

    // Existing method at index 1
    async fn list_devices() -> Vec<DeviceInfo>;

    // New method – always appended at the end (index 2)
    async fn set_dpi(dpi: u16) -> Result<(), SetDpiError>;
}

```

### Extending Cross-IPC Enums

Add new enum variants only at the end of the definition to preserve existing discriminant indices:

```rust
#[derive(Serialize, Deserialize)]
pub enum HidWriteError {
    Timeout,
    InvalidLength,
    // New variant appended at the end
    PermissionDenied,
}

```

### Running Post-Version-Bump Validation

After incrementing `PROTOCOL_VERSION` and modifying message structures, regenerate and verify the golden fixtures:

```bash

# Run the wire format tests to validate binary output

cargo test -p openlogi-ipc --test wire_format -- --nocapture

# If tests fail, update fixtures and verify the diff

```

## Summary

- **Append-only methods**: RPC methods never reorder; new methods append to the service trait, keeping `protocol_version` at index 0.
- **Strict versioning**: The `PROTOCOL_VERSION` constant enforces exact match compatibility between client and agent at connection time.
- **Immutable enums**: Enum discriminants derive from declaration order; new variants append to prevent index shifts.
- **Golden tests**: Binary fixtures in [`tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/tests/wire_format.rs) lock the wire format, catching accidental encoding changes.
- **Consistent serialization**: All IPC uses `tokio-serde` bincode with varint/little-endian options to ensure deterministic byte output.

## Frequently Asked Questions

### What happens if the client and agent have different protocol versions?

The connection is aborted during the initial handshake. According to the logic in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs), a strict equality check compares the client's `PROTOCOL_VERSION` against the agent's compiled constant. If they differ, the agent returns a `VersionMismatch` error and closes the connection immediately.

### Can I reorder existing RPC methods to group them logically?

No. Reordering methods changes their tarpc index, which alters the binary wire format and breaks compatibility with existing peers. The protocol mandates append-only modifications. If methods need reorganization, developers must create a new major protocol version and bump `PROTOCOL_VERSION` accordingly.

### Why does OpenLogi use bincode declaration order instead of explicit discriminants?

The protocol relies on bincode's default behavior of using declaration order as the discriminant value. This approach minimizes serialization overhead but requires strict append-only discipline for enums. Manually assigned discriminants are avoided to prevent accidental collisions and to align with the golden test fixtures that record exact byte sequences.

### How do I verify that my changes didn't accidentally modify the wire format?

Run `cargo test -p openlogi-ipc --test wire_format`. This executes the golden tests that compare current serialization output against stored binary fixtures. If the test fails, either revert your changes or increment `PROTOCOL_VERSION` and update the fixtures explicitly to document the intentional format change.