Skip to content

The agent console (TUI)

The Limestone agent runs in a terminal-style console in the bottom dock (ADR-0100). It opens on the [[limestone]] banner and renders a turn as structured output:

  • > your prompt — the turn you sent.
  • ⏺ answer — the agent's answer.
  • tool-use blocks — ⏺ tool(args) then ⎿ output (pretty-printed structured output).
  • thinking — the agent's intermediate reasoning, dimmed and collapsible.
  • a status line with a braille spinner while a turn runs (thinking… / running {tool}…).

While the model streams, its text types out live with a blinking cursor, and Stop interrupts the turn.

It's a theme-aware terminal (mono + the --ground surface + hairline frames), so it follows light/dark like the rest of the app — not a hardcoded dark panel.

Testing streaming + interrupt

Token streaming (B3) and the interrupt (B4) have both automated and manual coverage, because the test fake agent returns whole messages near-instantly (no token deltas, no long turn to cancel mid-flight):

Automated (CI):

  • Streaming — tests/unit/agent/test_service.py::test_run_turn_streams_content_deltas scripts a smolagents stream (content deltas → a final) and asserts run_turn emits assistant_delta + final. Frontend: AgentChat.test.tsx asserts deltas merge into one live message.
  • Interrupt — test_interrupt_stops_the_turn_cleanly interrupts mid-turn via a fake model that calls interrupt() after step 1, asserting a clean stop (no answer, no error). test_agent_ws.py::test_chat_survives_an_interrupt_frame proves the socket survives an {"interrupt": true} frame and stays usable. Frontend: Stop sends a real interrupt frame.

Manual (needs a real model — configure a provider first):

  1. make desktop-dev (or the web dev server), open the agent dock.
  2. Ask something substantive ("summarize my Experiments collection and suggest a next step"). Streaming: watch the reasoning text type out token-by-token with the cursor. Interrupt: on a long turn, click Stop — it should halt at the next step boundary and return to an idle prompt, socket still live; send another message to confirm the session continues.

If a provider's model has no generate_stream, streaming falls back to whole-step text (correct, just no cursor) — that's expected.