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

> Discover how OpenLogi uses bincode serialization over the tarpc protocol for efficient IPC. Learn about length-prefixed framing and protocol versioning.

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

---

**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/main/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.

```rust
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/main/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/main/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:

- **[[`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/ipc.rs)** — Contains the tarpc service trait, all request/response types, and the `PROTOCOL_VERSION` constant.
- **[[`crates/openlogi-ipc/src/client.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/client.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/client.rs)** — Implements the client-side connection logic and version validation.
- **[[`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/transport.rs)** — Provides the length-prefixed socket framing mechanism.
- **[[`crates/openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/tests/wire_format.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/tests/wire_format.rs)** — Houses golden-byte tests that verify the exact binary representation of each serializable type remains stable across builds.
- **[[`crates/openlogi-ipc/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/lib.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/lib.rs)** — Re-exports the public API consumed by the GUI, agent, and overlay binaries.

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

```rust
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:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.