# Control Protocol

URL: https://tuios.dev/docs/control-protocol

> Drive a TUIOS daemon from scripts and programs with line-delimited JSON on its socket.

The TUIOS daemon accepts JSON requests on its Unix socket. Send one JSON object per line and read one JSON object back per line. No client library is needed: a shell, `nc` and `jq` are enough.

Use it when a script or program needs structured errors, blocking waits, or a live stream of events. For quick one-liners, the [CLI commands](https://tuios.dev/docs/sessions#remote-control) such as `tuios send-text` and `tuios wait-for` do the same things and are built on this protocol.

## Connecting

The protocol shares the socket the TUIOS client uses. There is no separate port and no handshake to negotiate: connect and write JSON. The daemon tells the two apart by the first byte, since a JSON request starts with `{` and a binary client frame never does.

```bash
if [ -n "$XDG_RUNTIME_DIR" ]; then
  SOCK="$XDG_RUNTIME_DIR/tuios/tuios.sock"
else
  SOCK="/tmp/tuios-$(id -u)/tuios.sock"
fi

printf '{"id":1,"verb":"list-sessions"}\n' | nc -U "$SOCK" | jq .
```

`socat - UNIX-CONNECT:"$SOCK"` works in place of `nc -U "$SOCK"`. The socket path for Windows is in [Sessions](https://tuios.dev/docs/sessions#daemon-location).

You can send several requests on one connection. They are answered in order, one response line per request line. To run requests in parallel, open more connections.

The socket is only accessible to your user. There is no token or password on top of that.

## Discovering the protocol

The daemon describes itself, so you do not need this page to use it:

```bash
tuios list-verbs                # every verb, its parameters and example requests
tuios list-verbs capture-pane   # one verb
tuios list-verbs --json         # the same, as JSON
```

The output includes the protocol version, every parameter with its type, default and accepted values, the error codes, and the request and response shapes. The same catalogue is available over the socket as the `list-verbs` verb.

The accepted values come from the same lists the daemon checks requests against, so they cannot disagree with what it will actually take.

## Requests and responses

A request has an optional `id`, a `verb`, and `params` when the verb takes any:

```json
{"id":1,"verb":"send-text","params":{"session":"work","window":"build","text":"make\n"}}
```

The `id` can be a number or a string. It is echoed back on the response, and left out if you left it out.

A successful response carries a `result`. Every result has a `type` field naming its shape:

```json
{"id":1,"result":{"type":"hello","protocol":1,"min_protocol":1,"daemon_version":"0.7.0","pid":4242,"sessions":2}}
```

A failure carries an `error` with a stable `code`, a readable `message`, and usually a `hint`:

```json
{"id":2,"error":{"code":"invalid_params","message":"verb capture-pane has no parameter foo","hint":{"verb":"list-verbs","command":"tuios list-verbs capture-pane","param":"foo","accepted":["session","window","source","styled","scrollback","ansi","resolved","palette","lines","start","end"],"detail":"An unknown parameter is refused rather than silently ignored. Fix the name and retry."}}}
```

The hint says what to do next: the parameter that was wrong, the values it accepts, the closest match to what you typed (`did_you_mean`), the names that do exist (`available`), or a command to run. Every hint field is optional.

A few rules hold for every verb:

- **Unknown parameters are refused**, not ignored. A misspelt parameter fails with `invalid_params` instead of doing something you did not ask for.
- **`session` can usually be left out.** It then means the most recently active session.
- **`window` takes a window ID or name.** Left out, it means the focused window.
- **A request that cannot do what it asked never reports success.**

## Versioning

`hello` reports the protocol version the daemon speaks and the oldest one it accepts. The current version is 1. Calling it first is optional: every other verb works without it.

```json
{"id":1,"verb":"hello","params":{"client":"my-script","version":"1.0.0","protocol":1}}
```

A caller whose version is outside the accepted range gets `protocol_mismatch`. New verbs are added without changing the version; it changes only when an existing verb or the envelope changes incompatibly.

## Verbs

The daemon currently implements 62 verbs. `tuios list-verbs` always reflects the binary you are talking to.

### Discovery

| Verb         | Purpose                                                       |
| ------------ | ------------------------------------------------------------- |
| `hello`      | Report the protocol version and the range the daemon accepts. |
| `list-verbs` | Describe every verb, or one named verb.                       |

### Sessions

| Verb                 | Purpose                                                                                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list-sessions`      | List every session the daemon holds.                                                                                                                 |
| `new-session`        | Create a session, detached, with its first window. Optional `name`, `width`, `height` (default 80x24), `window`, `window_name`, `cwd` and `command`. |
| `session-info`       | Details about one session.                                                                                                                           |
| `kill-session`       | End a session and every window in it.                                                                                                                |
| `set-session-name`   | Set a display name. The session keeps its real name for addressing.                                                                                  |
| `set-session-accent` | Set or clear a session's accent colour.                                                                                                              |

### Windows

| Verb           | Purpose                                                                                                                                           |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list-windows` | List a session's windows with ID, index, name, workspace, size, focus and agent state.                                                            |
| `new-window`   | Create a window. Optional `name`, `workspace`, `cwd`, `focus`, `command` (an argv run instead of a shell) and `host` (run it on another machine). |
| `close-window` | Close a window.                                                                                                                                   |
| `focus-window` | Focus a window by `window`, `relative` (`next`, `prev`) or `direction` (`left`, `right`, `up`, `down`). Direction needs an attached client.       |
| `move-window`  | Move a window to another workspace, optionally following it.                                                                                      |
| `set-window`   | Rename a window or change its minimized state.                                                                                                    |
| `split-window` | Split a window `horizontal` or `vertical` and open a new one beside it. Needs an attached client with tiling on.                                  |
| `resize`       | Resize a window's PTY to `width` by `height`.                                                                                                     |
| `popup`        | Run a command in a floating window that closes when the command exits. Needs an attached client.                                                  |
| `screenshot`   | Render a window to a `png`, `svg`, `ansi`, `html` or `txt` file.                                                                                  |

### Workspaces and layout

| Verb                  | Purpose                                                                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `list-workspaces`     | List workspaces with their names, window counts and which one is showing.                                                                 |
| `select-workspace`    | Show a workspace.                                                                                                                         |
| `set-workspace-name`  | Name a workspace. It keeps its number for addressing.                                                                                     |
| `set-workspace-order` | Set the order workspaces are displayed in.                                                                                                |
| `set-layout`          | Turn tiling on or off, equalize splits, or rotate the focused split. Needs an attached client.                                            |
| `run-command`         | Run any named command, the same set tape scripts and keybindings use. Prefer a dedicated verb where one exists: they report what changed. |

### Input and output

| Verb           | Purpose                                                                                                         |
| -------------- | --------------------------------------------------------------------------------------------------------------- |
| `send-text`    | Write text to a window's PTY exactly as given. A trailing `\n` presses Enter.                                   |
| `send-keys`    | Send key names such as `"ctrl+b,n"`. Keys are split on spaces and commas, so use `send-text` for ordinary text. |
| `capture-pane` | Read a window's contents. See [Capturing windows](https://tuios.dev/docs/control-protocol#capturing-windows).   |

### Options, themes and the dock

| Verb                   | Purpose                                                                                                                                     |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `list-options`         | Every settable option path with its type, default, accepted values and description.                                                         |
| `get-option`           | Read the value in effect, and whether it comes from the session or the defaults.                                                            |
| `set-option`           | Set an option by path, for example `appearance.dockbar_position`. The path and value are checked first. An attached client applies it live. |
| `list-themes`          | List the themes, built-in and imported. Name one to get its colours and their contrast.                                                     |
| `list-glyphs`          | List the glyph sets, and describe one.                                                                                                      |
| `list-hooks`           | List the hooks and what each last did.                                                                                                      |
| `list-dock-components` | List the dock's components and what each last did.                                                                                          |
| `refresh-dock`         | Re-run one dock component, or all of them.                                                                                                  |

### Agents and messages

| Verb                   | Purpose                                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| `list-agents`          | List the windows running coding agents, with their state and unread mail.                                    |
| `get-agent-state`      | Read a window's agent state.                                                                                 |
| `set-agent-state`      | Report an agent's state: `working`, `needs_input`, `idle`, `done`, `errored`, `unknown`, or `none` to clear. |
| `explain-agent-detect` | Explain why a window was or was not detected as running an agent.                                            |
| `explain-agent-screen` | Show what the screen-reading rules make of a window.                                                         |
| `send-agent-message`   | Leave a message for another window, or a notice for the whole session.                                       |
| `read-agent-messages`  | Read a session's messages, or one window's inbox.                                                            |
| `ask-agent`            | Type a question into another agent's window and wait for its answer.                                         |

### Files and worktrees

| Verb              | Purpose                                                                                                                      |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `stash-put`       | Copy a file into the session's own store and return the stored path.                                                         |
| `stash-get`       | Read a stored file back.                                                                                                     |
| `stash-list`      | List the files in a session's store.                                                                                         |
| `new-worktree`    | Create a git worktree and a session in it.                                                                                   |
| `list-worktrees`  | List the sessions whose directory is a git worktree.                                                                         |
| `remove-worktree` | Remove a worktree session's worktree and kill the session. Refuses uncommitted changes unless told to stash or discard them. |
| `fan`             | Start one prompt in several agents, each in its own worktree and session.                                                    |

### Other machines

| Verb                   | Purpose                                                                     |
| ---------------------- | --------------------------------------------------------------------------- |
| `list-hosts`           | List the machines in the `[hosts]` config table and the state of each link. |
| `list-host-sessions`   | List sessions here and on every configured host.                            |
| `list-host-agents`     | List agent windows here and on every configured host.                       |
| `open-host-connection` | Turn this connection into a connection to the daemon on a host.             |

Five more verbs, `open-pane`, `pane-agent`, `pane-cwd`, `resize-pane` and `read-dir`, are what one daemon calls on another to run a window's process on a different machine. You do not normally call them yourself.

### Events and waiting

| Verb          | Purpose                                                  |
| ------------- | -------------------------------------------------------- |
| `subscribe`   | Turn this connection into a stream of events.            |
| `unsubscribe` | Stop this connection's event stream.                     |
| `wait-for`    | Block until a condition matches, or fail with `timeout`. |

## Capturing windows

`capture-pane` reads one of two buffers, chosen by `source`:

- `visible` (default): the screen as it is now.
- `recent`: the screen plus the scrollback above it.

Set `styled` to keep ANSI colours and attributes, and `resolved` to turn the 16 theme colours into 24-bit RGB. Limit the output with `lines` (the last N lines) or with `start` and `end` (a 1-based inclusive range, which overrides `lines`).

```json
{"id":1,"verb":"capture-pane","params":{"session":"work","window":"build","source":"recent","lines":50}}
```

Long lines come back split at the window's width. The emulator does not record which rows were soft-wrapped, so there is no unwrapped capture.

## Waiting for conditions

`wait-for` replaces poll loops. It blocks until the condition matches, then returns a `wait_result`. If the timeout passes first, it fails with the `timeout` error code.

| Condition        | Matches when                                                                                                                       |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `session-exists` | A session named `session` exists.                                                                                                  |
| `window-output`  | Text matching the regular expression `pattern` is in the window. Checked at once, then on every new output.                        |
| `window-exit`    | The window's process exits.                                                                                                        |
| `window-idle`    | The window prints nothing for `idle` milliseconds (default 500).                                                                   |
| `agent-state`    | A window's agent state becomes one of the states in `until` (comma-separated). Without `window`, any window in the session counts. |
| `agent-message`  | A message arrives. With `window`, in that window's inbox. `thread` narrows it to one thread.                                       |

`timeout` defaults to 30000 milliseconds. For `window-output`, `source` defaults to `recent`, so output that has already scrolled off the screen still matches.

```json
{"id":1,"verb":"wait-for","params":{"condition":"window-output","session":"work","window":"build","pattern":"build (ok|failed)","timeout":120000}}
```

## Event stream

`subscribe` turns the connection into a stream. The first line back is an acknowledgement carrying the current sequence number, and every line after it is an event:

```json
{"id":1,"verb":"subscribe","params":{"session":"work","types":["window-created","window-closed","bell"]}}
```

```json
{"id":1,"result":{"type":"subscribed","seq":42}}
{"seq":43,"type":"window-created","session":"work","window":"1f3c...","pty_id":"9ab2...","title":"build","time":1790102604545781000}
```

Filter with `session`, `window` and `types`. Leave `types` out to receive everything.

There is no backfill: events start at the moment you subscribe. Call `list-windows` first to learn the current state, then follow the stream.

The event types are `window-created`, `window-closed`, `window-exit`, `window-retitled`, `window-focused`, `window-moved`, `window-minimized`, `window-restored`, `workspace-switched`, `agent-state`, `agent-message`, `output`, `bell`, `mode-changed`, `session-created`, `session-closed` and `gap`.

Events fire the same way whether a change came from a verb or from a person using the TUI. Window geometry changes do not produce events.

If you read too slowly, the daemon drops events rather than wait for you, and sends a `gap` event with the number dropped before the next one it delivers. `queue` sets how many events buffer before that happens (default 256).

## Verbs that need an attached client

Some verbs change what is drawn, which needs a client to draw it: `split-window`, `set-layout`, `popup`, and `focus-window` with a `direction`. On a headless session, such as one made with `tuios new --detach`, they fail with `needs_client` instead of pretending to succeed. Attach a client first if you need them.

## Error codes

Codes are stable, so it is safe to branch on them.

| Code                | Meaning                                                                                        |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| `invalid_request`   | The line is not a valid request, or the connection is in the wrong state for the verb.         |
| `unknown_verb`      | No verb has that name. The hint has the closest match and the full list.                       |
| `invalid_params`    | A parameter is missing, malformed, unknown, or outside its accepted values. The hint names it. |
| `session_not_found` | No session has that name. The hint lists the sessions that exist.                              |
| `session_exists`    | `new-session` was given a name that is already taken.                                          |
| `window_not_found`  | The window target matched nothing. The hint lists the windows.                                 |
| `no_windows`        | The session has no windows to act on.                                                          |
| `pty_not_found`     | The window's process has already exited.                                                       |
| `needs_client`      | The verb needs an attached client and none is attached.                                        |
| `option_not_found`  | No option has that path. The hint has the closest match.                                       |
| `command_failed`    | The attached client ran the request and it failed.                                             |
| `timeout`           | A `wait-for` condition did not match in time.                                                  |
| `not_ready`         | The target agent is busy, so the call did not type into it.                                    |
| `loop_refused`      | The call would loop, for example a window messaging itself.                                    |
| `rate_limited`      | The sender is over the message rate limit.                                                     |
| `no_keyboard`       | The target is the person's inbox, which has no window to type into.                            |
| `protocol_mismatch` | The caller's protocol version is outside the range the daemon accepts.                         |
| `unknown_host`      | No host by that name is configured.                                                            |
| `host_unreachable`  | The host is configured but not answering.                                                      |
| `host_refused`      | The link to the host is up but cannot take another connection.                                 |
| `unknown_pane`      | The daemon is not running a pane with that ID.                                                 |
| `not_worktree`      | The session is not in a git worktree.                                                          |
| `worktree_dirty`    | The worktree has uncommitted changes and neither stash nor force was passed.                   |
| `git_failed`        | A git command failed. The message is git's own.                                                |
| `internal`          | An unexpected failure inside the daemon.                                                       |

## Examples

```bash
# Create a headless session and run a build in it
printf '{"verb":"new-session","params":{"name":"ci"}}\n' | nc -U "$SOCK"
printf '{"verb":"send-text","params":{"session":"ci","text":"make test\\n"}}\n' | nc -U "$SOCK"

# Block until it finishes, then read the last 20 lines
printf '{"verb":"wait-for","params":{"condition":"window-output","session":"ci","pattern":"(PASS|FAIL)","timeout":600000}}\n' | nc -U "$SOCK"
printf '{"verb":"capture-pane","params":{"session":"ci","source":"recent","lines":20}}\n' \
  | nc -U "$SOCK" | jq -r .result.content

# Watch for bells and exits in every session
printf '{"verb":"subscribe","params":{"types":["bell","window-exit"]}}\n' | nc -U "$SOCK" | jq -c .
```

## Related

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/docs/control-protocol)*
