How OpenLogi Implements Inter-Process Communication (IPC) with tarpc and Local Sockets

OpenLogi implements inter-process communication through a typed RPC layer built with tarpc, using bincode serialization over local sockets provided by the interprocess crate, enabling version-aware, crash-isolated interactions between the background agent and GUI/CLI clients.

OpenLogi coordinates its desktop GUI, overlay helpers, and command-line tools with a persistent background agent using a robust inter-process communication (IPC) architecture. According to the AprilNEA/OpenLogi source code, all cross-process messages flow through the openlogi-ipc crate, which defines a type-safe contract generated from a single #[tarpc::service] trait named Agent.

Typed RPC Foundation with tarpc

The IPC layer relies on tarpc to generate client and server stubs from the Agent trait defined in crates/openlogi-ipc/src/ipc.rs. This approach eliminates manual message parsing and ensures compile-time type safety across process boundaries.

Wire protocol details:

  • Serialization – All RPC calls use bincode for compact, fast binary encoding.
  • Transport – The interprocess crate provides local-socket abstractions (Unix domain sockets on macOS/Linux, named pipes on Windows). The transport initialization logic resides in crates/openlogi-ipc/src/transport.rs and is reused by both endpoints.
  • Server entry – The agent binary starts the IPC server in openlogi-agent/src/server.rs, binding the transport endpoint and running the generated AgentServer event loop.

Clients obtain connections through AgentClient::connect implemented in crates/openlogi-ipc/src/client.rs, creating a strongly-typed handle to the agent API.

Protocol Versioning and Compatibility

OpenLogi enforces strict versioning to prevent client-server mismatches. The first method in the Agent trait, protocol_version, returns a constant PROTOCOL_VERSION (currently set to 29) defined in the IPC crate.

Because tarpc encodes the order of trait methods as the variant index in the generated protocol, new RPC methods are always appended to the trait definition to preserve backward compatibility. The complete method list spans lines 38-64 of crates/openlogi-ipc/src/ipc.rs. Historic methods like next_pairing and next_action_ring (lines 85-96) remain in the trait even after being superseded by versioned state fields, ensuring the wire format remains stable for older clients.

State Observation Pattern

Rather than using pub-sub channels or replay buffers, the GUI receives live state updates through a level-triggered observe method (lines 50-55 of crates/openlogi-ipc/src/ipc.rs). This design implements a generation-based long-polling mechanism:

  1. The client passes the last seen generation number (a monotonic u64).
  2. The agent blocks internally until the observable state changes or a hold timeout expires.
  3. The agent returns a complete Observation struct containing the full AgentSnapshot and the new generation.

This pattern eliminates the complexity of managing subscription handles or ring buffers. The state structs—including AgentSnapshot, Observation, and RingObservation—are defined in lines 26-55 of the IPC contract.

Client Identification and Connection Management

The agent distinguishes between connection types using the declare_client RPC (lines 58-64). GUI, overlay, and CLI clients each call this method immediately after connecting to identify themselves, allowing the agent to arm specific features or remain dormant accordingly.

The connection flow is type-safe from end to end: AgentClient::connect returns a future that resolves to a client handle capable of calling any method defined in the Agent trait, with all parameters and return values validated at compile time.

Implementation Examples

To obtain the protocol version from the GUI process:

// In the GUI (openlogi-desktop)
let client = openlogi_ipc::client::AgentClient::connect().await?;
let version = client.protocol_version().await?;
assert_eq!(version, openlogi_ipc::PROTOCOL_VERSION);

To observe agent state changes using the generation-based long-poll:

let mut gen = 0u64;
loop {
    let obs = client.observe(gen).await?;
    // `obs` contains a complete `AgentSnapshot` – handle UI updates here
    gen = obs.generation;  // Next request waits for a state change
}

To start the IPC server in the agent binary:

// In openlogi-agent/src/server.rs (simplified)
use openlogi_ipc::transport::LocalSocket;
let listener = LocalSocket::bind("/tmp/openlogi.ipc")?;
let server = openlogi_ipc::AgentServer::new(AgentImpl::default());
tarpc::server::new(server).incoming(listener).await?;

Summary

  • Transport layer – Uses interprocess local sockets (Unix domain sockets or named pipes) configured in crates/openlogi-ipc/src/transport.rs.
  • Serialization – Bincode provides compact binary encoding for all RPC messages.
  • Version safety – Protocol version 29 is enforced via the protocol_version method, with new methods appended to the trait to maintain compatibility.
  • State sync – The observe method implements level-triggered, generation-based long-polling defined at lines 50-55 of crates/openlogi-ipc/src/ipc.rs.
  • Type safety – The tarpc-generated AgentClient and AgentServer ensure compile-time validation of all inter-process calls.

Frequently Asked Questions

How does OpenLogi handle IPC between the agent and GUI?

OpenLogi uses a tarpc-based RPC layer where the agent runs a server from openlogi-agent/src/server.rs and the GUI obtains a typed client via AgentClient::connect in crates/openlogi-ipc/src/client.rs. All communication occurs over local sockets provided by the interprocess crate, ensuring low-latency, platform-native IPC.

What serialization format does OpenLogi use for RPC messages?

OpenLogi serializes all RPC calls using bincode, a compact binary format. This is configured as the codec for the tarpc transport, providing efficient encoding for structs like AgentSnapshot and Observation defined in crates/openlogi-ipc/src/ipc.rs.

How does the GUI receive real-time state updates without polling?

The GUI uses the observe method (lines 50-55), which accepts a generation number and blocks until the agent state changes. This level-triggered approach returns the full AgentSnapshot in a single response, eliminating the need for separate subscription management or replay buffers.

Why does the order of methods in the IPC trait matter?

Because tarpc encodes trait methods as ordered variants in the wire protocol, changing the order would break binary compatibility. OpenLogi maintains protocol stability by appending new methods to the end of the Agent trait defined in crates/openlogi-ipc/src/ipc.rs and preserving obsolete methods to support older clients.

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 →