The rad CLI talks to a running radicle-node through a line-delimited
JSON protocol carried over a Unix domain socket. This document describes
the protocol at a high level: how the transport is set up, what the wire
format looks like, and how requests are dispatched on each side.
Transport
- The node binds a
UnixListener(typically at~/.radicle/node/control.sock) and accepts connections incrates/radicle-node/src/control.rs(listen). - Each
radinvocation that needs the node opens a freshUnixStreamviaNode::callincrates/radicle/src/node.rs. One connection carries exactly one command request; the response may be one or many lines depending on the command. - On the node, each accepted connection is handled on its own thread
(
control.rs, insidelisten), so commands are processed concurrently and a long-running command (e.g.subscribe) does not block others.
Wire format
Request
One JSON object terminated by \n. The schema is the Command enum in
crates/radicle/src/node/command.rs, serialized internally-tagged on
the command field with camelCase names. Example:
{"command":"announceRefsFor","rid":"rad:…","namespaces":["z6M…"]}Response
One or more lines, each a JSON value. The shape is CommandResult<T>
(command.rs), an untagged enum that is either:
- the success payload
Tserialized directly, or {"error":"<reason>"}on failure.
For void successes the payload is {}; for state-changing operations
(seed, unseed, follow, etc.) it is {"updated":true|false}. See
the Success type in command.rs.
Most commands return exactly one line. Two notable exceptions:
SubscribestreamsEventlines until the socket is closed (control.rs, theCommand::Subscribearm usesMAX_TIMEOUT).Fetchblocks until the fetch finishes and then writes a single result line back.
Dispatch on the node
control::command reads one line, deserializes it as Command, and
matches on the variant. Each arm calls a method on a Handle
(crates/radicle/src/node.rs, the Handle trait), which is the
in-process API to the running node — the control socket is essentially
a JSON facade in front of that trait. On error, the worker writes
CommandResult::error(e) and shuts the stream down.
Client side
Node::call returns a LineIter<T> (node.rs) that lazily reads
lines, parses each as CommandResult<T>, and yields Result<T, Error>.
A per-line read timeout is enforced via set_read_timeout, so streaming
commands like subscribe are simply iteration with a long timeout.
The Handle implementation for Node (node.rs) wraps
call(...).next() for the single-response commands, giving callers a
typed API that hides the JSON entirely.
Summary
- CLI opens the Unix socket.
- CLI writes one line of JSON describing a
Command. - CLI reads one or more lines of JSON, each a
CommandResultenvelope carrying either a typed payload or anerrorstring. - CLI closes the connection (or keeps reading, for
subscribe).