# 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"
methodeffect
pingliveness check, returns pong
statusworkspaces, tabs, and panes with id, kind, and state
workspace.createopen a directory, optionally in a group
workspace.movechange a workspace's group without restarting panes
group.listlist occupied group paths and their ancestors
workspace.closeclose a workspace by name or path
pane.createadd a pane (split: right, down, tab, float)
pane.send_texttype into a pane, optionally press enter
pane.readthe pane's visible screen as plain text
pane.waitblock until a pane's agent is idle or busy
pane.closeclose a pane by id
menu.registeradd a process-local pane, tab, or sidebar context-menu entry
events.subscribekeep the connection open and push events
plugins.statuslifecycle, grants, scopes, and contributions of approved plugins (requires --plugins)
plugins.controlstart, 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"]}}
methodeffect
continuous.hostshost profiles with managed, connection, and stopped state
continuous.host.start / .stopstart or stop a managed host
continuous.workspace_hosthost for new durable panes in a workspace
continuous.attachopen a durable view: a new agent, another view of a conversation, or a fork or subagent by id
continuous.listconversations on a host
continuous.snapshotexecution state, queue, run, approvals, recovery, tasks, usage
continuous.submitadmit a prompt with a required request_id; queue or steer; optional local files
continuous.submissionread one submission
continuous.renameset a title with a revision check
continuous.configurechange provider, model, or reasoning, keeping other settings
continuous.abortexplicitly abort active work and withdraw queued prompts
continuous.approvals / .decidelist or decide pending approvals
continuous.recovery / .unblockpreview interrupted runs or release a hold
continuous.taskshost tasks of a conversation
continuous.task.abort / .retry_cleanupabort a task or retry its cleanup
continuous.budgets / .budget.setread 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}}