# herdr socket api herdr exposes a local unix socket API for scripts, tools, and coding agents that want to control a running herdr instance or subscribe to pane/workspace events. this is the low-level integration surface. a CLI wrapper on top of it is planned, but the socket API is the foundation. ## transport - transport: unix domain socket - encoding: newline-delimited JSON - request/response: one JSON request per line, one JSON response per line - subscriptions: send `events.subscribe`, receive an ack, then keep the same connection open for pushed events socket path resolution: 1. `HERDR_SOCKET_PATH` 2. `$XDG_RUNTIME_DIR/herdr.sock` 3. `$XDG_CONFIG_HOME/herdr/herdr.sock` 4. `$HOME/.config/herdr/herdr.sock` 5. `/tmp/herdr.sock` ## request shape all requests use this envelope: ```json { "id": "req_1", "method": "ping", "params": {} } ``` success responses: ```json { "id": "req_1", "result": { "type": "pong", "version": "0.1.2" } } ``` error responses: ```json { "id": "req_1", "error": { "code": "pane_not_found", "message": "pane p_1_99 not found" } } ``` ## ids workspace ids look like: - `w_1` - `w_2` pane ids look like: - `p_1_1` - `p_1_2` - `p_2_1` that means: - first number = workspace number - second number = pane id inside that workspace ## core request methods currently useful methods include: ### basic - `ping` ### workspace - `workspace.list` - `workspace.get` - `workspace.create` - `workspace.focus` - `workspace.rename` - `workspace.close` ### pane - `pane.list` - `pane.get` - `pane.read` - `pane.send_text` - `pane.send_keys` - `pane.split` - `pane.close` ### waits / events - `pane.wait_for_output` - `events.subscribe` ## example: create a workspace ```json { "id": "req_create", "method": "workspace.create", "params": { "cwd": "/home/can/Projects/herdr", "focus": true } } ``` example response: ```json { "id": "req_create", "result": { "type": "workspace_info", "workspace": { "workspace_id": "w_1", "number": 1, "label": "herdr", "focused": true, "pane_count": 1, "agent_state": "unknown" } } } ``` ## example: read pane output ```json { "id": "req_read", "method": "pane.read", "params": { "pane_id": "p_1_1", "source": "recent", "lines": 80 } } ``` `source` can be: - `visible` - `recent` ## example: send text and press enter low-level input is intentionally explicit: ```json { "id": "req_send_text", "method": "pane.send_text", "params": { "pane_id": "p_1_1", "text": "bun run dev" } } ``` then: ```json { "id": "req_send_keys", "method": "pane.send_keys", "params": { "pane_id": "p_1_1", "keys": ["Enter"] } } ``` this is kept separate on purpose. sending text is not always the same thing as submitting it. a future CLI wrapper will likely offer a more ergonomic `pane run` style command on top of this. ## example: one-shot wait for output ```json { "id": "req_wait", "method": "pane.wait_for_output", "params": { "pane_id": "p_1_1", "source": "recent", "lines": 200, "match": { "type": "substring", "value": "ready" }, "timeout_ms": 30000 } } ``` regex matching is also supported: ```json { "type": "regex", "value": "server.*ready" } ``` ## subscriptions `events.subscribe` is the long-lived pubsub entrypoint. you send a subscribe request once, get an ack on the same connection, and then keep reading newline-delimited JSON events from that same socket. ### subscription ack ```json { "id": "sub_1", "result": { "type": "subscription_started" } } ``` ## supported subscriptions ### lifecycle / base events - `workspace.created` - `workspace.closed` - `workspace.focused` - `pane.created` - `pane.closed` - `pane.focused` - `pane.exited` - `pane.agent_detected` - `pane.agent_state_changed` ### parameterized event - `pane.output_matched` ## example: subscribe to lifecycle events ```json { "id": "sub_life", "method": "events.subscribe", "params": { "subscriptions": [ { "type": "workspace.created" }, { "type": "workspace.focused" }, { "type": "pane.created" }, { "type": "pane.focused" }, { "type": "pane.agent_detected" }, { "type": "pane.closed" }, { "type": "workspace.closed" } ] } } ``` example pushed event: ```json { "event": "workspace_created", "data": { "workspace": { "workspace_id": "w_1", "number": 1, "label": "herdr", "focused": true, "pane_count": 1, "agent_state": "unknown" } } } ``` ## example: subscribe to output matches and agent state changes ```json { "id": "sub_1", "method": "events.subscribe", "params": { "subscriptions": [ { "type": "pane.output_matched", "pane_id": "p_1_1", "source": "recent", "lines": 200, "match": { "type": "substring", "value": "ready" } }, { "type": "pane.agent_state_changed", "pane_id": "p_1_1", "state": "idle" } ] } } ``` example pushed `pane.output_matched` event: ```json { "event": "pane.output_matched", "data": { "pane_id": "p_1_1", "matched_line": "server ready", "read": { "pane_id": "p_1_1", "workspace_id": "w_1", "source": "recent", "text": "...server ready...", "revision": 0, "truncated": false } } } ``` example pushed `pane.agent_state_changed` event: ```json { "event": "pane.agent_state_changed", "data": { "pane_id": "p_1_1", "workspace_id": "w_1", "state": "idle", "agent": "pi" } } ``` ## behavior notes - `pane.output_matched` emits when a subscription transitions into a matching state. it does not repeatedly spam the same visible match on every poll. - closing the socket connection ends the subscription. - there is no separate transport for events. - the same herdr process can serve regular request/response calls and long-lived subscription connections at the same time. ## intended layering recommended architecture: - socket api = foundational integration protocol - future `herdr ...` commands = ergonomic wrapper for humans and coding agents for agent workflows, the future CLI should likely expose blocking commands like: - `herdr pane read ...` - `herdr pane run ...` - `herdr wait output ...` - `herdr wait agent-state ...` but those should sit on top of this socket surface rather than replacing it.