Skip to content

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.

Run the one discovery-only CLI route:

Terminal window
artu session list --json

It 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.

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.

CapabilityMethodBehavior
inventoryterminal.backend.inventoryEvery started PTY across ordinary tabs, with route, cwd, title, label, PID, and start marker
validateterminal.backend.validateReturns alive, gone, or unknown without mutating state
process_inspectionterminal.backend.processesCached executable identities under a validated root; never full argv
captureterminal.backend.captureBounded visible, recent_unwrapped, or detection text; optional safe SGR for the first two; recent capture includes passively retained alternate-screen rows
observeterminal.backend.observeInitial safe ANSI frame followed by revision-deduplicated frames when this terminal emits output
control_streamterminal.backend.controlThe observe stream plus correlated type_literal, submit_text, and send_key action frames
type_literalterminal.backend.type_literalLiteral UTF-8 with no implicit Enter
submit_textterminal.backend.submit_textOne atomic paste-and-Enter queue action
send_keyterminal.backend.send_keyOne documented logical key with terminal-mode-aware navigation
set_titleterminal.backend.set_titleBounded Artuditu label; does not inject OSC into the child
notify_terminalterminal.backend.notifyTerminal-scoped message through the existing notification policy
create_workspace / create_siblingterminal.backend.createExplicit cwd and optional argv; returns only after the PTY is live
closeterminal.backend.closeRemoves only the fully validated terminal
snapshotterminal.backend.snapshotInventory plus an event-sequence consistency fence
eventsterminal.backend.events.subscribeBounded, sequenced terminal lifecycle and output-ready events
wait_changeterminal.backend.wait_changeEvent-driven wait for a newer terminal content revision
wait_outputterminal.backend.wait_outputEvent-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.

The binary embeds the published schema, so packaged integrations do not need a source checkout:

Terminal window
artu uhp schema
artu uhp capabilities
artu uhp snapshot
artu uhp events

Consumers that cannot open the native local transport can forward exactly one bounded request and response through standard IO:

Terminal window
printf '%s\n' '{"id":"1","method":"terminal.backend.inventory","params":{}}' \
| artu uhp proxy

The same command composes over SSH without exposing a network listener:

Terminal window
printf '%s\n' '{"id":"1","method":"terminal.backend.inventory","params":{}}' \
| ssh host artu uhp proxy

Native 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:

Terminal window
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.

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.

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.

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.