sonny with no subcommand, or sonny run, opens the interactive terminal UI. This page walks through each region of that screen and explains what it is telling you. It is deliberately honest about where its illustrations come from — read the note at the bottom before you treat any of them as a screenshot.

The startup banner

The first thing on your terminal is the wordmark and the version line, followed on stderr by any posture warnings (an armed dangerous mode, an isolation result that did not satisfy its minimum). The banner is the source of the sentences the sidebar repeats later, so if you dismissed a warning at startup the sidebar is where you get it back.

The Sonny ASCII wordmark, rendered from the ascii.txt file in the sonny repository
Figure:Rendered from ascii.txt at commit 9726c080d8 (v0.12.0-818-g9726c080d8), not from a live run

The version string in the banner carries the same descriptor sonny --version prints, so it is the fastest way to confirm which build you are actually talking to before you file a bug against it.

The chat pane

The chat pane owns the transcript and the input box. The transcript is a turn-ordered log of what you said, what the model said, and what every tool call did — and it is the surface where Sonny is most insistent that what is drawn matches what was measured.

Each case below is a frame from the UI’s characterization fixture — the golden file the layout tests compare every render against.

Frames from the Sonny TUI transcript covering submit, streaming, replacement, and turn completion, typeset from the chat_update_characterization.golden test fixture
Figure:Typeset from cmd/sonny/ui/testdata/chat_update_characterization.golden, cases 007/010/011/014/016

The transcript behaviour those frames pin down is worth knowing before you trust a long session:

  • Your submission appears immediately. The you ▸ row is written as part of the submit, not after the first model token arrives, so a slow provider never looks like a dropped message.
  • A streamed replacement replaces. When the provider sends a full-text update rather than a delta, the existing assistant row is rewritten in place instead of appended to. You never see the same sentence twice.
  • A completed turn is sealed. Once the turn-done event lands, the assistant row stops accepting deltas. Late text starts a new row rather than quietly editing an answer you already read.

Above the input box sits a recipe pill showing the active recipe and version, the resolved profile (or routing… while that resolution is still in flight), and provider://model. A failed operation is kept in its own panel immediately above the input, wrapped so the whole message stays readable, and it clears when you dismiss it, send another message, clear the chat, or the operation succeeds.

The sidebar

The sidebar only renders when the terminal is at least 100 columns wide, and it is fixed at 36 columns. Narrow the window below 100 columns and the chat pane takes the whole screen — nothing is hidden behind a scroll, the sidebar simply is not drawn.

From top to bottom it stacks, and each section renders only if it has something to show:

SectionHeaderWhat it reports
Logo + status—○ idle, ● <turn phase>, ✓ done, or ! <failure>
Goal—Only a user-set goal (/goal or --goal); recipe defaults stay hidden
Pills—Provider, model, context; a locked: … row while pinned
LSP▼ LSP (N)One row per language server, with diagnostic tallies
MCP▼ MCP (N)State, name, ×N call count, ⇢ when proxied, transport
Skills▼ Skills (N)Active skills first
Dangerous modes▼ Dangerous modes (N)Only when something is armed or refused
Isolation▼ Isolation <status>Never hidden — an unknown posture says so
Sub-agents▼ Sub-agents (N)Worker tree with pack: and model:
Sessions▼ Sessions (N)Local ▢ / remote ⇄, active tab, cwd, posture
Mesh▼ Mesh (N nodes)★ self / ● peer, state, last-seen Ns
Changed files▼ Changed files (N)+12 -3 path/to/file.go
Tools▼ Tools (N)exec ×17, most-used first
Efficiency▼ Efficiency (N)The tool-efficiency block, see below
Capabilities▼ Capabilities (N)id state detail ×N, actionable first

Alt+C flips every section at once; /sidebar flips one. When the stack is taller than the window the sidebar collapses sections itself, least important first, and a section you opened by hand is left alone.

Dangerous modes

This is the section you most want to stay empty. It appears only when auto-approve is on, the isolation bypass is in force, or a request for one of them was refused by policy. A quiet session renders nothing here, because a permanent auto-approve: off row is exactly the line an eye learns to skip.

The Sonny sidebar dangerous-modes disclosure and its persistent notice, typeset from the TUI reference documentation
Figure:Typeset from docs/user/tui-reference.md at commit 9726c080d8, not from a live run

Three details make this section trustworthy rather than decorative:

  1. The two switches are never merged. Auto-approve and the isolation bypass are independently granted, independently revocable, and independently audited, so they get separate rows with their own origin (flag, env, config, session_prompt, slash_command).
  2. A refusal is marked differently. A policy refusal renders with ✗, not ⚠, in the warning colour. A refusal is policy working; a ⚠ always means something dangerous is genuinely armed.
  3. The notice cannot be overwritten. Unlike ordinary status text, the armed notice persists as long as the posture does.

Every auto-approved call also writes one muted, greppable transcript line:

auto-approved · exec · exec ls -la
auto-approved · list_directory

auto-approved · is the prefix to grep for. It marks calls whose proof source is auto_approve specifically — a call admitted by policy or by a persisted grant is a different fact and is not tagged here.

The auto-approved tool-call transcript lines as documented in the Sonny TUI reference
Figure:Typeset from docs/user/tui-reference.md — auto-approved tool call lines

Isolation posture

Isolation is the one section that never hides. A session with no posture information says ? unknown with the body posture not reported, because a silent sidebar would imply isolation is fine by omission. The status sits on the header line, so even a collapsed ▶ Isolation ✓ verified answers the question.

HeaderMeaning
? unknownNothing was reported
○ offNo isolation was requested
! UNISOLATEDRequested, but running unconfined
! POLICY VIOLATIONAchieved assurance is below the requested minimum
✓ verifiedEvidence produced, checked, fresh, and sufficient
◐ provisioningThe realm is still coming up
⚠ staleEvidence older than its freshness budget
⚠ degradedRunning, but not fully healthy
✗ failed / ✗ unavailableBackend failed / no backend available
? not verifiedA claim with no accepted evidence behind it

Evidence age is shown relatively (45s ago, (max 5m)) rather than as a timestamp precisely so it ticks: staleness becomes visible without arithmetic.

The efficiency block

▼ Efficiency (N) carries the tool-efficiency runtime status — the same projection sonny tools efficiency prints. The rows arrive already rendered by that projection, and the sidebar formats them without computing, reordering, or rewriting any of them, so the two surfaces cannot drift into different answers about the same turn.

The Sonny tool-efficiency report block, typeset from the TUI reference documentation
Figure:Typeset from docs/user/tui-reference.md at commit 9726c080d8, not from a live run

Two rules govern how it renders, and both exist to stop a display cap being mistaken for a saving:

  • renderer-preview is a display cap, not a context budget. The display row’s preview ceilings bound what is drawn on your screen after admission; they change no model input. The row says so in its own applies-to=transcript_display_after_admission token.
  • unknown is never zero. A count nobody measured renders as unknown. “This turn recorded none” and “nothing at this surface can see them” are different facts, and the block refuses to collapse them.

A row whose widest token cannot fit 36 columns is dropped whole and disclosed as +N rows too wide (sonny tools efficiency --json) — drawn directly under the header, above the content it accounts for, because the sidebar truncates trailing lines. An ellipsis through a measured value would still read like a measurement and would not be one.

Where to go next

Provenance of the images on this page

None of these images is a screenshot of a live sonny session. The TUI cannot be driven headlessly, so each one is typeset from text that already exists in the sonny repository:

ImageSource
sonny-banner.pngascii.txt
sonny-tui-chat.pngcmd/sonny/ui/testdata/chat_update_characterization.golden, cases 007, 010, 011, 014, 016
sonny-tui-dangerous.pngdocs/user/tui-reference.md — dangerous-modes section
sonny-tui-sidebar.pngdocs/user/tui-reference.md — auto-approved call lines
sonny-tool-efficiency.pngdocs/user/tui-reference.md — the /tool-efficiency status block

Sources are at commit 9726c080d8 (v0.12.0-818-g9726c080d8). The renderer reads those files, reproduces their lines verbatim, lays them out as a terminal window in the Sonny palette, and rasterises them with headless Chromium at 2× device scale. The per-file record lives in static/images/screenshots/PROVENANCE.md. They show you what the layout looks like and what the text says; they do not demonstrate that a live binary rendered that frame at that moment.