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

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 (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.

// 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—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.

// 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 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. These files capture the exact byte stream expected for each protocol version. Running the test suite validates that the current implementation produces identical bytes:

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:

// 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:

#[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:


# 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 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →