# socket api
While hrdx runs it serves a control API on a Unix socket next to the state file (hrdx.sock), so scripts, editors, and coding agents can inspect and drive a running session. Disable with --api=false. The protocol is newline-delimited JSON: one request per line, one response line with the same id.
On macOS and Linux, if the state directory cannot create Unix sockets, hrdx uses a private directory under $XDG_RUNTIME_DIR/hrdx/ when XDG_RUNTIME_DIR is an absolute, usable path. The API socket is named hrdx-<id>.sock, where the id is the first 16 hex digits of the SHA-256 hash of the absolute state file path. The holder socket uses holder-<id>.sock. Working legacy paths are unchanged, and hrdx keeps an existing holder socket when reattaching. Runtime sockets may disappear at logout. Native Windows keeps its existing socket location. WSL has its own socket namespace.
# macOS default$ SOCK="$HOME/Library/Application Support/hrdx/hrdx.sock"# Linux, or macOS with XDG_CONFIG_HOME set$ SOCK="$XDG_CONFIG_HOME/hrdx/hrdx.sock"# fallback only if the state directory cannot host sockets# STATE must be the absolute --state path used by hrdx$ STATE="/absolute/path/to/state.json"$ ID=$(printf %s "$STATE" | shasum -a 256 | cut -c1-16)# On Linux, use sha256sum instead of shasum -a 256 if needed$ SOCK="$XDG_RUNTIME_DIR/hrdx/hrdx-$ID.sock"# inspect and drive the running session$ echo '{"id": "1", "method": "status"}' | nc -U "$SOCK"$ echo '{"id": "2", "method": "workspace.create", "params": {"path": "~/Developer/api", "agent": "claude"}}' | nc -U "$SOCK"$ echo '{"id": "3", "method": "pane.create", "params": {"workspace": "api", "kind": "shell", "split": "down"}}' | nc -U "$SOCK"$ echo '{"id": "3b", "method": "pane.create", "params": {"workspace": "api", "kind": "shell", "split": "float", "anchor": "center", "width_pct": 40, "height_pct": 30}}' | nc -U "$SOCK"$ echo '{"id": "4", "method": "pane.send_text", "params": {"pane_id": 3, "text": "run the tests", "enter": true}}' | nc -U "$SOCK"$ echo '{"id": "5", "method": "pane.wait", "params": {"pane_id": 3, "until": "idle"}}' | nc -U "$SOCK"$ echo '{"id": "6", "method": "pane.read", "params": {"pane_id": 3}}' | nc -U "$SOCK"$ echo '{"id": "7", "method": "menu.register", "params": {"target": "pane", "label": "Run linter", "action_id": "custom.run_linter"}}' | nc -U "$SOCK"
| method | effect |
|---|---|
| ping | liveness check, returns pong |
| status | workspaces, tabs, and panes with id, kind, and state |
| workspace.create | open a directory, optionally in a group |
| workspace.move | change a workspace's group without restarting panes |
| group.list | list occupied group paths and their ancestors |
| workspace.close | close a workspace by name or path |
| pane.create | add a pane (split: right, down, tab, float) |
| pane.send_text | type into a pane, optionally press enter |
| pane.read | the pane's visible screen as plain text |
| pane.wait | block until a pane's agent is idle or busy |
| pane.close | close a pane by id |
| menu.register | add a process-local pane, tab, or sidebar context-menu entry |
| events.subscribe | keep the connection open and push events |
| plugins.status | lifecycle, grants, scopes, and contributions of approved plugins (requires --plugins) |
| plugins.control | start, stop, restart, or reload an approved plugin by id (requires --plugins) |
status also reports optional sub-repository status per workspace when nested repositories are enabled, and durable bindings for durable agents.
## durable agents
Durable methods are separate from pane input: pane.send_text types into a view, continuous.submit admits work to a conversation. They answer after a host round trip that never blocks the UI. Host error codes such as conflict, busy, queue_full, not_found, and unauthorized pass through unchanged.
{"id":"8","method":"continuous.attach","params":{"workspace":"api","split":"tab"}}
{"id":"9","method":"continuous.submit","params":{"profile":"local-1a2b3c4d5e6f","conversation":"<id>","content":"run the tests","request_id":"ci-42","files":["notes.txt"]}}| method | effect |
|---|---|
| continuous.hosts | host profiles with managed, connection, and stopped state |
| continuous.host.start / .stop | start or stop a managed host |
| continuous.workspace_host | host for new durable panes in a workspace |
| continuous.attach | open a durable view: a new agent, another view of a conversation, or a fork or subagent by id |
| continuous.list | conversations on a host |
| continuous.snapshot | execution state, queue, run, approvals, recovery, tasks, usage |
| continuous.submit | admit a prompt with a required request_id; queue or steer; optional local files |
| continuous.submission | read one submission |
| continuous.rename | set a title with a revision check |
| continuous.configure | change provider, model, or reasoning, keeping other settings |
| continuous.abort | explicitly abort active work and withdraw queued prompts |
| continuous.approvals / .decide | list or decide pending approvals |
| continuous.recovery / .unblock | preview interrupted runs or release a hold |
| continuous.tasks | host tasks of a conversation |
| continuous.task.abort / .retry_cleanup | abort a task or retry its cleanup |
| continuous.budgets / .budget.set | read or write cost and token budgets |
Durable events: continuous.bound, continuous.state_changed, and continuous.host_changed. Retrying a submit with the same request_id returns the original submission instead of new work.
## workspace groups
Groups are optional, nested labels for organising workspaces, including non-Git directories. Add a group_path array when creating a workspace, or move an existing workspace by name or path. Moving does not restart panes or holder sessions. workspace.move requires group_path. Use [] to make the workspace standalone. Group paths persist across restarts, and status keeps its flat workspaces list with optional group_path metadata.
{"id":"1","method":"workspace.create","params":{"path":"/work/game-feature","agent":"shell","group_path":["game","reviews"]}}
{"id":"2","method":"workspace.move","params":{"workspace":"/work/game-feature","group_path":["game","ready"]}}
{"id":"3","method":"group.list"}group.list returns occupied paths and their ancestors, for example ["game"] and ["game","ready"]. Groups disappear when their last workspace moves or closes. Names are case-sensitive labels, not filesystem paths. For an opt-in Git default, pass group_from_git: true on workspace.create without group_path. A repository root or linked worktree then uses the primary repository basename. Explicit group_path, including [], takes precedence. Branch names remain separate workspace metadata.
## events
After events.subscribe the connection stays open and hrdx pushes lines like:
{"event": "pane.busy_changed", "data": {"pane_id": 3, "busy": false}}Events: workspace.created, workspace.closed, workspace.moved, pane.created, pane.closed, pane.busy_changed, menu.action, and the durable continuous.* events. Workspace lifecycle events include optional group_path metadata. Events are best-effort, so query status or group.list to recover after a missed event. A script can react the moment an agent finishes or a custom menu action is selected instead of polling. pane.wait plus pane.send_text is enough to build simple agent pipelines: prompt an agent, wait until it is idle, read the screen, move on.
## floating panes
Create a tab-scoped pane above the split layout with split set to float. width_pct and height_pct are required integers from 1 through 100. anchor defaults to center and also accepts top, bottom, left, or right.
{"id":"3b","method":"pane.create","params":{"workspace":"api","kind":"shell","split":"float","anchor":"center","width_pct":40,"height_pct":30}}Floating panes do not change split ratios and are not shown in the sidebar or restored after hrdx restarts. Multiple panes stack in creation or focus order. Close one through its title-bar x or with pane.close. The status response marks them with floating, anchor, width_pct, and height_pct fields.
## custom menu actions
Register an ephemeral item in the pane, tab, or sidebar context menu. The item appears after the built-in actions. Re-registering the same action_id replaces it; registrations last until hrdx exits.
{"id":"7","method":"menu.register","params":{"target":"pane","label":"Run linter","action_id":"custom.run_linter"}}Selecting the item publishes a best-effort menu.action event to event subscribers. It includes the selected target's pane, tab, and workspace context when applicable.
{"event":"menu.action","data":{"action_id":"custom.run_linter","target":"pane","pane_id":3,"workspace":"api","path":"/path/to/api","tab_index":0}}