UHP terminal methods
This is the detailed terminal.backend.* method reference for the
Universal Harness Protocol. These methods use the same
artuditu-uhp 1.0 identity, request envelope, capabilities, and event sequence as
every other UHP method.
The namespace lets orchestrators discover every real PTY across all workspaces, capture bounded output, send exact input, and manage terminal lifecycles without scraping the TUI or depending on Artuditu agent manifests.
Start with Automating with UHP for a complete discovery-to-stream walkthrough, then use this page for the terminal namespace’s exact contract.
Protocol 1.0 is supported over Unix sockets on macOS and Linux and owner-only
named pipes on Windows. The request, response, identity, limit, and event
contracts are identical on every platform.
Discover and negotiate
Section titled “Discover and negotiate”Run the one discovery-only CLI route:
artu session list --jsonIt reads existing session entries and probes reachability without migrating
state, creating directories, selecting a session, or starting a server. Keep a
running row, validate its endpoint, then discover the global contract:
{"id":"cap-1","method":"uhp.capabilities","params":{}}The response returns the protocol version, a random server_generation, and
the exact methods and active limits. Never call an unannounced method.
Identity and lifecycle
Section titled “Identity and lifecycle”Every terminal has a random terminal_id for one successful PTY lifetime and a
current pane_id route. Moving a pane or reordering tabs preserves the terminal
ID. Replacing the PTY changes it. Restarting Artuditu changes
server_generation.
Every mutation supplies all three values. An optional expected root PID and process-start marker adds PID-reuse protection. Artuditu rejects stale routes and never follows a moved terminal implicitly for a write.
Operations
Section titled “Operations”| Capability | Method | Behavior |
|---|---|---|
inventory | terminal.backend.inventory | Every started PTY across ordinary tabs, with route, cwd, title, label, PID, and start marker |
validate | terminal.backend.validate | Returns alive, gone, or unknown without mutating state |
process_inspection | terminal.backend.processes | Cached executable identities under a validated root; never full argv |
capture | terminal.backend.capture | Bounded visible, recent_unwrapped, or detection text; optional safe SGR for the first two; recent capture includes passively retained alternate-screen rows |
observe | terminal.backend.observe | Initial safe ANSI frame followed by revision-deduplicated frames when this terminal emits output |
control_stream | terminal.backend.control | The observe stream plus correlated type_literal, submit_text, and send_key action frames |
type_literal | terminal.backend.type_literal | Literal UTF-8 with no implicit Enter |
submit_text | terminal.backend.submit_text | One atomic paste-and-Enter queue action |
send_key | terminal.backend.send_key | One documented logical key with terminal-mode-aware navigation |
set_title | terminal.backend.set_title | Bounded Artuditu label; does not inject OSC into the child |
notify_terminal | terminal.backend.notify | Terminal-scoped message through the existing notification policy |
create_workspace / create_sibling | terminal.backend.create | Explicit cwd and optional argv; returns only after the PTY is live |
close | terminal.backend.close | Removes only the fully validated terminal |
snapshot | terminal.backend.snapshot | Inventory plus an event-sequence consistency fence |
events | terminal.backend.events.subscribe | Bounded, sequenced terminal lifecycle and output-ready events |
wait_change | terminal.backend.wait_change | Event-driven wait for a newer terminal content revision |
wait_output | terminal.backend.wait_output | Event-driven wait for bounded literal text after a revision advances |
Capture responses include content_revision. To initialize without a race,
subscribe first, request a snapshot on another connection, discard
buffered events up through event_sequence, then apply later events. Each
subscriber queue is bounded to 256 events, with at most 64 simultaneous event
subscribers per server. A slow consumer is disconnected and must repeat
subscribe plus snapshot reconciliation. The server attempts to send
terminal.resync_required before closing an overflowed stream, but clients must
also treat EOF as possible loss because the final control frame is best effort.
The reference consumer replays output, metadata, and close events against the
snapshot. It requests a new snapshot for created, moved, or exited terminals
because those deliberately compact events do not repeat the full inventory
record.
For a rendered live view, use terminal.backend.observe instead of repeatedly
calling capture. It sends one terminal.frame immediately and then captures
only when the selected terminal’s existing coalesced terminal.output_ready
event advances content_revision. It never polls. Each stream has a two-frame
queue, a 64 KiB capture cap, a 200-row cap, and the server permits eight
combined observe/control streams. Slow clients receive best-effort
terminal.resync_required and disconnect. Treat EOF the same way and reconnect
for a fresh initial frame.
terminal.backend.control has the same output stream and accepts correlated
action frames after its acknowledgment:
{"id":"input-1","action":"type_literal","params":{"text":"cargo test"}}{"id":"input-2","action":"send_key","params":{"key":"enter"}}Actions reuse ordinary strict UHP validation and replies. Only one API control stream can lease a terminal at a time. Observe streams remain read-only, and local owner/TUI authority is unchanged.
While a full-screen agent owns the alternate screen, Artuditu passively retains
rows that scroll above that screen and includes them in recent_unwrapped.
Capture never sends mouse-wheel or navigation input into the child and never
moves the user’s viewport. Primary and alternate history share the existing
per-pane Scrollback Memory allowance. Alternate rows displace the oldest
primary rows only as they are produced, and alternate history is reclaimed
when the child returns to the primary screen.
Installed introspection
Section titled “Installed introspection”The binary embeds the published schema, so packaged integrations do not need a source checkout:
artu uhp schemaartu uhp capabilitiesartu uhp snapshotartu uhp eventsConsumers that cannot open the native local transport can forward exactly one bounded request and response through standard IO:
printf '%s\n' '{"id":"1","method":"terminal.backend.inventory","params":{}}' \ | artu uhp proxyThe same command composes over SSH without exposing a network listener:
printf '%s\n' '{"id":"1","method":"terminal.backend.inventory","params":{}}' \ | ssh host artu uhp proxyNative socket or pipe access remains preferable for high-frequency local calls.
The proxy is a portable bootstrap and remote-control path, not a streaming
replacement for terminal.backend.events.subscribe,
terminal.backend.observe, or terminal.backend.control. A native client sends
the stream request directly to the discovered endpoint. On Unix, this can also
be inspected manually with nc -U after replacing the identity fields with an
inventory result:
socket=$(artu session list --json | jq -r '.sessions[] | select(.default and .running) | .endpoint.address' | head -n 1)printf '%s\n' '{"id":"watch-1","method":"terminal.backend.observe","params":{"server_generation":"...","terminal_id":"...","pane_id":"7","mode":"visible","lines":80,"ansi":true}}' \ | nc -U "$socket"schema is local and works without a running server. The other commands target
the selected Artuditu session.
Framing and failure safety
Section titled “Framing and failure safety”An ordinary connection carries one UTF-8 JSON object request and one response,
each terminated by LF. Negotiated event and terminal observe/control methods
switch their connections to streams after one acknowledgment. Every complete
frame is capped at 1 MiB. Requests require id, method, and params; auth
is optional. Request IDs contain 1 to 128 ASCII letters, digits, ., _, :,
or -. Unknown fields, duplicate keys, invalid enums, and oversized frames are
rejected before dispatch.
Input success means the complete action was queued to the owned PTY writer.
Immediate metadata and lifecycle mutations report executed. Mutation errors
include dispatch: not_started or dispatch: rejected. If a client wrote a
mutation but lost its correlated response, only the client can classify it as
possibly executed. Do not retry it automatically. Refresh inventory instead.
Normative contract and example
Section titled “Normative contract and example”The normative schemas, limits, fixtures, endpoint rules, and conformance
manifest live in
protocol/uhp/v1/terminal.
The repository also includes a dependency-free Python consumer that validates
all fixtures and demonstrates capability and inventory reads. Public UHP
adapters should consume that package rather than copying types from Artuditu
source. The global request, response, and event schemas live at
protocol/uhp/v1.
The endpoint is a privileged local-user capability. On Unix, validate ownership, modes, symlinks, socket replacement, and the documented owner-only long-path alias. On Windows, require the discovery-supplied local pipe address, reject remote pipe paths, and verify that the named-pipe server process belongs to the current user. Artuditu creates its Windows listener with remote clients rejected and a protected owner/System DACL. Do not log prompts, captures, command arguments, cwd, or notification bodies by default.
Conformance and performance
Section titled “Conformance and performance”The repository includes independent fixture, mock, live-lifecycle, and
failure-injection consumers under examples/uhp/terminal/. Live tools
always create a dedicated ARTUDITU_HOME below the checkout’s target/ directory
and never target an installed server.
Before treating a build as protocol-ready, run the release benchmark with 1, 10, and 50 panes. It records direct-IPC latency, response sizes, idle CPU, RSS, threads, and descriptors. macOS additionally reports physical footprint, peak, and live malloc bytes. Results are host-specific engineering evidence, not portable guarantees; compare only matched release binaries and workloads. See the versioned conformance guide for exact commands and caveats.