Agent output UX
How the TUI presents agent turns as a first-class activity timeline. Theme tokens and spacing live in TUI theme JSON.
Turn anatomy
Live order within a turn (tools appear as /cmd lines are dispatched, after the model stream that emitted them):
user echo strip (inset + vertical pad, input bg)
← one blank row
thinking strip (agent accent + input bg + pad) ThinkingSegment (when provider emits)
markdown body, dimmed… up to 3 wrapped body rows when collapsed
← one blank row
assistant prose / markdown / code / diffs (writ lines swallowed)
← one blank row
○ fetch:https://… ToolSegment appears as dispatch starts
✓ exec:git status ▸ resolves when the result returns
← one blank row
· [interrupted] activity chrome (system lines)
→ delegating: /agent … bold/info routing status (not prose)
› /read #2 verbose writs (› + WritLine)
| Layer | Segment | Notes |
|---|---|---|
| User | ProseSegment + UserEchoText | Full-width strip with input inset + vertical pad |
| Tools | ToolSegment | One row per /cmd; expand for args/result; clustered (no gap between tools) |
| Reasoning | ThinkingSegment | Markdown body on input/echo bg; per-agent left accent from theme palette; dimmed readable text |
| Assistant | Prose / Code / Diff | Same StreamRenderer path as before |
| System | styled activity / delegation lines | Interrupts, advisor, → delegating: |
Quiet default: writ lines stay swallowed (BlockParser covers /read, /browse, /todo, /search, …); tools appear as compact status rows, not raw /fetch dumps. /verbose still streams raw writs with › WritLine styling.
Blocks are separated by exactly one blank row (layout.block_gap, default 1). Trailing soft blanks inside prose are trimmed before the next block, and empty user-echo pad rows already count toward that single gap so blanks never stack. Prose text buffers use newlines as separators (not terminators) so a phantom empty row cannot appear after every segment. The first block in an empty pane also gets a lead-in blank so user echoes don't sit flush against the header.
The mid-separator chrome (thinking indicator / tool status) is three rows tall: one blank pad, the status text, and one blank pad above the input strip.
Tool timeline
execute_agent_commands emits ToolActivityEvent at Started and Finished with process-wide unique ids. The REPL:
- Upserts a
ToolSegmentin scrollback - Bumps the mid-separator spinner count on Finished (armed once per turn)
- Records the Finished event onto the pane agent’s last assistant message as
tool_trace (conversation switch replays that history). When a nested /agent dispatched the tool, the same entry is mirrored onto the child.
Expand/collapse: ^O (same chord as code blocks) when a tool or thinking row is in view, or left-click the block in the scroll region.
Permission cards
Destructive /exec and disk /write confirms render a multi-line card:
permission write src/main.cpp
12 lines, 340 bytes
#include …
…
allow? [y/N]
Keys remain y / n. Declines still return ERR: user declined to the agent.
Reasoning
When Anthropic emits thinking_delta, OpenAI-compat emits reasoning_content / reasoning, or Gemini streams parts with "thought": true (via thinkingConfig.includeThoughts on Gemini 2.5/3; Flash-Lite also sets thinkingBudget), deltas land in a collapsed ThinkingSegment. The body is rendered as markdown on the same background as the user-echo / readline strip, with vertical pad + horizontal inset, dimmed readable text, and a left accent colored from the theme’s per-agent palette (agent_master / agent_palette). Models without a separate reasoning channel keep the header thinking… spinner only — Arbiter does not invent chain-of-thought from ordinary prose. Reasoning is stored on the assistant Message.thinking field (including nested /agent and /parallel deltas appended onto the pane agent’s open turn) and rebuilt on conversation switch.
Replay
transcript_replay rebuilds ThinkingSegments from Message.thinking, runs assistant content through StreamRenderer(kReplay), then rebuilds ToolSegments from each message’s tool_trace on the pane agent’s history (typically index).
Related
- Streaming and turn lifecycle — turn lifecycle, spinners, cancel
- TUI theme JSON —
TuiDesignschema, theme tokens, spacing