# libgrite-ipc
[](../LICENSE)
[](https://crates.io/crates/libgrite-ipc)
[](https://docs.rs/libgrite-ipc)
[](https://github.com/neul-labs/grite/actions)
**Zero-copy inter-process communication over Unix domain sockets for high-performance local RPC.**
`libgrite-ipc` defines the wire protocol between the `grite` CLI and the `grite-daemon` background process. It uses Unix domain sockets for low-latency local communication and `rkyv` for zero-copy serialization, enabling the CLI to delegate expensive operations to a warm daemon without the overhead of traditional RPC.
If you are building a Rust application that needs **fast local IPC** between a CLI and a background service, this crate provides a battle-tested protocol and implementation.
---
## What This Crate Provides
### Zero-Copy Serialization with rkyv
Traditional serialization (JSON, Protocol Buffers, MessagePack) requires parsing — converting bytes into heap-allocated structures. `rkyv` takes a different approach:
- **Zero-copy deserialization** — The serialized bytes are laid out in memory exactly as the Rust structs would be. Deserialization is a typecast, not a parse.
- **No heap allocation** — Archived types contain no pointers. They can be used directly from a memory-mapped file or socket buffer.
- **Deterministic size** — Archived types have a fixed size, making IPC message framing trivial.
This means a CLI request that takes microseconds to serialize with JSON takes nanoseconds with `rkyv`. When your agent is issuing hundreds of queries per session, this adds up to real time savings.
### Unix Domain Socket Transport
Unix domain sockets provide several advantages over TCP for local communication:
- **Lower latency** — No network stack overhead. Messages pass through the kernel directly.
- **Higher throughput** — No packetization, no congestion control, no retransmission.
- **File-system visibility** — Socket paths are visible in the file system, making discovery and debugging straightforward.
- **Permission control** — Standard Unix file permissions apply to sockets.
### Typed Request/Response Protocol
The IPC protocol is fully typed with `IpcCommand` and `IpcResponse` enums covering every CLI operation:
| `IssueList` | `IssueListResult` | Query issues with filters |
| `IssueCreate` | `IssueCreateResult` | Create a new issue |
| `IssueShow` | `IssueShowResult` | Get issue details |
| `IssueUpdate` | `IssueUpdateResult` | Update issue properties |
| `IssueClose` / `IssueReopen` | `IssueStateResult` | Change issue status |
| `IssueComment` | `CommentResult` | Add a comment |
| `SyncPull` / `SyncPush` | `SyncResult` | Synchronize with remote |
| `LockAcquire` / `LockRelease` | `LockResult` | Distributed lock operations |
| `Doctor` | `DoctorResult` | Health check |
### Daemon Discovery
The `DaemonLock` type manages daemon lifecycle discovery:
- **Lock file** — The daemon writes a lock file containing its PID and socket path.
- **Stale detection** — If the daemon crashes, the lock file is stale. The next CLI invocation detects this and starts a new daemon.
- **Socket resolution** — The CLI discovers the correct socket path from the lock file, supporting multiple repositories and actors.
---
## Key Types
### IPC Client
```rust
use libgrite_ipc::IpcClient;
// Connect to the daemon via Unix domain socket
let client = IpcClient::connect("/tmp/grite-daemon.sock")?;
// Send a command and receive a response
let response = client.send(IpcCommand::IssueList { filter: IssueFilter::default() })?;
```
### IPC Server
```rust
use libgrite_ipc::IpcServer;
// Bind a server socket
let server = IpcServer::bind("/tmp/grite-daemon.sock")?;
// Accept connections and handle commands
for request in server.incoming() {
let command = request.command()?;
let response = handle_command(command);
request.respond(response)?;
}
```
### Daemon Lock
```rust
use libgrite_ipc::DaemonLock;
// Check if a daemon is running and get its socket path
if let Some(info) = DaemonLock::read(".git/grite/actors/default/daemon.lock")? {
println!("Daemon running at PID {} on socket {}", info.pid, info.socket_path);
} else {
println!("No daemon running");
}
```
---
## Quick Example
```rust
use libgrite_ipc::{IpcClient, IpcCommand, IpcResponse};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to a running daemon
let client = IpcClient::connect("/tmp/grite-daemon.sock")?;
// Send a list issues command
let command = IpcCommand::IssueList {
filter: Default::default(),
};
let response = client.send(command)?;
match response {
IpcResponse::IssueListResult { issues, total } => {
println!("Found {} issues (showing {})", total, issues.len());
}
IpcResponse::Error { message } => {
eprintln!("Error: {}", message);
}
_ => {
eprintln!("Unexpected response type");
}
}
Ok(())
}
```
---
## Performance
| Serialization overhead | ~0ns | Zero-copy with rkyv |
| IPC round-trip | ~50µs | Unix domain socket on modern hardware |
| Throughput | 10,000+ req/s | Single client, warm connection |
| Message size | Compact | rkyv binary is typically 50-80% smaller than JSON |
Compare with JSON-over-HTTP localhost:
| JSON + HTTP | ~50µs | ~200µs | ~250µs |
| rkyv + Unix socket | ~0ns | ~50µs | ~50µs |
| **Speedup** | **Infinite** | **4x** | **5x** |
---
## Protocol Design
### Message Framing
IPC messages use a simple length-prefixed framing:
```
+----------+------------------+
+----------+------------------+
```
This is minimal overhead and trivial to implement correctly.
### Why Not gRPC / Cap'n Proto / FlatBuffers?
We evaluated several IPC frameworks and chose a custom rkyv-based protocol:
- **gRPC** — Requires HTTP/2, protobuf codegen, and a runtime. Massive overkill for local IPC.
- **Cap'n Proto** — Excellent zero-copy design, but the Rust ecosystem is smaller and the API more complex than rkyv.
- **FlatBuffers** — Similar to Cap'n Proto. Good for cross-language, but we only need Rust-to-Rust.
- **rkyv** — Native Rust, zero-copy, simple API, excellent performance. The clear winner for our use case.
### Why Not TCP?
Unix domain sockets are strictly better than TCP for local communication:
- No network stack = lower latency
- No port conflicts = simpler deployment
- File-system visibility = easier debugging
- Standard permissions = better security
The only downside is that Unix domain sockets are not portable to Windows. Grite currently targets Unix platforms only (Linux, macOS).
---
## Use Cases Beyond Grite
`libgrite-ipc` is designed for the Grite CLI/daemon pair but its primitives are reusable:
- **CLI + daemon pairs** — Any application that wants a fast CLI frontend with a warm background service.
- **Editor plugins** — Language servers and editor extensions that need to query a local index.
- **Multi-process Rust apps** — Worker pools, job queues, or sandboxed plugins that communicate over IPC.
- **Testing** — In-process IPC servers for fast integration tests.
---
## See Also
- [Grite Repository](https://github.com/neul-labs/grite) — Main project and documentation
- [grite-daemon](../grite-daemon) — The background daemon that uses this protocol
- [grite](../grite) — The CLI frontend that speaks this protocol
- [docs.rs/libgrite-ipc](https://docs.rs/libgrite-ipc) — Full Rust API documentation
---
## License
MIT License — see [LICENSE](../LICENSE) for details.