Socket API
Herdr exposes a local socket API for scripts and agents that need to inspect or control a running session.
Most automation should start with the CLI wrappers. Use the raw socket API only when you need direct request/response control or long-lived event subscriptions.
Choose an integration layer
| Layer | Use it for |
|---|---|
| Agent skill | Teaching a coding agent how to use Herdr from inside a pane. |
| CLI wrappers | Shell scripts, simple orchestration, and human debugging. |
| Raw socket API | Custom tools, protocol clients, and event subscribers. |
The layers share the same control surface.
Schema
The installed CLI can print the socket protocol schema bundled with that Herdr binary:
herdr api schemaherdr api schema --jsonherdr api schema --output herdr-api.schema.json
Terminal window
Plain herdr api schema prints a short summary. --json prints the
full JSON Schema document for tools, and --output PATH writes that
document to a file. The schema covers raw requests, success responses,
error responses, emitted events, and subscription events.
What you can control
The socket API can:
- create, list, focus, rename, and close workspaces
- create, list, focus, rename, and close tabs
- list, inspect, split, swap, focus, resize, rename, read, close, and send input to panes
- list, inspect, read, prompt, wait on, rename, focus, start, and attach agents through CLI helpers
- report custom agent state from hooks and plugins
- subscribe to events and wait for output or state changes
- install and uninstall built-in integrations
- stop the server and reload config
CLI examples
Create a workspace:
herdr workspace create --cwd ~/project --label api
Terminal window
Create a tab:
herdr tab create --label logs
Terminal window
Split a pane and run a command:
herdr pane split w1:p1 --direction rightherdr pane run w1:p2 "npm test"
Terminal window
Inspect and rearrange panes:
herdr pane layout --currentherdr pane neighbor --direction right --currentherdr pane resize --direction right --amount 0.1 --currentherdr pane swap --direction right --currentherdr pane zoom --on --currentherdr pane split w1:p1 --direction right --ratio 0.333
Terminal window
Wait for an agent:
herdr agent wait w1:p1 --until done
Terminal window
Read pane output:
herdr pane read w1:p2 --source recent --lines 50
Terminal window
Raw methods
Raw socket method names use dot notation:
| Area | Methods |
|---|---|
| Server | ping, server.stop, server.reload_config, server.agent_manifests, server.reload_agent_manifests |
| Notification | notification.show |
| Client | client.window_title.set, client.window_title.clear |
| Session | session.snapshot |
| Workspace | workspace.create, workspace.list, workspace.get, workspace.focus, workspace.rename, workspace.move, workspace.move_block, workspace.report_metadata, workspace.close |
| Worktree | worktree.list, worktree.create, worktree.open, worktree.remove |
| Tab | tab.create, tab.list, tab.get, tab.focus, tab.rename, tab.move, tab.close |
| Pane | pane.split, pane.swap, pane.move, pane.zoom, pane.layout, pane.process_info, pane.neighbor, pane.edges, pane.focus_direction, pane.resize, pane.list, pane.current, pane.get, pane.rename, pane.send_text, pane.send_keys, pane.send_input, pane.read, pane.graphics.info, pane.graphics.set, pane.graphics.clear, pane.graphics.stream, pane.report_agent, pane.report_agent_session, pane.report_metadata, pane.clear_agent_authority, pane.release_agent, pane.close, pane.wait_for_output |
| Popup | popup.close |
| Layout | layout.export, layout.apply, layout.set_split_ratio |
| Agent | agent.list, agent.get, agent.read, agent.explain, agent.send_keys, agent.prompt, agent.wait, agent.rename, agent.focus, agent.start, agent.view.set, agent.view.clear |
| Events | events.subscribe, events.wait |
| Integrations | integration.install, integration.uninstall |
| Plugins | plugin.link, plugin.list, plugin.unlink, plugin.enable, plugin.disable, plugin.action.list, plugin.action.invoke, plugin.log.list, plugin.pane.open, plugin.pane.focus, plugin.pane.close |
agent.wait is server-owned and event-driven. It pins the resolved pane
occupant so a replacement cannot satisfy the wait. agent.prompt
accepts an optional wait object with until and timeout_ms; this
submits the prompt and starts the wait in one request, avoiding a race
between separate calls. If the resolved agent is already blocked,
agent.prompt returns agent_blocked without sending input or starting
the wait.
workspace.move_block atomically moves the ordered workspace_ids
before before_workspace_id; omit the anchor to move the block to the
end. The ids must be unique and the anchor cannot be part of the block.
The response contains the authoritative ordered workspace list.
session.snapshot returns a one-time bootstrap snapshot for clients
that keep their own local runtime cache. The response includes
version/protocol metadata, focused workspace/tab/pane ids, workspace
records, tab records, pane records, tab layout snapshots, and agent
records. It is not a subscription; after reading it, subscribe to
resource events and update the local cache from those events. Call
session.snapshot again after reconnecting or when the local cache may
be stale. Attached worktree provenance is included on workspace records.
Full repo worktree discovery remains worktree.list.
From the CLI, herdr api snapshot prints the live session.snapshot
response as JSON for clients and agents that want a simple bootstrap
command.
Pane control methods use public pane ids such as w1:p1. Methods whose
schema makes pane_id optional use the server’s active focused pane
when it is omitted. pane.move always requires the source pane_id.
pane.send_keys and pane.send_input.keys accept Herdr key-combo
strings: plain printable keys, special keys like enter and esc,
modifier chords like ctrl+h, control+j, alt+x, and shift+tab,
function keys like f1, and named punctuation like minus and plus.
They do not accept prefix+ binding strings.
{"id":"req_current","method":"pane.current","params":{"caller_pane_id":"w1:p1"}}{"id":"req_layout","method":"pane.layout","params":{"pane_id":"w1:p1"}}{"id":"req_neighbor","method":"pane.neighbor","params":{"pane_id":"w1:p1","direction":"right"}}{"id":"req_edges","method":"pane.edges","params":{"pane_id":"w1:p1"}}{"id":"req_focus","method":"pane.focus_direction","params":{"direction":"right"}}{"id":"req_resize","method":"pane.resize","params":{"pane_id":"w1:p1","direction":"right","amount":0.1}}{"id":"req_zoom","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"toggle"}}{"id":"req_input","method":"pane.input.set","params":{"pane_id":"w1:p1","right_click":"pane"}}{"id":"req_split","method":"pane.split","params":{"direction":"right","ratio":0.333,"right_click":"pane","env":{"HERDR_ROLE":"tests"}}}{"id":"req_process","method":"pane.process_info","params":{"pane_id":"w1:p1"}}
pane.current returns a single PaneInfo. When caller_pane_id is
present, Herdr returns that pane. When it is omitted, Herdr returns the
active focused pane.
pane.input.set sets right_click to herdr or pane for one pane.
herdr is the default. pane forwards unmodified right-click hold and
drag gestures when the application requests terminal mouse reporting;
otherwise Herdr falls back to its pane menu. Right-clicking the pane
frame always opens Herdr’s menu. pane.split accepts the same optional
right_click value for the newly created pane.
PaneInfo includes scroll when terminal scroll metrics are available:
{ "offset_from_bottom": 12, "max_offset_from_bottom": 240, "viewport_rows": 30}
Clients can treat offset_from_bottom == 0 as at-bottom state.
Experimental pane graphics
Pane graphics let a plugin place image data over a pane. They are
available only when [experimental].kitty_graphics = true; otherwise
every pane graphics method returns feature_disabled. Calling
pane.graphics.info explicitly activates capability discovery and
returns the attached client’s cell size, file-frame options, pixel-mouse
support, the 16-layer limit, and pane_visible. pane_visible is true
only when the target is in the active workspace and tab and is not
hidden by zoom. Short-lived UI modes do not change it.
pane.graphics.set, pane.graphics.clear, and pane.graphics.stream
accept an optional layer_id (default primary). Set and stream also
accept z_index; layers are placed in stable (z_index, layer_id)
order. Each stream exclusively owns its layer, and closing it removes
that layer. Inline frames accept png, rgb, rgba, or bgra; BGRA
is normalized once to owned RGBA. Herdr advances the host cache one
image transaction per render pass, so arbitrary layer sets progress
without an aggregate frame. Headless transport keeps each transaction
within its 32 MiB wire limit; local monolithic rendering does not apply
that transport limit.
{"id":"graphics_info","method":"pane.graphics.info","params":{"pane_id":"w1:p1"}}{"id":"graphics_set","method":"pane.graphics.set","params":{"pane_id":"w1:p1","format":"png","image_width":800,"image_height":600,"data_base64":"...","placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}}}{"id":"graphics_clear","method":"pane.graphics.clear","params":{"pane_id":"w1:p1"}}
For repeated frames, open a dedicated socket with
pane.graphics.stream. After Herdr replies with ok, send one JSON
header and then exactly data_length raw bytes per inline frame.
Concurrent operations on that layer return stream_conflict.
{"id":"graphics_stream","method":"pane.graphics.stream","params":{"pane_id":"w1:p1","z_index":0}}{"format":"png","image_width":800,"image_height":600,"data_length":12345,"placement":{"viewport_col":0,"viewport_row":0,"grid_cols":80,"grid_rows":30}}
When pane.graphics.info advertises
file_frame_transport: "direct-kitty", an eligible local Ghostty,
kitty, or WezTerm client may submit an immutable private rgba or
bgra file with file.path, sequence, and revision. Direct Kitty
file transport is reserved for the default primary page layer; named
secondary layers use owned inline RGBA. BGRA is always copied, swizzled,
and rendered inline. Herdr replies with a pane_graphics_frame_ack only
after the terminal accepts the file, or after a safe owned inline
fallback is installed. Confirmed file-transport failure disables direct
files for that client connection without disabling exact pixel mouse. A
timeout or client loss closes the stream without acknowledging source
reuse. Monolithic --no-session mode advertises neither fast file
transport nor exact pixel mouse and remains on owned inline fallback.
Direct files are always complete canonical width * height * 4 RGBA
frames. file_frame_max_bytes is the limit that remains eligible for
owned inline fallback. Primary-layer RGBA files may use the larger
file_frame_direct_max_bytes limit when file_frame_transport is
available. Frames above the fallback limit are acknowledged only when
the terminal accepts the direct transfer; rejection closes the stream.
If a frame cannot use owned inline fallback while its pane is
temporarily hidden or cannot be placed during a redraw, Herdr uploads
the image without displaying it and replays its placement when the pane
becomes visible again. file_frame_damage: true means Herdr accepts
optional damage metadata for producer-side canonical-ring efficiency; it
still copies or presents the full file. Resize and full redraw replay
placements without retransmitting pixels.
pane.layout returns the tab layout snapshot with workspace_id,
tab_id, zoomed, outer area, focused_pane_id, pane rects, and
split rects/ratios. pane.neighbor and pane.edges include that same
layout snapshot so clients can make the next decision without private
layout state.
pane.process_info returns the pane’s shell pid, foreground process
group id when available, and foreground processes with pid, name,
argv/cmdline, and cwd when the platform exposes them.
layout.export returns a portable tab layout tree. Omit tab_id and
pane_id to export the active tab, pass tab_id to export that tab, or
pass pane_id to export the tab containing that pane.
{"id":"req_export","method":"layout.export","params":{"tab_id":"w1:t1"}}
The response includes workspace_id, tab_id, zoomed,
focused_pane_id, and root. root is a BSP tree of pane and
split nodes. Pane nodes can include pane_id, label, cwd, and
argv command. Split nodes use direction (right or down),
ratio, first, and second.
layout.apply creates a fresh tab from a declarative tree. If tab_id
is provided, Herdr creates the replacement tab first and then closes the
old tab. This restores structure, labels, cwd, env, and optional argv
commands; it does not preserve live PTYs, scrollback, or running
processes.
{ "id": "req_apply", "method": "layout.apply", "params": { "workspace_id": "wabc", "tab_label": "dev", "focus": true, "root": { "type": "split", "direction": "right", "ratio": 0.65, "first": { "type": "pane", "label": "editor", "cwd": "/repo" }, "second": { "type": "pane", "label": "tests", "cwd": "/repo", "command": ["sh", "-c", "just test"], "env": { "HERDR_ROLE": "tests" } } } }}
layout.set_split_ratio updates an existing split in a tab layout. The
response is type: "layout_split_ratio_set" with the updated portable
layout.
{"id":"req_ratio","method":"layout.set_split_ratio","params":{"tab_id":"w1:t1","path":[],"ratio":0.6}}
Process-launching methods accept an env object. Herdr applies those
key/value pairs to the newly launched process only. Herdr also injects
HERDR_SOCKET_PATH, HERDR_ENV=1, HERDR_WORKSPACE_ID,
HERDR_TAB_ID, and HERDR_PANE_ID into managed pane processes.
Herdr-managed variables are authoritative when they conflict with
caller-provided env.
pane.swap supports directional and explicit forms:
{"id":"req_swap_dir","method":"pane.swap","params":{"pane_id":"w1:p1","direction":"right"}}{"id":"req_swap_explicit","method":"pane.swap","params":{"source_pane_id":"w1:p1","target_pane_id":"w1:p2"}}
Swap is same-tab only. It preserves split shape, split ratios, pane ids,
and running processes. The response is type: "pane_swap" with
changed, optional reason, source_pane_id, optional
target_pane_id, focused_pane_id, and layout. Reason values are
no_neighbor, same_pane, not_found, and cross_tab. When a tab is
zoomed, swap keeps zoom active and mutates the hidden full-tab layout.
pane.move moves a running pane to a different tab, a new tab, or a new
workspace:
{"id":"req_move_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"tab","tab_id":"w1:t2","target_pane_id":"w1:p3","split":"right","ratio":0.5},"focus":true}}{"id":"req_move_new_tab","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_tab","workspace_id":"w1","label":"logs"},"focus":true}}{"id":"req_move_new_workspace","method":"pane.move","params":{"pane_id":"w1:p2","destination":{"type":"new_workspace","label":"logs","tab_label":"main"},"focus":true}}
Existing-tab moves require split: "right" | "down". target_pane_id
is optional and defaults to the target tab’s focused pane. Same-tab
layout changes remain pane.swap; moving to the source tab returns
changed: false with reason: "same_tab". Moves involving a zoomed
source or target tab return changed: false with
reason: "zoomed_tab".
The response is type: "pane_move" with changed, optional reason,
previous_pane_id, previous_workspace_id, previous_tab_id, the
moved pane, optional source_layout, target_layout, optional
created workspace or tab records, optional closed workspace or tab ids,
and focused_pane_id. Cross-workspace moves keep the internal pane and
terminal alive but assign a new public pane id in the destination
workspace. Subscribers can listen for pane.moved; Herdr does not emit
fake pane close/create events for the moved terminal process.
pane.zoom toggles, enables, or disables zoom for the target pane’s
tab:
{"id":"req_zoom_toggle","method":"pane.zoom","params":{"pane_id":"w1:p1"}}{"id":"req_zoom_on","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"on"}}{"id":"req_zoom_off","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"off"}}
Omitting pane_id targets the server’s active focused pane. The
response is type: "pane_zoom" with changed, zoom_changed,
focus_changed, optional reason, pane_id, focused_pane_id,
zoomed, and layout. changed is true when either zoom state or
focus changed. Reason values are single_pane, already_zoomed, and
already_unzoomed.
The CLI wrapper for notification.show is:
herdr notification show "build failed" --body "api workspace" --position top-left --sound request
Terminal window
Show a user notification through the configured toast delivery:
{"id":"req_notify","method":"notification.show","params":{"title":"build failed","body":"api workspace","position":"top-left","sound":"request"}}
title is required and must contain visible text after control
characters and repeated whitespace are removed. body is optional.
Herdr collapses newlines, tabs, carriage returns, and repeated
whitespace into spaces, then trims notification text to 80 characters
for title and 240 characters for body. An empty sanitized title
returns invalid_params. position is optional and applies only when
ui.toast.delivery = "herdr"; desktop positions are relative to the
full Herdr frame, and omitted positions use ui.toast.herdr.position.
Terminal, system, and off delivery ignore position. sound is
optional and can be none, done, or request; it defaults to none
and plays only when the notification is shown.
The response reports whether anything was shown:
{"id":"req_notify","result":{"type":"notification_show","shown":true,"reason":"shown"}}
Possible reasons are shown, disabled, rate_limited,
no_foreground_client, and busy. disabled means
ui.toast.delivery = "off". busy means an existing in-app toast was
not replaced. Terminal and system delivery are best-effort through the
current foreground attached Herdr client.
Set or clear the foreground client’s outer terminal window title:
{"id":"req_title","method":"client.window_title.set","params":{"title":"herdr api"}}{"id":"req_title_clear","method":"client.window_title.clear","params":{}}
client.window_title.clear hands the title back to ui.window_title.
The response is type: "client_window_title" with changed and reason
set, cleared, or no_foreground_client.
Worktree methods manage Git checkouts as Herdr workspaces.
worktree.create creates a checkout and returns the new workspace,
tab, root_pane, and worktree records. If the requested branch
already exists locally, it checks out that branch; otherwise it creates
the branch from the requested base or HEAD. worktree.open opens an
existing checkout or returns the already-open workspace.
worktree.remove runs git worktree remove against a linked child
workspace and never deletes the branch.
Create a worktree from a source workspace:
{"id":"req_1","method":"worktree.create","params":{"workspace_id":"w1","branch":"worktree/api","focus":false}}
Open an existing checkout:
{"id":"req_2","method":"worktree.open","params":{"workspace_id":"w1","branch":"worktree/api","focus":true}}
Remove a linked checkout:
{"id":"req_3","method":"worktree.remove","params":{"workspace_id":"2","force":false}}
Use at most one of workspace_id or cwd for worktree.list,
worktree.create, and worktree.open; omit both to use the active
workspace. Use exactly one of path or branch for worktree.open.
Raw socket cwd and path values must be absolute; the CLI expands
relative --cwd and --path values before sending requests. Workspace
responses include optional worktree provenance when a workspace
belongs to a Herdr worktree group. Worktree commands can emit
workspace.updated when an existing workspace gains or changes worktree
provenance.
Worktree commands also emit lifecycle events. worktree.create emits
workspace.created, tab.created, pane.created, and
worktree.created. worktree.open emits worktree.opened, and it also
emits workspace/tab/pane creation events when it opens a new Herdr
workspace. worktree.remove emits worktree.removed; if the linked
workspace is still open, it also emits workspace.closed.
Agent view queries
agent.view.set installs one transient declarative projection for the
built-in Agents view. The projection is reevaluated whenever agent facts
or current UI context change. It controls the expanded and collapsed
sidebar, mobile Agents list, mouse targets, indexed focus, and
next/previous Agent navigation. It does not change agent.list,
notifications, detection, or global attention counts.
Show agents in the currently presented Space or agents needing attention elsewhere, then order by attention and most recent state transition:
{ "id": "view_set", "method": "agent.view.set", "params": { "source": "plugin:example.agent-views", "label": "focus", "filter": { "op": "any", "filters": [ { "op": "eq", "field": "workspace_id", "value": {"context": "current_workspace_id"} }, { "op": "in", "field": "status", "values": ["blocked", "done"] } ] }, "sort": [ {"field": "attention", "order": "desc"}, {"field": "state_change_seq", "order": "desc"} ] }}
Filter nodes use op values all, any, not, eq, in, or
exists. Built-in filter fields are status, workspace_id, tab_id,
pane_id, agent, seen, and state_change_seq. Use
{"token":"name"} as a field to filter plugin-reported pane metadata.
Values are strings, booleans, unsigned numbers, or a context object.
Context values are current_workspace_id and current_tab_id, and may
only be compared to the matching ID field. Effective status values are
idle, working, blocked, done, and unknown; done means idle
and not yet seen.
Sort fields are workspace_order, tab_order, pane_order,
attention, status, agent, seen, state_change_seq, or
{"token":"name"}. Sorts are stable, evaluated in order, and accept
asc or desc. Missing values stay after present values. When sort
is omitted, the existing ui.agent_panel_sort policy remains active. A
custom sort temporarily replaces that policy without rewriting config.
source identifies the owner. Plugins use plugin:<HERDR_PLUGIN_ID>;
Herdr rejects plugin-owned sets when that plugin is missing or disabled.
Other callers may use their own non-plugin: source. A successful set
atomically replaces the previous view. The view lasts until it is
cleared, replaced, its owning plugin is disabled, unlinked, or
uninstalled, or the server exits. Plugins that want durable behavior
should save the query under HERDR_PLUGIN_STATE_DIR and reapply it from
a [[startup]] hook.
Clear unconditionally, or only when the named source still owns the view:
{"id":"view_clear","method":"agent.view.clear","params":{}}{"id":"view_clear_owned","method":"agent.view.clear","params":{"source":"plugin:example.agent-views"}}
A source mismatch leaves the active view unchanged. Set and clear
responses use type: "agent_view" and report active, source, and
optional label.
Plugin APIs
The plugin API is an early interface for executable workflow tools. A
plugin is a package with a herdr-plugin.toml manifest. The manifest
declares startup hooks, shareable actions, event hooks, terminal pane
entrypoints, and link handlers. Startup hooks run once after restore
when the API is ready. Actions and panes are manifest-only; runtime
action registration and runtime argv pane creation are not part of v1.
Installed and linked plugins persist across restarts. Herdr writes a
plugins.json registry file alongside session.json on plugin.link,
plugin.unlink, plugin.enable, and plugin.disable. The
herdr plugin install and herdr plugin link CLIs also write the same
registry when Herdr is not running, then startup loads it automatically.
On startup, Herdr re-reads each manifest from its original path; if the
file is missing or unparseable, the entry is kept with a warnings
field so plugin.list surfaces it.
Herdr validates event hook on values against known event names at link
time. An unrecognised name does not block the link, but the returned
plugin info includes a warning (e.g.
"unknown event 'worktree.craeted'"). Check the warnings field in the
plugin.link and plugin.list responses.
Link a local plugin manifest:
{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/path/to/plugin","enabled":true}}
plugin.link also accepts optional source metadata. The CLI uses this
when it installs from GitHub so plugin.list can show origin, requested
ref, resolved commit, and managed checkout path:
{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/managed/plugin/herdr-plugin.toml","enabled":true,"source":{"kind":"github","owner":"ogulcancelik","repo":"herdr-plugin-examples","subdir":"worktree-bootstrap","requested_ref":"main","resolved_commit":"abc123","managed_path":"/data/plugins/github/<managed-checkout>","installed_unix_ms":1780000000000}}}
The path can be a plugin directory containing herdr-plugin.toml or a
direct manifest path. The manifest shape is:
id = "example.worktree-bootstrap"name = "Worktree Bootstrap"version = "0.1.0"min_herdr_version = "0.7.0"description = "Prepare new worktrees"platforms = ["linux", "macos", "windows"]
[[build]]command = ["bun", "install"]
[[actions]]id = "bootstrap"title = "Bootstrap worktree"contexts = ["workspace"]command = ["bun", "run", "bootstrap.ts"]
[[events]]on = "worktree.created"command = ["bun", "run", "bootstrap.ts"]
[[panes]]id = "board"title = "Worktree board"placement = "overlay"command = ["bun", "run", "board.ts"]
[[link_handlers]]id = "github-issue"title = "Open GitHub issue"pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$"action = "bootstrap"
min_herdr_version is required. The server refuses to link a plugin
when the field is missing, invalid, or newer than the running Herdr
binary.
Declare platforms at the top level with the OS identifiers (linux,
macos, windows) your plugin supports. Omitting platforms is
allowed for local development. plugin.link succeeds, but the response
includes a warning. Individual build commands, actions, event hooks,
panes, and link handlers can declare their own platforms to override
the plugin-level list; if omitted they inherit from the plugin. Invoking
an action or opening a pane whose effective platforms do not include the
current OS returns a platform_unsupported error.
List, enable, disable, or unlink linked plugins:
{"id":"req_plugin_list","method":"plugin.list","params":{}}{"id":"req_plugin_disable","method":"plugin.disable","params":{"plugin_id":"example.worktree-bootstrap"}}{"id":"req_plugin_enable","method":"plugin.enable","params":{"plugin_id":"example.worktree-bootstrap"}}{"id":"req_plugin_unlink","method":"plugin.unlink","params":{"plugin_id":"example.worktree-bootstrap"}}
Actions are resolved from the linked manifest. plugin.action.list
returns all actions across installed plugins; pass plugin_id to
filter.
{"id":"req_plugin_actions","method":"plugin.action.list","params":{}}{"id":"req_plugin_actions_filtered","method":"plugin.action.list","params":{"plugin_id":"example.worktree-bootstrap"}}
plugin.action.list returns each action’s effective platforms after
applying plugin-level inheritance.
Invoke an action by its qualified id or bare action id:
{"id":"req_plugin_invoke","method":"plugin.action.invoke","params":{"action_id":"example.worktree-bootstrap.bootstrap","context":{"invocation_source":"keybinding"}}}
plugin.action.invoke resolves the manifest action, starts the manifest
command, and returns the Herdr-built invocation context plus the started
command log record. Missing context fields are filled from the active
workspace, tab, focused pane, worktree provenance, and request id.
Invoking an action from a disabled plugin returns a plugin_disabled
error.
Herdr injects HERDR_SOCKET_PATH, HERDR_BIN_PATH, HERDR_ENV=1,
HERDR_PLUGIN_ID, HERDR_PLUGIN_ROOT, HERDR_PLUGIN_CONFIG_DIR,
HERDR_PLUGIN_STATE_DIR, HERDR_PLUGIN_CONTEXT_JSON, and available
HERDR_WORKSPACE_ID, HERDR_TAB_ID, and HERDR_PANE_ID values. Action
commands also receive HERDR_PLUGIN_ACTION_ID; event hooks receive
HERDR_PLUGIN_EVENT and HERDR_PLUGIN_EVENT_JSON; pane commands
receive HERDR_PLUGIN_ENTRYPOINT_ID.
List recent action and event command logs:
{"id":"req_plugin_logs","method":"plugin.log.list","params":{"plugin_id":"example.worktree-bootstrap","limit":20}}
Event hooks run for enabled installed plugins when Herdr emits a
matching event name such as worktree.created.
There is no Herdr-managed plugin storage API in v1.
HERDR_PLUGIN_CONFIG_DIR and HERDR_PLUGIN_STATE_DIR are path
discovery only; plugins own their files, schemas, migrations, and
cleanup.
Open a managed terminal UI:
{"id":"req_plugin_pane","method":"plugin.pane.open","params":{"plugin_id":"example.board","entrypoint":"board","placement":"zoomed","target_pane_id":"w1:p1","env":{"HERDR_ROLE":"board"},"focus":true}}
plugin.pane.open requires an installed, enabled, platform-compatible
plugin, then launches the requested manifest [[panes]] entrypoint as
an argv-backed terminal pane. Manifest pane placement defaults to
overlay; request placement overrides the manifest with overlay,
popup, split, tab, or zoomed. Overlay and popup placements use
the active tiled pane as launch context. Popup terminals are
session-modal and do not change the tab layout; optional width and
height fields set their outer size as terminal cells or percentages
such as "80%". Omitted dimensions default to half the terminal size,
with too-small values clamped to the popup minimum. A popup has no pane
ID, remains outside all pane.* and agent APIs, emits no pane lifecycle
events, leaves plugin focus context on the underlying tiled pane, and
does not export HERDR_PANE_ID to its process. Popup launch returns
ok; popup.close closes the active popup and returns popup_not_open
when none exists. Split and zoomed panes target an existing pane; tab
panes can target a workspace. Split, tab, zoomed, and overlay panes
behave like normal Herdr panes, and plugin.pane.focus and
plugin.pane.close continue to operate on those panes.
Socket transport
Herdr uses newline-delimited JSON over a local socket. On Unix, that socket is a Unix domain socket. On Windows, it is a named pipe.
Send one request per line:
{"id":"req_1","method":"ping","params":{}}
A successful response includes the same id:
{"id":"req_1","result":{"type":"pong"}}
Event subscriptions keep the connection open after the initial response.
Socket paths
The default socket lives under your Herdr config directory.
Named sessions have separate sockets:
~/.config/herdr/herdr.sock~/.config/herdr/sessions/<name>/herdr.sock
Resolution order:
- explicit CLI
--session <name> HERDR_SOCKET_PATHHERDR_SESSION=<name>- default session socket
Use HERDR_SOCKET_PATH only for low-level overrides.
For plugins, prefer invoking HERDR_BIN_PATH and the CLI wrappers when
you need portable Windows behavior. Raw socket clients are responsible
for using the platform-native local socket form.
Agent state reporting
Integrations report agent state with pane.report_agent.
{ "id": "req_1", "method": "pane.report_agent", "params": { "pane_id": "w1:p1", "source": "custom:docs", "agent": "docs-bot", "state": "working", "message": "building docs" }}
state carries semantic agent state and affects waits, notifications,
and rollups. Report display-only values separately through metadata.
Session-only official integrations report native session references with
pane.report_agent_session. State-reporting integrations can still
include native session references in pane.report_agent.
State-independent session reports do not affect waits, notifications, or
rollups.
{ "id": "req_2", "method": "pane.report_agent_session", "params": { "pane_id": "w1:p1", "source": "herdr:codex", "agent": "codex", "agent_session_id": "..." }}
pane.get, pane.list, agent.get, and agent.list expose a
read-only agent_session object when Herdr has a stored native session
reference:
{ "agent_session": { "source": "herdr:codex", "agent": "codex", "kind": "id", "value": "..." }}
If no native session reference is stored, the field is omitted.
pane.get, pane.list, agent.get, and agent.list also expose
foreground_cwd when Herdr can resolve the cwd of the process currently
controlling the pane PTY. The existing cwd field remains the
pane/workspace cwd used for labels, follow-cwd behavior, and restored
session state.
PaneInfo and AgentInfo expose optional terminal_title and
terminal_title_stripped fields. terminal_title is the latest OSC 0/2
title after safety normalization. terminal_title_stripped removes one
recognized leading activity or spinner glyph and following whitespace.
These server-owned values are ephemeral across a cold restart and are
independent of the metadata title and semantic agent state.
Use pane.report_metadata when a user hook wants to customize
presentation without taking over lifecycle state from a Herdr
integration.
{ "id": "req_2", "method": "pane.report_metadata", "params": { "pane_id": "w1:p1", "source": "user:claude-title", "agent": "claude", "title": "Refactor auth middleware", "display_agent": "Claude: auth", "state_labels": { "working": "refactoring auth", "idle": "ready", "done": "review ready" }, "tokens": { "summary": "refactor auth", "model": "opus" }, "ttl_ms": 3600000 }}
Metadata reports are display-only. Valid metadata can override the pane
title, displayed agent name, visible state labels, and arbitrary named
tokens. working, blocked, idle, waits, notifications, and rollups
still come from semantic state. Native session restore comes from stored
official session references. agent is an optional guard for
presentation fields against the authoritative agent label;
applies_to_source similarly guards presentation fields against the
active lifecycle authority source. These guards do not apply to token
patches: token reporters own clearing and TTL refresh. Use
display_agent to change the visible name. state_labels keys must be
idle, working, blocked, done, or unknown.
Token maps are per-resource patches. A string sets a key, JSON null
clears it, and omitted keys remain unchanged. The latest accepted update
wins. Optional TTL applies independently to token keys updated by that
report. Pane tokens are exposed by pane and agent get/list responses and
can be rendered as $name in Agent sidebar rows. A report may mention
at most 16 token keys, and a pane or workspace may retain at most 32
keys. Token names are 1–32 ASCII letters, digits, underscores, or
hyphens.
Workspace tokens use the same contract:
{"id":"req_3","method":"workspace.report_metadata","params":{"workspace_id":"w1","source":"user:jj","tokens":{"jj_status":"2 changes","old":null},"ttl_ms":5000}}
Workspace get/list responses expose the resulting tokens map, and
Space sidebar rows can render values such as $jj_status. Changes and
TTL expiry emit workspace.metadata_updated with the latest workspace
snapshot. This metadata event is available to API subscribers but does
not invoke plugin event hooks.
Presentation text is normalized before storage. Herdr trims surrounding
whitespace, removes control characters, and caps title,
display_agent, each state label, and token values at 80 characters.
Empty normalized token values clear that key.
source and applies_to_source are source identifiers. They must be 80
characters or fewer and may contain only ASCII letters, digits, colon,
dot, underscore, and hyphen.
Use ttl_ms for short-lived metadata. It must be between 1 and
86400000 milliseconds. Omit ttl_ms for metadata that should stay
until replaced, cleared, or the pane/workspace closes. Presentation
fields retain their existing source-scoped expiry behavior; each token
updated by the call receives its own deadline. Token metadata is not
restored after a server restart.
Use seq when a hook may send updates out of order. For the same
source, reports with a sequence number less than or equal to the last
accepted sequence are accepted by the API but ignored by the pane state.
A pane or workspace accepts sequenced token reports from at most 32
distinct sources during its lifetime; clearing or expiry does not
release those source slots.
Event subscriptions
Subscribe to events when you need a long-lived stream:
{ "id": "sub_1", "method": "events.subscribe", "params": { "subscriptions": [ { "type": "pane.agent_status_changed", "pane_id": "w1:p1", "agent_status": "blocked" } ] }}
The first response acknowledges the subscription. Later lines are pushed events.
Workspace event subscriptions include workspace.created,
workspace.updated, workspace.metadata_updated, workspace.renamed,
workspace.moved, workspace.reordered, workspace.closed, and
workspace.focused. workspace.metadata_updated reports token changes
and TTL expiry without invoking plugin event hooks. Other workspace
events describe Herdr UI/runtime lifecycle. workspace.created includes
optional workspace.worktree provenance when the workspace belongs to a
worktree group. workspace.moved includes the moved workspace_id,
requested insert_index, and updated ordered workspaces list.
workspace.reordered includes the atomically moved workspace_ids,
optional before_workspace_id, and authoritative ordered workspaces
list. workspace.closed includes a final workspace snapshot when
Herdr can still identify it before removal. Tab event subscriptions
include tab.created, tab.closed, tab.focused, tab.renamed, and
tab.moved. tab.moved includes the moved tab_id, workspace_id,
requested insert_index, and updated ordered tabs list for that
workspace. Pane event subscriptions include pane.created,
pane.updated, pane.closed, pane.focused, pane.moved,
pane.exited, pane.agent_detected, pane.output_matched,
pane.agent_status_changed, and pane.scroll_changed. Terminal-title
changes can emit pane.updated, but spinner-only raw-title changes do
not emit it when terminal_title_stripped is unchanged.
pane.scroll_changed is scoped to one pane_id and emits pane_id,
workspace_id, and the current scroll metrics whenever Herdr observes
a changed scroll snapshot. Layout event subscriptions include
layout.updated. The event carries the updated PaneLayoutSnapshot for
one tab. Clients that bootstrap with session.snapshot should replace
the cached layout with the same workspace_id and tab_id.
Worktree event subscriptions include worktree.created,
worktree.opened, and worktree.removed. Worktree events describe Git
checkout lifecycle. worktree.created includes the opened workspace
and created worktree. worktree.opened includes the target
workspace, opened worktree, and already_open. worktree.removed
includes the workspace_id, removed worktree, and forced.
Use events.subscribe for lifecycle events. Dedicated wait helpers are
documented separately when a one-shot wait is supported.
Reading panes
Use pane.read through the CLI unless you are writing a protocol
client.
herdr pane read w1:p1 --source visible --lines 80herdr pane read w1:p1 --source recent --lines 120herdr pane read w1:p1 --source recent-unwrapped --lines 120herdr pane read w1:p1 --source detection
Terminal window
recent-unwrapped is useful for logs because it ignores soft wrapping.
detection returns the bottom-buffer snapshot used by agent screen
detection.
Waiting for state
Use waits to coordinate agents and scripts.
herdr agent wait w1:p1 --until doneherdr agent wait w1:p1 --until blocked
Terminal window
Agent waits observe semantic state, not arbitrary command completion.
Response shapes
Successful responses look like this:
{ "id": "req_1", "result": { "type": "pane_info", "pane": { "pane_id": "w1:p1", "terminal_id": "term_abc123", "workspace_id": "w1", "tab_id": "w1:t1", "focused": true, "agent_status": "working", "revision": 42 } }}
server.agent_manifests returns the active agent detection manifest
sources and remote update diagnostics without reloading rules:
{ "id": "req_1", "result": { "type": "agent_manifest_status", "last_check_unix": 1781043522, "last_result": "checked", "manifests": [ { "agent": "cursor", "source": "/home/me/.config/herdr/agent-detection/cursor.toml", "source_kind": "local override", "active_version": "2026.06.10.1", "cached_remote_version": "2026.06.10.1", "local_override_shadowing_remote": true, "remote_update_result": "current" } ] }}
Fields such as last_check_unix, last_result, active_version,
cached_remote_version, remote_update_result, remote_update_error,
remote_last_checked_unix, and warning are omitted when not
available. server.reload_agent_manifests returns
agent_manifest_reload with the same manifests item shape after
reloading the in-memory rule cache.
agent.explain evaluates the target pane’s detection snapshot in the
running server using the server’s active manifest cache:
{ "id": "req_2", "method": "agent.explain", "params": { "target": "w1:p1" }}
The response contains the same explain object printed by
herdr agent explain --json, including the final state, manifest source
and version, matched rule, evaluated rule evidence, skip-state reason,
idle fallback reason, and screen_detection_skip_reason when a full
lifecycle hook authority makes screen rules non-authoritative.
Clients need a running server that supports agent.explain; after
upgrading Herdr, restart or live-handoff the server before relying on
this method.
Errors look like this:
{ "id": "req_1", "error": { "code": "not_found", "message": "pane not found" }}Protocol stability
Herdr has a protocol version for client/server compatibility. Protocol changes are reviewed for release compatibility.
Check the server protocol with ping or herdr status before depending
on new behavior. Handle unknown fields gracefully.
Last updated Oct 08, 2026