Wire Format Used for IPC in OpenLogi: Bincode over Tarpc Protocol

OpenLogi employs a tarpc-based RPC protocol with bincode serialization for inter-process communication, using length-prefixed framing over Unix domain sockets and enforcing compatibility through a hardcoded PROTOCOL_VERSION constant.

The wire format used for IPC in OpenLogi governs every byte exchanged between the GUI frontend and the background agent process. As implemented in the AprilNEA/OpenLogi repository, this protocol combines tarpc’s service contract semantics with bincode’s deterministic binary encoding to produce a compact, version-stable communication layer.

Protocol Architecture and Serialization

Tarpc Service Contract

At the heart of the system lies the tarpc service definition in [crates/openlogi-ipc/src/ipc.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/ipc.rs). This file declares the Agent trait, which defines the RPC methods available to the GUI, such as protocol_version() and observe(). The trait serves as the schema for request and response types, ensuring type safety across process boundaries.

Bincode Encoding Rules

All structures crossing the wire implement Serialize and Deserialize from the bincode crate. The implementation uses fixed-integer encoding options to guarantee deterministic output. Critically, bincode encodes enum variants by their index position, not by name. This design choice mandates that enums remain append-only; new variants can only be added to the end of existing definitions to preserve backward compatibility with older binaries.

Versioning and Backward Compatibility

The protocol guards against mismatched binaries through the PROTOCOL_VERSION constant, defined as 29 in the source. When the GUI initiates a connection, it queries the agent’s protocol_version() method and aborts the session immediately upon detecting any discrepancy.

use openlogi_ipc::PROTOCOL_VERSION;

async fn verify_agent(client: openlogi_ipc::AgentClient) -> Result<(), String> {
    let reported = client.protocol_version().await.map_err(|e| e.to_string())?;
    if reported != PROTOCOL_VERSION {
        return Err(format!(
            "Incompatible IPC protocol: GUI expects {}, agent reports {}",
            PROTOCOL_VERSION, reported
        ));
    }
    Ok(())
}

This strict version handshake prevents serialization errors that would otherwise arise from divergent type definitions between the GUI and agent.

Transport Layer Implementation

While bincode handles payload encoding, the transport layer manages message boundaries. The [crates/openlogi-ipc/src/transport.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/transport.rs) module implements length-prefixed framing over Unix domain sockets. Each serialized tarpc message is prefixed with its byte length, allowing the receiver to unambiguously parse complete frames from the byte stream. The tarpc-generated client code in [client.rs](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/client.rs) consumes these frames, deserializing the bincode payloads into concrete Rust types.

Key Implementation Files

The following source files collectively define and enforce the wire format:

Practical Serialization Examples

Serializing a Request

The following pattern demonstrates how the GUI serializes an AgentRequest using the same bincode configuration as the underlying library:

use openlogi_ipc::AgentRequest;
use bincode::Options;

fn encode_request(req: &AgentRequest) -> Vec<u8> {
    bincode::DefaultOptions::new()
        .with_fixint_encoding()
        .serialize(req)
        .expect("bincode serialization")
}

Deserializing a Response

Upon receiving bytes from the socket, the client deserializes them into the expected response type:

use openlogi_ipc::AgentResponse;
use bincode::Options;

fn decode_response(bytes: &[u8]) -> Result<AgentResponse, bincode::Error> {
    bincode::DefaultOptions::new()
        .with_fixint_encoding()
        .deserialize(bytes)
}

Handling Streaming Responses

For long-running operations like observe(), the tarpc protocol models the stream as an open request that yields multiple AgentResponse payloads over time. Each payload follows the same bincode encoding rules and length-prefix framing as standard RPC calls.

Summary

  • Protocol Stack: Tarpc defines the service contract; bincode provides the binary encoding.
  • Version Safety: The PROTOCOL_VERSION constant (currently 29) prevents communication between incompatible GUI and agent versions.
  • Schema Evolution: Enums use append-only variant ordering to maintain backward compatibility, as bincode discriminates by index.
  • Transport: Length-prefixed framing over Unix domain sockets delimits bincode payloads.
  • Verification: Golden-byte tests in tests/wire_format.rs lock the wire format to prevent accidental changes.

Frequently Asked Questions

How does OpenLogi prevent version mismatches between GUI and agent?

The GUI queries the agent’s protocol_version() method immediately upon connection. If the returned u32 does not match the PROTOCOL_VERSION constant compiled into the GUI, the application aborts with an explicit error message before any substantive RPC traffic occurs.

Why was bincode chosen over JSON or Protocol Buffers?

Bincode provides deterministic, compact binary serialization with zero-copy deserialization capabilities and minimal overhead. Unlike JSON, it produces consistent byte sequences for identical data structures, enabling deterministic golden-byte tests. Unlike Protocol Buffers, it does not require separate .proto schema files, instead leveraging native Rust derive macros for seamless integration with the tarpc framework.

What happens if a new enum variant is added to the protocol?

New variants must be appended to the end of the enum definition. Because bincode encodes variants by their positional index rather than a tag value, inserting variants in the middle or changing the order would shift all subsequent indices, breaking wire compatibility with existing binaries. The codebase enforces this constraint through documentation and code review.

What transport mechanism carries the serialized payloads?

The protocol uses Unix domain sockets with a custom length-prefix framing scheme implemented in transport.rs. This framing layer prepends each bincode-serialized message with its length as a fixed-width integer, allowing the receiver to parse complete messages from the byte stream without ambiguity.

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 →