Skip to content

Terminal methods

This is the detailed terminal.backend.* method reference for the Universal Harness Protocol. These methods use the same luvus-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 Luvus agent manifests.

Start with Getting started 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
luvus 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 Luvus changes server_generation.

Every mutation supplies all three values. An optional expected root PID and process-start marker adds PID-reuse protection. Luvus rejects stale routes and never follows a moved terminal implicitly for a write.

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

Each pane admits at most 4,096 pending input actions and 8 MiB of owned input buffer capacity across the channel and writer queues together. Capabilities advertise these as terminal.limits.queued_input_actions and terminal.limits.queued_input_bytes. These cumulative limits are separate from the per-request input_bytes limit. A submit reserves its final Enter too. Reservations remain held until the whole action is written or cancelled.

Admission never waits for a blocked child. At capacity, control calls return send_failed with no input queued. TUI input and terminal-generated replies share the same bound and produce a coalesced warning rather than silently discarding overflow. The server logs pty.input_rejected without input content. No automatic retry is performed: inspect the pane and retry only a rejected action after it becomes responsive. Already accepted actions retain FIFO order; queue acceptance is not proof of child consumption or task completion.

While a full-screen agent owns the alternate screen, Luvus 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
luvus uhp schema
luvus uhp capabilities
luvus uhp snapshot
luvus 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":{}}' \
| luvus 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 luvus 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=$(luvus 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 Luvus 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 Luvus 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. Luvus 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 LUVUS_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.