How tarpc RPC Protocol Works in OpenLogi: Wire-Format Rules and Versioning Explained
OpenLogi uses the tarpc RPC framework with bincode serialization over length-delimited local sockets, enforcing strict append-only method ordering and a hardcoded PROTOCOL_VERSION constant to guarantee wire-format compatibility between GUI clients and the background agent.
The tarpc RPC protocol in OpenLogi powers all communication between the graphical interface and the system agent. This architecture is implemented in the openlogi-ipc crate, where a single #[tarpc::service] trait defines the complete contract. Understanding this protocol requires examining how method ordering, binary encoding, and versioning interact to produce a stable, evolvable interface.
Service Definition and Method Ordering Rules
The RPC contract centers on the Agent trait declared in [ipc.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/ipc.rs). This trait uses tarpc's procedural macro to generate client stubs, server handlers, and the wire-format enums:
#[tarpc::service]
pub trait Agent {
async fn protocol_version() -> u32;
async fn status() -> AgentStatus;
async fn inventory() -> Vec<DeviceInventory>;
// … additional methods …
async fn observe(since: u64) -> Observation;
}
Method order is immutable in this design. Tarpc generates a Rust enum where each method becomes a variant, and bincode encodes the variant index (discriminant) directly into the wire format. The first method, protocol_version, occupies index 0. Every new RPC must be appended at the end of the trait. Inserting a method in the middle would shift subsequent discriminants, breaking compatibility with existing clients. This constraint is documented in the source comments at lines 41–45 of ipc.rs.
Wire-Format Encoding and Framing
The tarpc RPC protocol in OpenLogi relies on bincode for binary serialization with specific framing rules:
| Aspect | Implementation |
|---|---|
| Serialization | bincode — compact, binary, index-based enum encoding |
| Framing | Length-delimited via tarpc::serde_transport::Transport |
| Transport | OS-local sockets (Unix domain sockets on Unix, named pipes on Windows) |
| Message structure | [length prefix][variant index][payload fields] |
The transport layer in [transport.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/transport.rs) wraps the raw socket:
pub fn wrap<Item, SinkItem>(stream: Stream) -> Transport<Stream, Item, SinkItem, Bincode<Item, SinkItem>>
where
Item: DeserializeOwned,
SinkItem: Serialize,
{
Transport::from((stream, Bincode::default()))
}
This wrap function constructs a Transport that automatically handles length-prefix framing and bincode serialization for both directions. The interprocess crate provides the cross-platform local socket implementation.
Enum Encoding Constraints
All types crossing the RPC boundary—AgentRequest, AgentResponse, InventoryHealth, PairingPhase, and others—follow append-only enum semantics. Bincode serializes enums as a variant index followed by the variant's fields. Consequently:
- Never reorder existing variants
- Never remove existing variants
- Only add new variants at the end
Violating these rules changes the binary representation and breaks wire compatibility.
Protocol Versioning and Compatibility
Version management in OpenLogi's tarpc implementation uses a hardcoded constant with golden-file testing:
// In ipc.rs, line 64
pub const PROTOCOL_VERSION: u32 = 29;
The protocol_version RPC (variant index 0) must return this value. On connection, the GUI client queries this endpoint; a mismatch aborts the handshake immediately.
Version Bump Policy
A protocol version increment is mandatory when:
- Adding, removing, or reordering methods in the
Agenttrait - Changing the shape of any bincode-serialized type (adding fields, new enum variants)
- Any modification that alters the binary representation of existing requests or responses
The enforcement mechanism resides in [wire_format.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/tests/wire_format.rs). These golden-wire-format tests assert that each request and response serializes to an exact, stored hex string:
assert_wire(&AgentRequest::ProtocolVersion {}, "00");
Changing this test output without bumping PROTOCOL_VERSION constitutes a protocol violation. The test at line 104 explicitly validates version negotiation.
Concrete Wire Format Example
A request to protocol_version produces the following bytes on the wire:
01 // length prefix: 1 byte payload follows
00 // variant index 0: ProtocolVersion
A DeclareClient request (variant 0x19 / 25 decimal), which identifies the client type during handshake, encodes accordingly with its discriminant fixed in the wire format.
Complete Client Connection Flow
use openlogi_ipc::{client, transport};
use tarpc::context;
// Establish local socket connection
let stream = transport::connect().await?;
let transport = transport::wrap(stream);
// Instantiate tarpc client with current context
let client = client::new(transport, context::current());
// Execute RPC: returns u32 = 29
let version = client.protocol_version().await?;
assert_eq!(version, openlogi_ipc::PROTOCOL_VERSION);
The DeclareClient request (index 0x19) precedes other operations, informing the agent whether the peer is a GUI, CLI, or Overlay client.
Key Source Files
| File | Role in tarpc RPC Protocol |
|---|---|
[ipc.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/ipc.rs) |
Agent trait definition, PROTOCOL_VERSION constant, all RPC data structures |
[transport.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/transport.rs) |
Local socket establishment, Transport wrapping for length-delimited + bincode framing |
[wire_format.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/tests/wire_format.rs) |
Golden-file tests locking binary encoding; version bump enforcement |
[client.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/client.rs) |
Client-side convenience wrapper around generated AgentClient |
server.rs in openlogi-agent |
Server-side Agent implementation binding |
Summary
- tarpc generates RPC enums from the
Agenttrait, with bincode encoding variant indices directly to the wire - Method order is append-only; new RPCs extend the trait end, preserving discriminant stability
- Length-delimited framing via
tarpc::serde_transport::Transportoverinterprocesslocal sockets PROTOCOL_VERSION(currently 29) guards compatibility; mismatches abort connection- Golden-wire-format tests in
wire_format.rsenforce binary stability, requiring version bumps on any format change
Frequently Asked Questions
What happens if I reorder methods in the Agent trait?
Reordering methods shifts bincode variant indices, corrupting the wire format. Existing clients would send requests that the server misinterprets, or vice versa. The golden tests in wire_format.rs would fail, signaling a required PROTOCOL_VERSION increment.
How does OpenLogi detect incompatible protocol versions?
The GUI calls Agent::protocol_version() immediately after connection and compares the returned u32 against its compiled PROTOCOL_VERSION constant. A mismatch triggers an immediate disconnect, as verified in wire_format.rs line 104.
Why choose bincode over JSON or Protocol Buffers for tarpc?
Bincode provides zero-overhead, compact binary encoding with deterministic variant indices—critical for the enum-based method dispatch that tarpc generates. The tradeoff is strict schema evolution rules (append-only) that OpenLogi enforces through version constants and golden tests.
Can the RPC protocol work over network sockets instead of local sockets?
The current implementation in transport.rs uses interprocess for local sockets, but the tarpc::serde_transport::Transport abstraction supports any AsyncRead + AsyncWrite stream. Replacing the connection establishment would enable network transport while preserving the identical wire format and versioning semantics.
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 →