# How to Use the Herdr Unix Socket API for External Agents

> Learn how to use the Herdr Unix socket API for external agents. Connect and exchange JSON messages via the newline-delimited interface to extend Herdr's functionality.

- Repository: [Can Celik/herdr](https://github.com/ogulcancelik/herdr)
- Tags: how-to-guide
- Published: 2026-05-31

---

**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`](https://github.com/ogulcancelik/herdr/blob/main/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 location
- `HERDR_CLIENT_SOCKET_PATH` – Client-side socket location

Permission restrictions are enforced at [`src/server/socket_paths.rs`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/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 envelope
- `Method` – Enum variants including `Ping`, `WorkspaceCreate`, `AgentStart`, and `RuntimeStatus`
- `SuccessResponse` / `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`](https://github.com/ogulcancelik/herdr/blob/main/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 session
- `ApiClient::for_target()` – Connects to a specific `ConnectionTarget`
- `request()` – Sends a request and returns a `ResponseResult`
- `subscribe_value()` – For streaming event subscriptions

Example of querying runtime status:

```rust
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:

```rust
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:

1. Determine the socket path (run `herdr socket-path` or read `HERDR_CLIENT_SOCKET_PATH`)
2. Open a `SOCK_STREAM` connection to the Unix domain socket
3. Serialize a JSON object with `id`, `method`, and optional `params`
4. Append `\n` and write to the socket
5. Read one line and parse as JSON

Python implementation example:

```python
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`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/src/server/socket_paths.rs), defaulting to the user data directory with `0o600` permissions.
- The API speaks newline-delimited JSON defined in [`src/api/schema.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/api/schema.rs), using `Request` objects and `SuccessResponse`/`ErrorResponse` envelopes.
- Rust developers can use `ApiClient::local()` from [`src/api/client.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/api/client.rs) to 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`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/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.