OpenLogi IPC Protocol Contract: tarpc, bincode, and Local Socket Communication
OpenLogi's IPC protocol contract uses a tarpc service serialized with bincode over local sockets, enforcing wire-format stability through an append-only versioning strategy.
The inter-process communication (IPC) between OpenLogi's background agent and its clients (GUI, CLI, and overlay helper) is governed by a strict contract defined in the openlogi-ipc crate. This contract ensures type-safe, versioned communication across all OpenLogi components using Rust-native RPC tooling.
What Is the OpenLogi IPC Protocol?
The protocol is implemented as a tarpc service—the Agent trait defined in crates/openlogi-ipc/src/ipc.rs. All messages are serialized using bincode, a compact binary serialization format that preserves Rust data structure layouts exactly. This combination provides zero-overhead RPC calls between processes on the same machine.
The contract lives in the openlogi-ipc crate, which serves as the single source of truth for both the agent (server) and all client implementations.
Transport Layer and Serialization
Local Socket Transport
Clients connect to the agent via a local Unix/AF_UNIX socket provided by the interprocess crate. The connection logic resides in crates/openlogi-ipc/src/transport.rs, which handles the low-level socket establishment and stream management.
Request/Response with Long-Polling
Because tarpc is strictly request/response, the protocol simulates "push" notifications through long-polling. Methods like observe, observe_action_ring, and next_pairing accept a generation number and block until the agent state changes or a timeout expires (OBSERVE_HOLD). This allows the agent to stream state updates without requiring a native publish/subscribe mechanism.
Wire-Format Stability and Versioning
The protocol enforces append-only evolution to maintain backward compatibility:
- Method ordering is part of the wire format. The first method (
protocol_version) must remain at index 0, and new methods can only be appended to the trait (see lines 38-45 insrc/ipc.rs). - Type variants are also append-only. Enums like
InventoryHealth,PairingPhase, andMonitorEventmust only grow new variants; existing indices are fixed by bincode's serialization. - Breaking changes require bumping
PROTOCOL_VERSION(currently 30) and updating the golden-test fixtures intests/wire_format.rs.
This design ensures older binaries detect incompatibility immediately via the protocol_version handshake, preventing subtle desynchronization bugs.
Key Elements of the Contract
The Agent Trait
The Agent trait in src/ipc.rs declares every available RPC method:
protocol_version()– Returns thePROTOCOL_VERSIONconstant for handshake validation.status()andinventory()– Query current agent state.set_dpi(),start_pairing()– Control hardware configuration.declare_client(kind)– Registers the client type (Gui,Cli, orOverlay) so the agent can adjust behavior (e.g., disabling macOS dormancy gates for overlays).
Long-Polling Observation
State synchronization relies on generation-tracked long-polling:
observe(generation)– Blocks until theAgentSnapshotchanges, returning the new generation and full state.observe_action_ring(generation)– Specifically monitors action ring invocations for the overlay helper.next_pairing(generation)– Waits for pairing state machine transitions.
Clients increment the generation counter with each response to maintain consistency.
Client Type Declaration
After version verification, clients must call declare_client with their kind:
Gui– The main desktop application.Cli– Command-line interface tools.Overlay– The helper process that displays on-screen rings.
This distinction allows the agent to optimize resource usage, such as arming the dormancy gate only for GUI clients.
Implementation Examples
Basic Client Connection and Version Check
use openlogi_ipc::client::AgentClient;
use openlogi_ipc::transport::connect_to_agent;
// Connect to the local Unix socket the agent listens on
let conn = connect_to_agent()?; // src/transport.rs
let mut client = AgentClient::new(conn); // src/client.rs
// Verify protocol compatibility
let version = client.protocol_version().await?;
assert_eq!(version, openlogi_ipc::PROTOCOL_VERSION);
// Query current state
let status = client.status().await?;
let inventory = client.inventory().await?;
Long-Polling State Updates
// Observe state changes using generation tracking
let mut generation = 0_u64;
loop {
let observation = client.observe(generation).await?;
// Generation increases only when state changes
generation = observation.generation;
println!("New snapshot: {:?}", observation.snapshot);
}
Overlay Helper Pattern
use openlogi_ipc::ClientKind;
// Declare as overlay to bypass dormancy gates
client.declare_client(ClientKind::Overlay).await?;
let mut gen = 0_u64;
loop {
let ring = client.observe_action_ring(gen).await?;
gen = ring.generation;
if let Some(inv) = ring.invocation {
println!("Display ring {} with {} slots", inv.session_id, inv.slots.len());
}
}
Summary
- OpenLogi uses tarpc over bincode for IPC, transported via local sockets using the
interprocesscrate. - The contract is defined in
crates/openlogi-ipc/src/ipc.rsas theAgenttrait. - Wire-format stability is enforced through append-only evolution of methods and types, with
PROTOCOL_VERSION(currently 30) guarding against incompatibility. - Push notifications are implemented via long-polling (
observemethods) rather than native pub/sub. - Clients must declare their type (
Gui,Cli, orOverlay) to influence agent behavior.
Frequently Asked Questions
What serialization format does OpenLogi use for IPC?
OpenLogi uses bincode for all IPC serialization. This is a compact, binary format that encodes Rust data structures directly, making it ideal for high-performance local communication. The Agent trait methods and all parameter types are serialized with bincode before transmission over the local socket.
How does the agent push updates to clients without native pub/sub?
Because tarpc only supports request/response patterns, OpenLogi implements long-polling. Clients call methods like observe or observe_action_ring with a generation number, and the agent blocks the response until state changes or a timeout occurs. The client then immediately re-polls with the new generation, creating an efficient event stream without maintaining persistent server-side subscriptions.
What happens if client and agent have incompatible protocol versions?
All clients must call protocol_version() immediately after connection. If the returned version does not match the client's expected PROTOCOL_VERSION constant (currently 30), the client should disconnect. Because the wire format is positional and append-only, version mismatches indicate potential binary incompatibility that would corrupt deserialization.
Where is the IPC protocol contract defined in the source code?
The contract is centralized in the openlogi-ipc crate. The primary definition is in crates/openlogi-ipc/src/ipc.rs, which contains the Agent trait, all shared types, and the PROTOCOL_VERSION constant. Transport logic lives in src/transport.rs, while tests/wire_format.rs contains golden tests that verify the binary representation remains stable across changes.
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 →