┌──────────────────────────┐ │ app │ │ ├─ tabs │ │ │ ├─ tab "Active" │ │ │ └─ tab "Done" │ │ ├─ list │ │ │ └─ list_item "Ship" │ │ └─ text_input "New task" │ └──────────────────────────┘
Your TUI publishes its widget tree, focus and available actions. AI agents act on nodes instead of guessing at a screen.
Install the bridge your agent harness talks to.
$ curl -sSf https://y0sif.github.io/taria/install.sh | sh
$ claude mcp add taria -- taria-mcp --app <label>
Also on crates.io: cargo install taria-mcp --locked. Prebuilt binaries for x86_64 and aarch64 Linux and both macOS architectures are attached to every release, each with its sha256.
One dependency, five edits, and your TUI is legible to agents.
$ cargo add taria-ratatui
It re-exports the protocol crate, so taria::{Action, Node, Role} is reachable without a second dependency. The integration guide walks through the retrofit.
Agents reach TUIs by scraping rendered screens. A highlighted row and a selected row look the same. A dialog and a bordered panel look the same. Whether d deletes something depends on which widget has the keyboard, and the screen does not say which one does.
So an agent infers structure from layout, aims keystrokes at a keymap it guessed, and decides whether an input worked by comparing two screens, which reports any unrelated repaint as success.
A grid of characters. Everything for a program you did not write, and no answer to why any of it is there.
A node has an identity, a role, a label, a value, whether it holds the keyboard, and the actions it accepts right now.
taria removes the inference by asking the app. An agent acts by naming a node and one of its advertised actions, and the app answers that specific input delivered, dropped, or ignored. The price is that none of it exists until the app's author puts it there.
+---------------+ Unix socket +-----------+ MCP over stdio +---------------+ | TUI app | <------------> | taria-mcp | <--------------> | agent harness | | taria-ratatui | (ndjson) | bridge | | (Claude Code) | +---------------+ +-----------+ +---------------+
The app publishes a semantic snapshot of its widget tree over a Unix domain socket, one JSON object per line, on every meaningful change. The bridge holds the latest snapshot and exposes it, plus input back into the app, as MCP tools. The app acknowledges each input by id, so the bridge can tell an input the app acted on from one it never saw.
What an app can say about a widget is a fixed vocabulary: 29 roles and 7 actions, plus a custom action for anything an app names itself. Both vocabularies are open: a peer that meets a role or action it does not know degrades that one field instead of failing the tree, which is what lets either grow inside a frozen format.
| Tool | What it does |
|---|---|
read_tree | The app's current semantic tree as JSON: node ids, roles, labels, values, focus, and the actions each node advertises. |
act | Invokes an advertised action on a node by id. The node and the action are checked against the latest tree before anything is sent. |
type_text | Types a literal string in one call. It goes where the app puts typing, never through the app's key bindings. |
key | A raw key press, the fallback for parts of the UI without semantic coverage. |
Every input is answered by the app itself, not inferred from a repaint: the updated tree, an input the app deliberately ignored with the current tree to re-plan from, or an error for one that was dropped or left unaccounted for.
The app is not yours. taria cannot read htop, vim, psql, or a vendor binary you have no source for. If you cannot patch the app and get your patch shipped, screen level is not a compromise, it is the only thing that works. Use tmux, ht, or one of the PTY MCP servers.
You need the rendered output itself. Colours, box drawing, a progress bar, the exact layout: taria publishes a tree, not cells.
You are driving a shell rather than an app. That is a terminal session, not a widget tree. tmux is good at it.
taria is not a replacement for tmux, and the two are not competing for the same slot. An agent can hold both. The full comparison puts taria beside ht, tmux, agent-tui, tui-use and the PTY MCP servers.
That is the analogy it is built on. A web page exposes an accessibility tree so a screen reader does not have to guess at the pixels, and taria does the same for a TUI. The similarity is the shape of the idea rather than the standard, and taria is not affiliated with the W3C or with ARIA. The vocabulary is taria's own: 29 roles and 7 actions, shaped by a census of 15 real ratatui apps rather than ported from the ARIA role list.
MCP is a convenience. taria-mcp is one client of a plain protocol: ndjson over a Unix domain socket, one JSON object per line. Anything that can open a socket can read snapshots and send input with no MCP in the picture. The specification covers every message.
The adapter that exists today is taria-ratatui, so ratatui is the path with no work in front of it. The protocol itself knows nothing about ratatui or Rust, and the specification is written for someone building an adapter for another framework. Adapters for Bubble Tea, Textual and Ink are wanted and not written.
Five edits: bind a layer in main, write a function that turns your state into nodes, publish it after each draw, drain agent input around the blocking call in your event loop, and acknowledge the inputs you deliberately ignore. It touches neither your rendering nor your state. If the socket cannot be bound the layer is inert and the app runs exactly as it did before, so taria cannot keep your app from starting.
No. The adapter is built on unix-only APIs, and the transport is a Unix domain socket bound through them. Linux is the tested platform and the only one CI runs the suites on. macOS binaries are built but nothing exercises them end to end.
The wire format is frozen at PROTOCOL_VERSION 1, and changes within it are additive: new optional fields, new message variants, new roles, new action names. A peer that meets a role or action it does not know degrades that one field instead of failing the tree. The crates are pre-alpha and their Rust APIs can still move under semantic versioning; the format is the part that made a promise.
The rest of the FAQ, including what happens to the parts of your UI you have not annotated.