--- name: gaia-tui-manual description: The GAIA terminal UI (gaia-tui) — how to build it, launch it with a real pty, drive it through the loopback control API, choose a local or cloud model provider, and read its logs. Use when running, testing, debugging, or documenting the TUI, when a preflight row blocks a launch, when driving the TUI from a script or assistant, or when a TUI question would otherwise be answered by guessing at the Go source. --- # GAIA Terminal UI Two manuals carry the detail. **Read the matching one before touching the TUI** — both are written from verified runs, not from reading the source. - **[User manual](../../../docs/guides/terminal-hub.mdx)** (`docs/guides/terminal-hub.mdx`) — install, the setup gate and what each row means, choosing where inference runs, day-to-day use, the permission prompt, troubleshooting. - **[Developer manual](../../../docs/reference/tui-dev.mdx)** (`docs/reference/tui-dev.mdx`) — the three-process architecture, building, the full control-API reference, pty allocation, logs and traces, and running a local model on a dev box. `.claude/skills/driving-the-tui/` covers the same control API from the assistant's side; the developer manual is the reference it defers to for endpoint shapes. ## The five things that waste the most time Each of these cost a real debugging session. They are in the manuals too — this is the short list worth carrying in your head. 1. **The agent subprocess outlives your edits.** `gaia-tui` spawns `gaia-agent` once. Change Python under `src/gaia/` or `hub/agents/` and the running session keeps executing the old code — a before/after measurement taken without a restart is two measurements of the same build. Restart the TUI. 2. **`keys` takes an array.** `{"keys": ["p"]}`. A bare string is rejected with `cannot unmarshal string into Go struct field keysRequest.keys of type []string`. `text` types but does not submit — follow with `["enter"]`. 3. **It needs a real pty.** `script` fails on a socket (`tcgetattr/ioctl: Operation not supported`), and driving Terminal.app via `osascript` can time out with `-1712` and leave nothing running. Allocate the pty yourself with `pty.fork()`, size it with `TIOCSWINSZ`, and drain the fd or the child blocks. Call `resize` first in any drive script. 4. **Wait, never sleep** — `POST /control/v1/wait` blocks server-side on `contains` / `absent` / `state`. But `{"state":{"streaming":false}}` can match the instant *before* a turn starts; confirm streaming went true first, or poll `status`. 5. **The "Language model" preflight row includes the embedder.** A machine with a working chat model still blocks on the ~300 MB `user.embeddinggemma-300m-GGUF`, while the row says "Several GB". `gaia init --check` names the exact missing id. ## Before reporting a TUI bug - Capture the **whole** screen. A cropped capture once produced a fabricated "uninstall silently does nothing" — the status line is the second-to-last row. - Check `view` in `status` before sending keys; keys sent to the wrong screen do nothing and read as a broken binding. - Check the `TOOL_LOADER` lines in `~/.gaia/logs/gaia-agent.log` before believing the agent when it says a capability "isn't available" — it may simply not have been selected for that turn. - Say which branch you built. Stacked branches are siblings; a leaf build shows behaviour already fixed elsewhere.