How to Use the Herdr Unix Socket API for External Agents
Herdr exposes a newline-delimited JSON RPC interface over a Unix-domain socket that external processes can access by connecting to the client socket path and exchanging JSON messages terminated by newlines.
The ogulcancelik/herdr repository provides a terminal workspace manager that exposes control primitives through a Unix socket API. This interface allows external agents written in any language to query runtime status, manage workspaces, and spawn agents by connecting to a domain socket and speaking a simple JSON protocol.
Locating the Unix Socket Path
The server determines the socket location in src/server/socket_paths.rs (lines 14-34). When an explicit session is active, the code uses session::client_socket_path_for to resolve the path. Otherwise, it derives the client socket from the server-side socket specified in HERDR_SOCKET_PATH by inserting "client" before the ".sock" extension, falling back to the legacy environment variable or the session data directory.
You can also override paths using environment variables:
HERDR_SOCKET_PATH– Server-side socket locationHERDR_CLIENT_SOCKET_PATH– Client-side socket location
Permission restrictions are enforced at src/server/socket_paths.rs lines 80-83 through the restrict_socket_permissions function, which sets the socket permissions to 0o600 (owner-only read/write). Ensure your external agent runs as the same user that launched Herdr.
Understanding the JSON-RPC Protocol
The wire format consists of newline-delimited JSON objects defined in src/api/schema.rs. Every request must include an id and a method field, while responses return either a SuccessResponse or ErrorResponse.
Key schema types:
Request { id, method }– The inbound RPC envelopeMethod– Enum variants includingPing,WorkspaceCreate,AgentStart, andRuntimeStatusSuccessResponse/ErrorResponse– Server reply structures
Each message terminates with a newline character (\n), and clients should read exactly one line per response.
Connecting from Rust
Herdr ships with a first-party client in src/api/client.rs that handles socket resolution, serialization, and deserialization.
The ApiClient struct provides:
ApiClient::local()– Connects to the default client socket for the active sessionApiClient::for_target()– Connects to a specificConnectionTargetrequest()– Sends a request and returns aResponseResultsubscribe_value()– For streaming event subscriptions
Example of querying runtime status:
use herdr::api::client::ApiClient;
use herdr::api::schema::{Method, Request};
let client = ApiClient::local();
let status_req = Request {
id: "status".into(),
method: Method::RuntimeStatus,
};
let status = client.request(status_req).expect("cannot query status");
println!("Herdr version: {}", status.version.unwrap_or_default());
Starting an external agent programmatically:
use herdr::api::client::ApiClient;
use herdr::api::schema::{Method, Request, AgentStartParams};
let client = ApiClient::local();
let start = Request {
id: "agent-1".into(),
method: Method::AgentStart(AgentStartParams {
name: "my-agent".into(),
cwd: None,
workspace_id: None,
tab_id: None,
split: None,
focus: true,
argv: vec!["/usr/bin/bash".into()],
}),
};
let resp = client.request(start).expect("failed to start agent");
println!("Agent started with ID {}", resp.result["agent_id"]);
Implementing Clients in Other Languages
Because the protocol is language-agnostic, any runtime capable of opening Unix-domain sockets can control Herdr. The procedure mirrors the Rust client: resolve the socket path, write a JSON request ending with newline, and read the response line.
General steps:
- Determine the socket path (run
herdr socket-pathor readHERDR_CLIENT_SOCKET_PATH) - Open a
SOCK_STREAMconnection to the Unix domain socket - Serialize a JSON object with
id,method, and optionalparams - Append
\nand write to the socket - Read one line and parse as JSON
Python implementation example:
import socket
import json
import os
# Determine socket path
sock_path = os.environ.get(
"HERDR_CLIENT_SOCKET_PATH",
"/home/user/.local/share/herdr/sessions/default/herdr-client.sock"
)
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(sock_path)
# Send Ping request
req = {"id": "ping-1", "method": "Ping", "params": {}}
s.sendall((json.dumps(req) + "\n").encode())
# Read response
resp_line = s.makefile().readline()
resp = json.loads(resp_line)
print(f"Response: {resp}")
The request format maps directly to the Rust Request struct, and responses conform to either SuccessResponse (containing a result field) or ErrorResponse (containing an error field).
Handling Server-Side Events
For long-lived connections that receive server-pushed events, use the subscribe_value method available in src/api/client.rs. This maintains the socket connection and deserializes incoming JSON lines as they arrive, enabling real-time monitoring of workspace changes or agent status updates.
Summary
- Herdr exposes a Unix-domain socket at a path derived from
src/server/socket_paths.rs, defaulting to the user data directory with0o600permissions. - The API speaks newline-delimited JSON defined in
src/api/schema.rs, usingRequestobjects andSuccessResponse/ErrorResponseenvelopes. - Rust developers can use
ApiClient::local()fromsrc/api/client.rsto handle connection management and serialization automatically. - External agents in any language can implement the protocol by opening the socket, writing JSON terminated with
\n, and reading line-oriented responses.
Frequently Asked Questions
Where does Herdr store the Unix socket file?
According to src/server/socket_paths.rs, the socket resides in the user's data directory (typically ~/.local/share/herdr/sessions/default/). The exact path is computed by inserting "client" before the ".sock" extension of the server socket, or by evaluating HERDR_CLIENT_SOCKET_PATH if set.
Can I use the Herdr API from Python or Node.js?
Yes. The Herdr Unix socket API is language-agnostic. Any runtime that supports Unix-domain sockets and JSON can connect to the socket path, write newline-terminated JSON requests, and parse line-oriented responses. The src/api/schema.rs file defines the exact field names required in request and response objects.
Why do I get permission denied when connecting to the socket?
The server calls restrict_socket_permissions in src/server/socket_paths.rs (lines 80-83) to set permissions to 0o600, restricting access to the owner only. Ensure your external agent runs under the same user account that started the Herdr daemon.
How do I discover available API methods?
Inspect the Method enum in src/api/schema.rs to see supported RPC calls such as Ping, WorkspaceCreate, AgentStart, and RuntimeStatus. Each variant corresponds to a specific action the server can perform, with parameters defined in the associated parameter structs.
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 →