vh d3569904bc refactor(tui): thinking streams into whole pane (v0.6.5)
Operator: "Why does the thinking scroll a little section at the
bottom of the thinking pane instead of scrolling the whole pane?"

Root cause: v0.6.1's thinking-current Static was docked to the
bottom of the Thinking pane and rendered the last 200 chars of
streaming content. As deltas arrived, the displayed 200-char tail
shifted — old text fell off the left, new text appeared on the
right — visually reading as "a little section scrolling at the
bottom" while the larger thinking-log RichLog above showed only
the previous run's closed content (or nothing on first turn).

## Fix: stream directly into thinking-log

The Static is gone. Thinking deltas now write straight to the
`thinking-log` RichLog (one delta = one line in the scrollable
log). The whole pane scrolls naturally as content arrives —
operator can switch to Ctrl+3 and see streaming content fill
the pane top-to-bottom.

Routing pattern:

  First Thinking delta of run:
    → write Rule(title="turn N · thinking #K start") to thinking_log
    → write delta content as a line
    → set thinking_open = True
  Subsequent Thinking deltas:
    → write delta content as a line
  Non-thinking event (closes the run):
    → write Rule(title="turn N · thinking #K end") to thinking_log
    → reset thinking_open

The Rule(start) at the top of an in-progress run is now the
"thinking is happening" indicator. No more separate live-preview
widget required.

## Trade-off: no markdown re-render

Pre-v0.6.5 closed runs got a Markdown(full_content) render between
the start/end Rules. v0.6.5 drops that — the streamed deltas ARE
the content; re-rendering as Markdown would either need to wait
for run-end (no streaming) OR re-render incrementally per delta
(bad UX). Streaming wins for "live observability" framing.

The downside: if model thinking has Markdown structure (lists,
code), it renders as raw text. Acceptable per operator's "stream
in line" framing.

## Removed widgets

- `Static#thinking-current` (right column / Thinking pane bottom)
- `TuiPresenterState.render` no longer takes a `thinking_widget` param
- `TuiPresenterState.thinking_buffer` field dropped (no accumulation)
- `_stream_turn_worker` no longer queries `#thinking-current`
- `on_mount` no longer hides `#thinking-current`
- DEFAULT_CSS `#thinking-current` block removed

## Contract amendment

INV-022 amended: thinking now streams as raw delta lines, not
Markdown-rendered on close. INV-024 amended: thinking-current
Static removed entirely (was relocated v0.6.1, removed v0.6.5).
Drift-check clean.

## Tests

238/238 GREEN (was 241 — 3 obsolete widget tests deleted:
test_thinking_widget_truncation, test_thinking_widget_visibility_lifecycle,
test_terminal_events_belt_and_braces_widget_cleanup). 5 routing tests
rewritten for the new streaming shape (test_thinking_streams_into_thinking_log,
test_thinking_closes_to_thinking_log, test_multiple_thinking_runs_...,
test_render_exception_fallback, test_cancelled_mid_thinking_closes,
test_left_column_content_only).

ruff clean. Manual injection test confirms routing: Rule(start) +
delta lines write to thinking_log; transcript untouched.

Patch bump (v0.6.4 → v0.6.5) — internal restructure within Thinking
pane; presenter signature narrowed; no caller-visible public API
change (RatatoskrApp + AgentPickerApp surfaces identical).
2026-05-24 19:00:58 -07:00

Ratatoskr

A Worldtree Conversation API debug TUI. Runs up and down Worldtree's API surface — sessions, turns, persona, tools, admin events, Bifrost state — carrying messages between layers. Like the squirrel.

The product is the observability surface; chat is the input mechanism. Devs run Ratatoskr against a local Worldtree to watch a turn flow through every layer of the system, side-by-side, in one terminal.

Status

v0 scaffold. Design locked; implementation hasn't started. The dev team owns the implementation pass.

Read in this order

  1. docs/design-brief.md — the locked design. Read this first. Every architectural decision is recorded with its rationale, the alternatives considered, and (where relevant) the operator's lock-in moment.
  2. docs/SPEC-PIN.md — what Worldtree spec version Ratatoskr is built against, where the vendored snapshot lives, and how to bump the pin.
  3. docs/conversation-api-spec.md — the vendored Worldtree spec snapshot. Read this to understand the API surface Ratatoskr consumes. Do not import anything from a Worldtree checkout — the boundary is the spec, not the code. See docs/design-brief.md §2.
  4. CLAUDE.md — Claude Code conventions for this repo (mostly inherited from the Corviduo template).
  5. persistent-memory.md — durable intent across context resets. Update as decisions and state evolve.

Quickstart

# 1. Environment
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"

# 2. Verify the spec pin
cat docs/SPEC-PIN.md   # documented Worldtree SHA + bump procedure

# 3. Tests (none yet; scaffold only)
uv run pytest

# 4. Run against a local Worldtree (once implementation lands)
# Worldtree must be running:  python -m core.conversation_api
ratatoskr --agent mimir

What this repo is NOT

  • NOT a polished consumer for Worldtree's end-users — that's the web app.
  • NOT a Worldtree admin tool — admin CLI is separate (sessions_cli.py lives in Worldtree).
  • NOT a featuretracking shadow of the web app — when the Conversation API surface grows, Ratatoskr does not necessarily grow with it.
  • NOT a remote-Worldtree debug client — file-tail surfaces (persona log, server log) assume local-dev posture.

The full negative-clause list lives in docs/design-brief.md §6.

Boundary rule

Ratatoskr depends on three things only:

  • httpx + httpx-sse (network layer)
  • textual (TUI framework)
  • Worldtree's published Conversation API spec at the pinned SHA

Hard rule: no imports from a Worldtree checkout. No core.* imports, no from worldtree.*, no shared models, no submodule of Worldtree. The spec is the entire surface. Boundary smoke test at tests/test_no_worldtree_imports.py enforces this in CI.

Version-skew strategy

The dev team is decoupled from Worldtree's dev team. To detect drift when Worldtree changes the API:

  1. Spec-version pin in pyproject.toml (worldtree-spec-rev). Bump explicitly; bumps are a tracked action.
  2. Recorded-SSE snapshot tests at tests/snapshots/. Captured against a live Worldtree; replayed in CI. Re-record after every pin bump.
  3. Conformance smoke test — boots Worldtree via Docker compose in CI, runs a one-turn happy path. Catches integration-level drift.

See docs/SPEC-PIN.md for the bump procedure.

Consumer-side discoveries

If you find a spec gap, ambiguity, or missing-but-needed endpoint while working on Ratatoskr: route the discovery back to worldtree-dev via althing rather than via PR on Worldtree directly. The separate-team boundary is intentional and helps catch spec gaps that an in-tree consumer would paper over.

  • Worldtree — the API Ratatoskr consumes
  • brokkr-smithy — authored this design brief
  • Skaldsong — another Worldtree API consumer (Python; render-and-aggregate pattern)
  • mead-hall — another Worldtree API consumer (TypeScript; server-side broker)
S
Description
No description provided
Readme 20 MiB
Languages
Python 82%
HTML 17.6%
Shell 0.4%