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:
- The client opens a connection and sends its
PROTOCOL_VERSION. - The agent compares the received value against its local constant using strict equality.
- If the values differ, the connection is rejected immediately with a
VersionMismatcherror. - 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_versionat index 0. - Strict versioning: The
PROTOCOL_VERSIONconstant 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.rslock the wire format, catching accidental encoding changes. - Consistent serialization: All IPC uses
tokio-serdebincode 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →