Skip to content

Automating with UHP

Universal Harness Protocol, or UHP, is Luvus’s single public automation contract. It covers workspaces, tabs, panes, agents, terminal streams, files, Git, DIFF, worktrees, modules, bars, configuration, and events.

Normal commands such as luvus pane list are friendly wrappers over the same UHP methods. Unix sockets on macOS and Linux and named pipes on Windows are local transports, not separate APIs.

Terminal window
luvus session list --json
luvus uhp schema
luvus uhp capabilities
luvus uhp snapshot

UHP is always available to the local owner. Capability discovery returns luvus-uhp version 1.0, the complete method registry, access contracts, limits, event sequence, identity rules, and terminal capabilities.

Each ordinary connection carries one UTF-8 JSON request and response. Both are terminated by LF and bounded to the advertised frame limit.

{"id":"pane-1","method":"pane.list","params":{}}

Inside a Luvus pane, $LUVUS_SOCKET_PATH contains the selected endpoint. On macOS or Linux you can send a request directly:

Terminal window
printf '%s\n' '{"id":"pane-1","method":"pane.list","params":{}}' \
| nc -U "$LUVUS_SOCKET_PATH"

For a transport-neutral one-frame bridge, including over SSH:

Terminal window
printf '%s\n' '{"id":"snap","method":"session.snapshot","params":{}}' \
| luvus uhp proxy
printf '%s\n' '{"id":"snap","method":"session.snapshot","params":{}}' \
| ssh host luvus uhp proxy

uhp proxy forwards one bounded request and response. Persistent event and terminal streams must connect to the discovered local endpoint.

A success returns the request ID and result. A failure returns the request ID and a structured error.

{"id":"pane-1","result":{"type":"pane_list","panes":[]}}
{"id":"pane-1","error":{"code":"not_found","message":"pane not found"}}

If a connection is lost during a mutation, reconcile state before retrying. Never blindly repeat prompts, terminal input, or agent launches. State results include a monotonic revision; mutations can pass if_revision and stale callers receive revision_conflict before execution.

{"id":"explain","method":"agent.explain","params":{"pane":"7"}}
{"id":"prompt","method":"agent.prompt","params":{"target":"reviewer","text":"Review the diff","wait":true,"until":["idle","done","blocked"],"timeout_s":600}}

Terminal methods use stable PTY identity. Discover terminals first, then reuse the returned server_generation, terminal_id, and pane_id:

{"id":"inventory","method":"terminal.backend.inventory","params":{}}
{"id":"capture","method":"terminal.backend.capture","params":{"server_generation":"...","terminal_id":"...","pane_id":"7","mode":"recent_unwrapped","lines":100,"ansi":false}}

terminal.backend.observe provides a bounded read-only ANSI stream. terminal.backend.control adds correlated input frames and gives one client an exclusive control lease. Both are change-driven and idle without polling.

  1. Subscribe with events.subscribe.
  2. Fetch session.snapshot on another connection.
  3. Discard buffered events at or below the snapshot sequence.
  4. Apply later events in order.
  5. Resnapshot after a gap, overflow, EOF, reconnect, or generation change.

Use luvus uhp events for a stream test. Production clients should reconnect through the discovered endpoint and pass after_sequence.

The endpoint is owner-only and grants the owner full authority. Create an in-memory, expiring token when a harness needs narrower access:

{"id":"token","method":"uhp.token.create","params":{"scopes":["read","terminal"],"ttl_s":3600}}

Supply the returned secret in auth. Use uhp.token.list and uhp.token.revoke to manage it. Never log the secret.

Continue with the UHP reference and UHP method reference.