6.4 KiB
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:
HERDR_SOCKET_PATH$XDG_RUNTIME_DIR/herdr.sock$XDG_CONFIG_HOME/herdr/herdr.sock$HOME/.config/herdr/herdr.sock/tmp/herdr.sock
request shape
all requests use this envelope:
{
"id": "req_1",
"method": "ping",
"params": {}
}
success responses:
{
"id": "req_1",
"result": {
"type": "pong",
"version": "0.1.2"
}
}
error responses:
{
"id": "req_1",
"error": {
"code": "pane_not_found",
"message": "pane p_1_99 not found"
}
}
ids
workspace ids look like:
w_1w_2
pane ids look like:
p_1_1p_1_2p_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.listworkspace.getworkspace.createworkspace.focusworkspace.renameworkspace.close
pane
pane.listpane.getpane.readpane.send_textpane.send_keyspane.splitpane.close
waits / events
pane.wait_for_outputevents.subscribe
example: create a workspace
{
"id": "req_create",
"method": "workspace.create",
"params": {
"cwd": "/home/can/Projects/herdr",
"focus": true
}
}
example response:
{
"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
{
"id": "req_read",
"method": "pane.read",
"params": {
"pane_id": "p_1_1",
"source": "recent",
"lines": 80
}
}
source can be:
visiblerecent
example: send text and press enter
low-level input is intentionally explicit:
{
"id": "req_send_text",
"method": "pane.send_text",
"params": {
"pane_id": "p_1_1",
"text": "bun run dev"
}
}
then:
{
"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
{
"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:
{
"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
{
"id": "sub_1",
"result": {
"type": "subscription_started"
}
}
supported subscriptions
lifecycle / base events
workspace.createdworkspace.closedworkspace.focusedpane.createdpane.closedpane.focusedpane.exitedpane.agent_detectedpane.agent_state_changed
parameterized event
pane.output_matched
example: subscribe to lifecycle events
{
"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:
{
"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
{
"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:
{
"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:
{
"event": "pane.agent_state_changed",
"data": {
"pane_id": "p_1_1",
"workspace_id": "w_1",
"state": "idle",
"agent": "pi"
}
}
behavior notes
pane.output_matchedemits 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.