Skip to content

UHP method reference

Everything the CLI does goes over owner-only local IPC speaking newline-delimited JSON: Unix sockets on macOS and Linux, and local named pipes on Windows. The default logical path remains ~/.artuditu/artuditu.sock. A named server normally uses ~/.artuditu/sessions/<name>/artuditu.sock. On Unix, an unusually long custom ARTUDITU_HOME gets a deterministic short socket alias under an owner-only directory in the native sticky temporary root (/private/tmp on macOS and /tmp on Linux) to stay within the operating system’s path limit. Windows discovery exposes the actual \\.\pipe\... address instead of asking clients to reproduce Artuditu’s internal mapping.

The resolved logical path is injected into every pane as $ARTUDITU_SOCKET_PATH. The selected local endpoint is protected by current-user ownership and is the default full-authority boundary, so methods do not need a session parameter. Harnesses can be given an optional short-lived scoped token instead of ambient owner authority.

Start with Automating with UHP for a practical connection, discovery, authentication, and streaming guide. Use artu --session <name> <command> from outside a pane. Inside a pane, keep the inherited socket so commands cannot drift to another server. Server-facing CLI commands map to these methods. Session lifecycle and theme acquisition are local helpers: theme files are validated and installed client-side, then only a bounded registry reload is sent to the selected server.

One request per connection, newline-terminated, and one JSON reply:

→ {"id":"1","method":"pane.split","params":{"down":true}}
← {"id":"1","result":{"type":"ok","pane":"7"}}

auth is optional. It carries a delegated token when a harness should have less authority than the local account:

→ {"id":"2","method":"workspace.get","auth":"luv_tok_…","params":{"workspace_id":"workspace_…"}}

Errors come back structured:

← {"id":"1","error":{"code":"not_found","message":"no such pane: 42"}}
Terminal window
# Try it raw (macOS/Linux):
printf '%s\n' '{"id":"1","method":"ping","params":{}}' | nc -U ~/.artuditu/artuditu.sock

artu uhp schema prints the complete installed UHP 1.0 contract without starting or connecting to a server. artu uhp capabilities queries the selected running server and returns its exact method registry, event sequence fence, identity rules, limits, and graphics support. Discover capabilities instead of inferring support from the Artuditu release number.

FamilyMethods
discovery / configuhp.capabilities · uhp.stats · uhp.token.create · uhp.token.list · uhp.token.revoke · config.get · config.patch · server.reload_config · server.agent_manifests · server.reload_agent_manifests
workspacesworkspace.list · workspace.get · workspace.open · workspace.focus · workspace.rename · workspace.pin · workspace.move · workspace.move_block · workspace.report_metadata · workspace.close
tabstab.list · tab.get · tab.new · tab.focus · tab.move · tab.swap · tab.rename · tab.close
runtime and agentssession.snapshot · agent.start · agent.prompt · agent.wait · events.subscribe
panespane.list · pane.get · pane.current · pane.layout · pane.neighbor · pane.edges · pane.split · pane.move · pane.swap · pane.focus · pane.focus_direction · pane.resize · pane.zoom · pane.rename · pane.purpose · pane.run · pane.send_input · pane.read · pane.status · pane.processes · pane.close · attach.pane
layoutslayout.export · layout.apply · layout.set_split_ratio
agentsagent.list · agent.get · agent.explain · agent.report · agent.release · agent.start · agent.prompt · agent.wait · agent.name · agent.fork · agent.send · agent.keys · agent.read · agent.sessions · agent.resume · pane.report_session · pane.report_event
searchsearch · search.capabilities · search.query · search.activate
filesfiles.tree · files.open · files.reveal · files.refresh
gitgit.status · git.branches · git.log · git.open
diffdiff.refresh · diff.list · diff.open · diff.get · diff.navigate · diff.note.add · diff.note.apply · diff.note.list · diff.note.edit · diff.note.resolve · diff.note.reopen · diff.note.remove · diff.note.send
terminalterminal.backend.inventory · terminal.backend.snapshot · terminal.backend.validate · terminal.backend.processes · terminal.backend.capture · terminal.backend.observe · terminal.backend.control · terminal.backend.type_literal · terminal.backend.submit_text · terminal.backend.send_key · terminal.backend.set_title · terminal.backend.notify · terminal.backend.create · terminal.backend.close · terminal.backend.wait_change · terminal.backend.wait_output · terminal.backend.events.subscribe
worktreesworktree.list · worktree.create · worktree.open · worktree.remove
orchestrationtask.add · task.list · task.get · task.claim · task.next · task.start · task.heartbeat · task.update · task.done · task.merge · task.release · lease.acquire · lease.list · lease.release
modulesmodule.list · module.info · module.link · module.unlink · module.enable · module.disable · module.action.list · module.action.invoke · module.pane.open · module.pane.focus · module.pane.close · module.config_dir · module.settings.list · module.settings.get · module.settings.set · module.log.list
themestheme.list · theme.path · theme.use · theme.reload
ui / serverui.sidebar · ui.dock.push · ui.dock.list · ui.dock.move · ui.bar.push · ui.bar.list · ui.bar.move · ui.bar.remove · ui.notification.push · ui.notification.clear · ui.toast · ping · server.stop · events.subscribe · events.wait

Parameter shapes mirror the CLI flags (artu task add --paths x{"paths": ["x"]}). For CLI commands, an omitted pane uses $ARTUDITU_PANE_ID when available; a raw API request with no pane uses the currently focused pane.

All families belong to the same Universal Harness Protocol. Terminal methods do not inherit the focused pane. They require explicit runtime identity and stricter framing, limits, and failure evidence. See the detailed UHP terminal methods reference.

Workspace indices are 0-based and tab positions are 1-based for backward compatibility. Every workspace and tab also has an opaque workspace_id or tab_id that survives reorder and restart. Requests may use the explicit ID field instead of a position. Terminal panes expose terminal_id, which remains stable for that exact PTY lifetime across pane and tab moves. Read methods do not change focus:

→ {"id":"p1","method":"pane.neighbor","params":{"pane":"7","direction":"right"}}
← {"id":"p1","result":{"type":"pane_neighbor","pane":"7","neighbor":"9"}}

pane.layout returns the pane rectangle in a normalized 10,000 by 10,000 logical area. Headless clients therefore receive deterministic geometry that does not depend on the latest attached terminal size. pane.edges reports which outer edges the pane touches. Directional queries use the same geometric selection as the TUI.

layout.export returns the complete binary split tree and focused pane. layout.apply accepts that tree plus an optional focus. It succeeds only when the tree contains every current pane in the target tab exactly once. It never spawns, closes, or recreates a pane, so PTYs, scrollback, process identity, and agent sessions survive. Duplicate, missing, foreign, invalid-axis, invalid-ratio, and over-deep trees are rejected before mutation. Dashboard tabs do not have mutable pane layouts.

layout.set_split_ratio takes a root-relative path of "a" and "b" steps and a ratio from 0 to 1. The live layout clamps the result to preserve the minimum pane size. pane.resize takes direction and cells; pane.swap requires both panes to be in the same tab. pane.focus_direction changes focus, while pane.neighbor only inspects it.

workspace.move_block takes a unique, bounded workspaces array and a final zero-based to position. All indices are validated before anything changes, the block keeps its supplied order, and the active workspace remains active. workspace.move is the single-workspace form.

config.get returns the normalized live config. config.patch takes {"patch":{...}}, recursively merges only known fields, validates the result, applies theme, language, keymap, prefix, sidebars, layout gaps, file visibility, and pane history budgets live, then persists atomically. Unknown or invalid fields leave the current config unchanged. server.reload_config reloads the on-disk config through the same apply path.

server.agent_manifests returns the active recognized agent names and rule count. server.reload_agent_manifests reloads built-in, managed, and user manifests and forces a fresh detection pass. manifest.reload remains a compatibility alias.

events.wait is the bounded, event-driven alternative to client polling. It takes an exact event name, an optional flat where object matched against event data, and timeout_s up to 3600 seconds. The default timeout is 30 seconds.

→ {"id":"w1","method":"events.wait","params":{"event":"pane.agent_status_changed","where":{"pane":"7"},"timeout_s":30}}
← {"id":"w1","result":{"type":"event_wait","matched":true,"sequence":41,"event":{"event":"pane.agent_status_changed","sequence":42,"data":{"pane":"7","status":"done"}}}}

Registration happens before the returned sequence fence. Pass after_sequence to consume a matching event already retained before the new connection. The waiter consumes the existing bounded event bus, is reclaimed when the client disconnects, and adds no app-loop polling or background process.

Reconnect, concurrency, and delegated authority

Section titled “Reconnect, concurrency, and delegated authority”

events.subscribe accepts after_sequence and replays retained events before live delivery. The replay window holds at most 256 shared frames and 1 MiB in total. A sequence older than that fixed window returns resync_required; take a fresh snapshot and subscribe again. Subscriber queues stay bounded and a slow consumer receives events.resync_required before disconnect.

Application-state results include the current revision, which is the same monotonic fence used by the event stream. A mutation may pass if_revision; if another successful mutation already changed state, Artuditu returns revision_conflict without executing the request. Existing compound methods such as agent.start, agent.prompt, workspace.move_block, layout.apply, and diff.note.apply validate and commit as one operation.

Create a delegated credential with:

→ {"id":"token-1","method":"uhp.token.create","params":{"scopes":["read","workspace"],"ttl_s":3600}}

The secret is returned once. Supported scopes are read, workspace, agent, terminal, orchestration, extensions, admin, and all. Tokens are memory-only, expire after at most 24 hours, are capped at 64 per server, and can be listed by public ID or revoked. They are a least-authority delegation tool; Unix socket ownership or the Windows named-pipe DACL remains the security boundary.

uhp.stats exposes bounded operational counters: active/capacity/accepted/ rejected connections, initial-frame timeouts, completed requests, byte totals, mean request latency, uptime, replay limits, and active terminal streams. Artuditu permits at most 80 active API connections, waits at most five seconds for the initial frame, and applies owner checks before dispatch. These limits prevent silent clients from growing threads or memory without bound.

The installed UHP schema embeds strict terminal method and stream components. Live terminal observe/control streams are capped at eight connections, use a two-frame queue, and are driven by existing coalesced output events rather than an idle timer. See the UHP terminal methods for framing and resynchronization rules.

uhp.capabilities negotiates the app-level automation surface and its limits. session.snapshot returns every workspace, tab, terminal pane, native view, stable terminal identity, root process, and agent state in one response. It includes the current event sequence, so a consumer can subscribe first and reconcile later events without a snapshot race. Neither method reads terminal text or starts a process.

pane.processes returns the already-cached executable names from Artuditu’s shared off-loop process scan. Full argument vectors are never exposed because they can contain prompts, credentials, or tokens. scan:"unavailable" is uncertainty, not proof that the pane has no children.

Agent state has two paths. The built-in and manifest-driven detector remains the default. agent.explain makes its identity source, state evidence, rule region, priority, confidence, and active integration authority explicit. A new agent integration can instead publish a leased authoritative state:

→ {"id":"r1","method":"agent.report","params":{"pane":"7","source":"fx/plugin","agent":"fx","status":"blocked","message":"approval required","sequence":12,"ttl_s":300}}
← {"id":"r1","result":{"type":"agent_report","pane":"7","agent":"fx","status":"blocked","source":"fx/plugin","sequence":12,"ttl_s":300}}

One source owns a pane at a time. Its sequence must increase, another source is rejected with authority_conflict, and the lease expires automatically. The owner renews with another report or calls agent.release. This lets unfamiliar agents integrate immediately without weakening process-based fallback detection or leaving stale state after an adapter crashes.

The normative schemas and fixtures live under protocol/uhp/v1 and are embedded in packaged binaries. Print that exact contract with artu uhp schema.

agent.wait takes {pane, status, timeout_s?} and parks on the server. The initial comparison and registration are atomic on the app loop, so it has no subscribe-then-check race and performs no polling. The existing artu wait agent-status command uses this method and falls back only when it talks to an older server.

agent.start takes name, kind, optional pane or anchor, optional direction, an args string array, and timeout_s. Artuditu selects or creates the pane, queues the launch as one submitted shell action, reserves the unique name, and waits for manifest-driven detection without composing separate UHP requests.

agent.prompt takes target, text, optional wait (default false), optional until, and optional timeout_s. until and timeout_s require wait:true. Paste and Enter are one PTY queue action. With wait:true, the response is held until a requested state has post-submission evidence. Artuditu accepts a detected Working transition or a newer output revision followed by a short quiet window, so a fast turn cannot disappear between detector ticks. A timeout returns submitted:true, matched:false, and evidence:"timeout". Never resend that prompt automatically because it may already have executed. Only one waiting prompt may own a pane at a time. A conflicting call returns agent_prompt_busy before Artuditu queues its text.

The legacy search method remains exact retained-output search. Its existing request and response fields do not change.

search.query is the typed fuzzy API:

→ {
"id": "find-1",
"method": "search.query",
"params": {
"query": "api auth",
"scope": "all",
"case_sensitive": false,
"all_sessions": true,
"limit": 50
}
}
← {
"id": "find-1",
"result": {
"type": "search_query",
"query": "api auth",
"scope": "all",
"total": 3,
"shown": 3,
"partial": false,
"matches": [
{
"id": "workspace:0",
"kind": "folder",
"label": "api-service",
"detail": "default › /work/api-service",
"score": 4120,
"target": { "workspace": 0 }
}
]
}
}

scope must be all, navigate, files, or output. query must contain 1 to 256 bytes after trimming, and limit must be 1 to 200. Case sensitivity applies to retained output. all_sessions defaults to false and queries only other running sessions in the selected Artuditu home when true. partial:true means at least one source was capped, unreadable, unavailable, timed out, or incompatible.

Every match has a stable result kind and structured target. Result kinds are session, folder, tab, pane, agent, file, and output. Consumers must not derive an action by parsing label or detail.

search.capabilities returns the search protocol version, supported methods, scopes, and response limits. Cross-session callers check it before querying a sibling server.

search.activate accepts one kind and an exact target returned by that same session. The owner revalidates workspace, tab, pane, file containment, and output anchors before focusing or opening anything. It is primarily used by Artuditu during named-session handoff. Invalid or stale targets return a structured invalid_request error instead of focusing a different object.

Workspace indices are 0-based positions. Use workspace_id when identity must remain stable across API reordering and server restart.

MethodParamsResult
workspace.rename{workspace, name}{type:"workspace_rename", workspace, name, cwd, pinned, display_position}
workspace.pin{workspace, pinned}{type:"workspace_pin", workspace, name, cwd, pinned, display_position}

The equivalent CLI commands are artu workspace rename <i> <name>, artu workspace pin <i>, and artu workspace unpin <i>. Renaming changes only the display label, trims surrounding whitespace, rejects empty or over-40-character labels, and never renames the folder on disk. Pinning changes sidebar display order without changing the API index or stealing focus. A pin on a parent or linked worktree floats that complete worktree group while preserving its internal order.

workspace.list remains in API-index order. Each row includes workspace_id, workspace, name, cwd, pinned, display_position, active, and tabs. Both workspace and display_position are 0-based; callers can therefore target a current position or use the stable ID and separately verify where it appears in the sidebar. Missing or malformed parameters return invalid_request, while an out-of-range workspace returns not_found without changing state.

Public tab positions are 1-based.

MethodParamsResult
pane.move{pane?, tab}{type:"pane_move", pane, workspace, tab}
pane.move{pane?, new_tab:true}{type:"pane_move", pane, workspace, tab}
tab.focus{tab}{type:"ok"}
tab.move{tab, to}{type:"tab_move", from, to, active}
tab.move{direction, tab?} where direction is "left" or "right"{type:"tab_move", from, to, active}
tab.swap{tab, with}{type:"tab_swap", tab, with, active}
tab.rename{name, tab?}{type:"ok"}

pane.move resolves an explicit pane anywhere, then keeps the move inside that pane’s workspace. The pane’s process and PTY stay alive, focus follows it, and an empty source tab is removed. tab must name another ordinary pane tab; Git, orchestration, and Mission Control dashboards are not valid destinations. Pass exactly one of tab or new_tab:true. The result’s workspace is the same zero-based workspace index used by the other workspace API results; its tab is the pane’s final 1-based position after any empty source tab is removed.

tab.focus selects one exact tab in the active workspace. tab.move either moves a tab to the requested final to position or moves it one position using direction. Directional movement targets the active tab when tab is omitted. tab.swap exchanges the positions identified by tab and with. The currently active tab remains active even when its number changes. Zero, missing, out-of-range, same-position, edge, and mixed direction + to requests return invalid_request without changing state.

tab.rename targets the active tab when tab is omitted. name must be a string of at most 40 characters after trimming; an explicit empty string clears the custom label. Invalid, zero, or out-of-range tab positions and all dashboard tabs return invalid_request without renaming another tab.

pane.purpose stores descriptive metadata without changing the pane name, address, command, or UI title. The purpose is trimmed, must fit on one line, and is limited to 60 Unicode characters.

{"id":"1","method":"pane.purpose","params":{"pane":"8","purpose":"frontend development server"}}

Pass {"pane":"8","clear":true} to remove it. Each pane.list row always includes purpose, using null when none is saved. The value follows the pane across session snapshot restoration.

pane.list (for the active tab) and pane.status (for any resolved pane) add read-only history fields to their existing result rows:

{
"scroll_offset": 420,
"history_rows": 1830,
"history_budget_bytes": 10485760,
"history_bytes": 7340032,
"history_exact": false
}

scroll_offset is the current distance from live output in rows, and history_rows is the retained scrollback row count. history_budget_bytes is the configured per-pane budget. history_bytes is the engine’s retained-memory reading; history_exact tells consumers whether that number is exact. With the current Alacritty adapter it is a conservative estimate (false), because the underlying terminal engine limits rows rather than allocation bytes. These fields observe state only; they do not grant remote control of a pane viewport.

The top-level pane.list result also includes additive process-lifetime render_performance and detection_performance objects. They expose bounded counts for render causes, loop wake sources, client projections, changed and unchanged projections, frame backpressure, panes considered and extracted, full-fleet audits, and audit recoveries. The counters are intended for local performance comparisons and contain no pane text, commands, paths, prompts, or client identifiers. Existing detection_extractions and detection_skips remain available for compatibility.

MethodParamsResult
agent.fork{target, name?, focus?}{type:"agent_fork", from, pane, agent, name, workspace, tab, focused}

target is a live alias, numeric pane ID, or a unique agent kind. Artuditu uses the agent’s native fork command and creates the sibling to the right of the source pane in that pane’s own workspace and tab. The parent stays running. name optionally assigns the new fork a live alias. focus defaults to true; when false, the current workspace, tab, pane focus, and zoom state are preserved.

Unsupported agents return unsupported_agent; a supported agent whose session ID cannot be resolved returns session_unknown; and PTY launch failure returns spawn_failed. Codex requires a hook-reported or Artuditu-resumed exact session identity and never falls back to the newest rollout in the workspace. Validation completes before a new pane is spawned.

files.tree returns the active workspace root and its currently expanded rows. When the root has not been loaded yet, including immediately after server restore with the FILES dock hidden, the request waits for one off-loop directory read rather than returning an empty root. files.refresh invalidates the cached listings and schedules that same worker immediately; it does not require a TUI client or visible dock.

DIFF methods are additive and read Git state without mutating the repository. Paths resolve inside the active workspace. When one path exists in more than one layer, callers must pass layer instead of accepting an ambiguous result.

MethodParamsResult
diff.refresh{}completes one shared FILES/DIFF status refresh and returns its generation
diff.list{layer?}cached staged, worktree, untracked, and conflict rows; schedules a bounded background refresh
diff.open{path?, layer?, view?, placement?}opens a native preview, pane, or tab
diff.get{path, layer?, include_patch?}bounded semantic hunks; line text is omitted by default
diff.navigate{pane?, action}moves an open native DIFF view
diff.note.add{file, layer?, old_line? or new_line?, end_line?, body, kind?}one local note
diff.note.apply{notes:[...]}validates and creates the whole batch or creates nothing
diff.note.list{file?, state?}local notes and delivery metadata
diff.note.edit{id, body}revised note
diff.note.resolve{id}resolved note
diff.note.reopen{id}reopened note
diff.note.remove{id}removes only the local note
diff.note.send{to, ids? , all_open?}sends one bounded grouped message to one live agent

layer is staged, worktree, untracked, or conflict. view is auto, split, or stack; placement is preview, pane, or tab. A note kind is question, issue, suggestion, or praise. Notes are anchored to old or new Git source lines, not rendered rows.

diff.list returns result.files from the latest shared FILES/DIFF snapshot and never runs Git on the app loop. On first use it waits for one off-loop scan. A later call returns the cache immediately and may schedule a cadence-gated background refresh; result.refreshing reports whether that refresh is in flight. Call diff.refresh when the caller must wait for a newly completed scan. Each file includes path, old_path, layer, status, additions, deletions, binary, unresolved notes, review state, and a fingerprint. Addition and deletion counts can be null until the file is loaded. If a path appears in multiple layers, all later calls for that path must include layer.

diff.get returns file metadata, aggregate counts, truncation metadata, and semantic hunks. Every hunk includes its ID, old/new start lines, and header. Without include_patch:true, lines is null. With it, every line carries its kind, old/new source numbers where applicable, and sanitized text.

diff.open returns the created or reused pane ID. Note mutations return the complete note under result.note; its id is used by edit, resolve, reopen, remove, and send. diff.note.list returns those objects under result.notes.

diff.navigate accepts these exact actions:

ActionBehavior
next, next_linenext source row
previous, previous_lineprevious source row
next_file, previous_filenext or previous changed file
next_hunk, previous_hunknext or previous hunk
next_note, previous_notenext or previous local note
top, bottomfirst or last row

Pass the pane returned by diff.open. An omitted pane follows the normal API focus rules, but fails if that target is not a native DIFF view.

→ {"id":"1","method":"diff.open","params":{"path":"src/app.rs","layer":"worktree","placement":"tab","view":"split"}}
← {"id":"1","result":{"type":"diff_open","pane":"7","path":"src/app.rs","layer":"worktree"}}
→ {"id":"2","method":"diff.navigate","params":{"pane":"7","action":"next_hunk"}}
← {"id":"2","result":{"type":"ok","pane":"7"}}

One note requires exactly one positive old_line or new_line. end_line extends that anchor on the same side and cannot precede the start. Every source line in the range must exist in the loaded bounded diff. Artuditu derives and stores bounded context from the actual source; clients cannot provide or forge anchor context.

diff.note.apply accepts the same fields as diff.note.add for every array item. It validates every path, layer, kind, body, source range, capacity limit, and anchor before writing anything. One invalid item rejects the whole request.

→ {
"id": "notes-1",
"method": "diff.note.apply",
"params": {
"notes": [
{
"file": "src/app.rs",
"layer": "worktree",
"new_line": 120,
"end_line": 123,
"kind": "suggestion",
"body": "Extract this validation"
},
{
"file": "src/cli.rs",
"layer": "staged",
"old_line": 88,
"body": "Is this fallback still required?"
}
]
}
}
← {"id":"notes-1","result":{"type":"diff_notes_applied","notes":[{"id":"n1","path":"src/app.rs","state":"open"},{"id":"n2","path":"src/cli.rs","state":"open"}]}}

Note filters accept only open, resolved, outdated, or orphaned. Note bodies are non-empty plain text, limited to 8 KiB, and reject unsupported control characters. Removal deletes only local review data. Sending requires a live agent target and records delivery only after its PTY accepts the bounded message. all_open:true selects all open notes; otherwise pass note IDs in ids.

include_patch:true remains subject to the 4 MiB raw patch, 20,000-row, and 16 KiB logical-line limits. Control sequences are stripped before content can reach the renderer or API. diff.note.send uses the same live-agent validation as agent.send; a shell or exited pane is rejected, and a failed input enqueue does not record delivery.

See DIFF Review for the interactive flow.

Theme acquisition does not accept an API URL or path. Use the CLI’s bounded, client-side theme install flow, which validates and writes the shared home before asking the selected server to reload.

MethodParamsResult
theme.list{}{themes:[...], problems:[...]}
theme.path{}{type:"theme_path", path}
theme.use{id}{type:"theme_selected", id}
theme.reload{}{type:"themes_reloaded", count, selected_available, problems}

theme.list returns built-in entries first, installed entries sorted by ID, and virtual terminal last. Every entry includes metadata, source, warnings, and whether it is active. Invalid local files are omitted from themes and reported in problems.

theme.use validates the ID against the server’s current registry before changing config.theme or rendering. Missing IDs return not_found without mutation. theme.reload scans and parses the home-level themes/ directory on the API connection worker, then sends one validated registry to the single-writer app loop. If the configured theme disappeared, Artuditu falls back visually but preserves its configured ID and reports selected_available:false.

For plugins that contribute a sidebar dock (a panel in the left or right sidebar. See Writing a Module). A module declares the dock in its manifest and pushes its content here, and artuditu owns the rendering.

MethodParamsResult
ui.sidebar{side?: "left"|"right", width?: int, visible?: bool}{width, visible} for that side
ui.dock.push{id, title?, placement?: "left"|"right", rows: Row[]}{type:"ok"}
ui.dock.list{}{docks: [{id, side}]}
ui.dock.move{id, side: "left"|"right"}{type:"ok"}

A Row is:

{ "text": string,
"dot": "idle" | "working" | "blocked" | "done",
"action": string,
"value": string,
"menu": [ {"title": string, "action": string,
"value"?: string, "destructive"?: bool} ] }

Only text is required. The first push mounts the dock into placement (default left); later pushes refresh its rows. A row’s action is a module action id run when the row is left-clicked, with value handed to it as ARTUDITU_MODULE_ROW_VALUE so one action can serve every row.

menu gives the row a right-click menu. Each entry runs an action id, an entry with an empty action is a divider, and destructive tints the label. An absent menu means the row has no context menu. An entry’s own value overrides the row’s, so one action can back a menu of variants. Menu entries also set ARTUDITU_MODULE_ACTION_ID, so one script can handle the whole menu.

The CLI wrappers are artu ui dock push|list|move (push also reads its rows from stdin).

Artuditu Bar is the bounded single-row extension surface beside tabs and between the fixed bottom guidance and version control. Modules declare widget ownership with [[bars]], then publish structured segments. Artuditu validates, caches, themes, compresses, and renders them; raw ANSI and arbitrary terminal drawing are never accepted.

Start with the Artuditu Bar guide for installation, placement, CLI use, and troubleshooting. This section documents the underlying UHP contract.

MethodParamsResult
ui.bar.push{owner?, id, region?, content, compact_content?, priority?}{type:"ok", changed, key}
ui.bar.list{}{type:"bar_list", widgets:[...]}
ui.bar.move{owner?, id, region:"top-right"|"bottom-right"|"off"}{type:"ok", key, region}
ui.bar.remove{owner?, id}{type:"ok", removed}
ui.notification.push{owner?, text, level?, ttl_ms?, action?, value?, dedupe_key?}{type:"ok"}
ui.notification.clear{owner?, dedupe_key?}{type:"ok", removed}

owner is injected automatically as $ARTUDITU_MODULE_ID when a module calls the CLI. A raw client may omit it only when the local widget id resolves unambiguously. push atomically replaces a widget’s complete live content; invalid input leaves the previous valid value intact. remove clears only live content, while move persists presentation. Disabling, unlinking, or uninstalling a module clears its widgets and notifications.

content and compact_content are arrays of these tagged segment shapes:

[
{"type":"text", "text":"CI", "tone":"muted"},
{"type":"symbol", "symbol":"", "tone":"success"},
{"type":"state", "state":"done", "label":"passing"},
{"type":"badge", "text":"2", "tone":"error",
"action":"details", "value":"run-1842"},
{"type":"progress", "value":3, "total":7, "width":8},
{"type":"spacer", "width":1},
{"type":"separator"}
]

Tones are normal, muted, accent, success, warning, and error. States are blocked, working, done, idle, and unknown. An action must belong to the widget’s enabled module. Clicks expose ARTUDITU_MODULE_BAR_ID, ARTUDITU_MODULE_BAR_SEGMENT, and optional ARTUDITU_MODULE_BAR_VALUE to that action.

Limits are intentionally small: 16 segments and 256 display columns per widget, 16 live widgets per module, 64 live widgets globally, and 30 updates per module per second. Text/value fields are capped at 256 bytes and reject control characters. A supplied notification TTL must be a positive integer and is clamped to 500–60,000 ms; the queue is capped at 32, and a matching (owner, dedupe_key) replaces the older notification. Top and Bottom rendering are each capped at 100 display columns for the region and 100 for one widget. The current viewport may provide less because tabs, tab navigation, shortcut guidance, and the fixed version retain their protected space. Extra inactive tabs use the existing tab-scroll window.

The equivalent wrappers are:

Terminal window
artu bar list
artu bar push --id status --region top-right --content '[{"type":"text","text":"CI"}]'
artu bar push --id status --content-file ./bar.json
artu bar move --id status --region bottom-right
artu bar remove --id status
artu ui notification push --text "CI passed" --level success --ttl-ms 4000
artu ui notification clear --dedupe-key ci-main

events.subscribe turns the connection into a stream: after one acknowledgment line, every event arrives as a sequenced JSON line. Subscriber queues are bounded, so a client that cannot keep up is disconnected instead of growing server memory. The acknowledgment declares loss_behavior: resync_required_then_close; overflow attempts one resync event before EOF. artu events is exactly this.

{"event":"pane.agent_status_changed","sequence":41,"data":{"pane":"4","status":"blocked","agent":"claude"}}
{"event":"task.gate_passed","sequence":42,"data":{"task":"t1"}}
{"event":"lease.acquired","sequence":43,"data":{"lease":"L2","task":"t3"}}

Event names: pane.created · pane.closed · pane.forked · pane.moved · pane.agent_status_changed · agent.hook · workspace.created · workspace.closed (a module hook may still spell these node.created / node.closed) · tab.created · tab.closed · tab.moved · task.added · task.claimed · task.started · task.ready · task.gate_running · task.gate_passed · task.gate_failed · task.needs_compaction · task.done · task.merged · task.merge_conflict · task.released · lease.acquired · lease.released.

terminal.backend.events.subscribe uses the same stream framing but filters to terminal.* events. Its acknowledgment includes the current sequence and queue capacity. See UHP terminal methods for subscribe-first snapshot reconciliation.

  • Prefer the CLI in scripts (artu --verbose-free, stable JSON to stdout). Talk raw JSON only when embedding Artuditu into another local client or integration.
  • The endpoint is owner-only. Unix uses a 0600 socket in a 0700 directory. Windows uses a local-only named pipe with an owner/System DACL and same-user server validation. Access equals command execution as your user. See the security model.
  • ping returns the server’s version and selected session name, useful for health checks, upgrade detection, and routing verification.