# Tarpc IPC Wire Format Version Compatibility Contract in OpenLogi

> Understand OpenLogi's tarpc IPC wire format version compatibility contract for binary-stable GUI and agent communication. Discover its append-only RPC and enum rules.

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

---

**OpenLogi's tarpc IPC wire format version compatibility contract mandates append-only RPC methods, append-only enum variants, and strict protocol version bumps to ensure binary-stable communication between GUI and agent processes across releases.**

OpenLogi leverages tarpc over local sockets with bincode serialization for inter-process communication (IPC) between its graphical interface and background agent. Because this wire format encodes data positionally rather than as self-describing messages, the project enforces a rigorous compatibility contract to prevent corruption when mixing different versions. Contributors to the `AprilNEA/OpenLogi` repository must adhere to these rules when modifying the `openlogi-ipc` crate to maintain backward and forward compatibility.

## Positional Encoding in the Wire Format

The tarpc IPC implementation in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) relies on **positional encoding** where the binary structure carries no field names. Tarpc encodes the *method order* as positional indices, while bincode encodes *enum variant order* using declaration indices rather than explicit discriminants.

This design choice means that changing the order of methods in a service trait, or reordering enum variants, immediately corrupts the binary protocol. The `#[repr(u8)]` attribute on enums is ignored by bincode; only the physical order of variants in the source code determines the wire representation.

## The Three Rules of the Compatibility Contract

According to [`crates/openlogi-ipc/AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/AGENTS.md), the compatibility contract consists of three linked rules that govern all changes to the IPC boundary:

### Append-Only Service Methods

The first method in any tarpc service definition, `protocol_version`, is permanently fixed as method index 0 and serves as the handshake mechanism. New RPC methods may only be appended to the end of the service definition; reordering or removing existing methods breaks wire compatibility.

As documented in [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md) (lines 7-9), this append-only policy ensures that method indices remain stable across releases. The `protocol_version` method specifically enables version detection and takeover detection, preventing debug-build agents from displacing running release agents.

### Append-Only Serde Enums

Any enum that crosses the IPC boundary must follow append-only semantics. Bincode serializes enum variants based on their *declaration index*—the order they appear in the source—regardless of any `#[repr(u8)]` discriminant values. Reordering, removing, or inserting variants before existing ones changes the binary encoding and violates the contract.

The [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md) file (lines 9-13) explicitly warns that developers must append new variants only at the end of the enum definition. This rule applies to all data structures passed across the tarpc boundary, including error types and configuration enums.

### Mandatory Protocol Version Bumping

Whenever the wire format changes intentionally—whether by adding a new RPC method, appending an enum variant, or modifying serialized data structures—the `PROTOCOL_VERSION` constant must be incremented. The current version is pinned at `30` in the source code.

The version check uses strict equality during the initial handshake; a mismatch immediately aborts the connection. The test suite enforces this through [`crates/openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/tests/wire_format.rs) (lines 101-106), which contains a golden-bytes test that fails if the protocol version constant is out of sync with the actual wire format.

## Enforcement Through Golden-Bytes Testing

The contract relies on automated testing to prevent accidental violations. The [`wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/wire_format.rs) test file contains golden-bytes assertions that serialize specific IPC messages and compare them against committed byte arrays. If a developer unintentionally alters the wire format—such as by reordering enum variants—the test fails and forces a deliberate `PROTOCOL_VERSION` bump and regeneration of the golden data.

This mechanism ensures that no silent refactoring can corrupt the binary protocol exchanged between a newer GUI and an older agent, or vice versa.

## Practical Implementation Examples

When defining a tarpc service in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs), new methods must be appended to the trait definition:

```rust
#[tarpc::service]
pub trait Agent {
    // Method 0: Fixed forever as the handshake
    async fn protocol_version(&self, _: Context) -> u32;
    
    async fn set_dpi(&self, _: Context, route: DeviceRoute, dpi: Dpi) -> Result<(), WriteError>;
    
    // New methods MUST be added at the end
    async fn new_feature(&self, _: Context, data: NewFeatureData) -> Result<(), Error>;
}

```

For enums crossing the boundary, append variants only at the end:

```rust
#[derive(Serialize, Deserialize)]
pub enum DeviceKind {
    Mouse,
    Keyboard,
    // Existing variants remain in place
    Unknown,
    // New variants appended here:
    Light,
}

```

The protocol version assertion in tests enforces the current version constant:

```rust
#[test]
fn protocol_version_is_pinned() {
    // Current wire-format version (must be bumped on any change)
    assert_eq!(PROTOCOL_VERSION, 30);
}

```

## Summary

- **Positional encoding**: The tarpc IPC wire format uses bincode without field names, making method order and enum variant order critical to binary stability.
- **Append-only methods**: New RPC methods must be appended to the end of service definitions in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs); `protocol_version` remains fixed at index 0.
- **Append-only enums**: Enum variants must only be appended at the end of the definition; `#[repr(u8)]` discriminants are not used by bincode.
- **Version pinning**: The `PROTOCOL_VERSION` constant (currently `30`) must increment on any wire-format change, enforced by [`wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/wire_format.rs) tests.
- **Handshake validation**: Strict version equality checks during connection prevent communication between incompatible GUI and agent versions.

## Frequently Asked Questions

### Why can't I reorder methods in the tarpc service definition?

Reordering changes the positional index that tarpc uses to identify RPC methods on the wire. Since the binary protocol contains no method names—only indices—reordering causes the client to invoke the wrong server method or deserialize return values incorrectly. The append-only rule in [`AGENTS.md`](https://github.com/AprilNEA/OpenLogi/blob/main/AGENTS.md) ensures method indices remain stable across all versions.

### What happens if I forget to bump PROTOCOL_VERSION when changing the wire format?

The [`wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/wire_format.rs) test suite will fail because the golden-bytes assertions detect a mismatch between the committed binary samples and the current serialization output. This failure forces developers to explicitly update the `PROTOCOL_VERSION` constant and regenerate the golden data, preventing accidental breaking changes from reaching production.

### Why does bincode use enum variant order instead of #[repr(u8)] discriminants?

Bincode serializes enums using the declaration index (the order variants appear in source code) rather than explicit numeric discriminants. The `#[repr(u8)]` attribute affects the in-memory representation but not the bincode wire format. Therefore, reordering variants changes the binary encoding even if the discriminant values remain constant, which is why the contract requires append-only enum modifications.

### How does the protocol_version handshake prevent version mismatches?

Upon connection, the client immediately calls `protocol_version` (method index 0) and compares the returned value against its local `PROTOCOL_VERSION` constant. If the values are not strictly equal, the connection aborts before any business logic executes. This ensures that a new GUI cannot inadvertently send new enum variants to an old agent that cannot deserialize them, and vice versa.