feat(cli,tui): issue #12 — presenter contract semantics amendment (v0.2.0)
Replaces the stateless _render_event / _render_event_to_log helpers with stateful per-turn presenters (CliPresenterState / TuiPresenterState). Coalesces thinking-event deltas into a single growing display per run; demotes telemetry events with editorial hierarchy; formats duration + usage for human reading. Headline behavior change: a 50-token thinking phase now renders as ONE coalesced growing line in CLI (or one closed RichLog entry + per-delta live Static widget in TUI), not 50 lines of [thinking] spam. Editorial promotion line (issue #12 INV-002): - Load-bearing (no demotion prefix): Text, Done, Error, Cancelled - Demoted telemetry (`. ` ASCII prefix in CLI; dim `· ` in TUI): WorkerPhase, Thinking, TextBoundary, ToolStart, ToolResult Stateful coalescing: - Thinking deltas accumulate into thinking_buffer; first non-thinking event closes the run with a single \n boundary in CLI / one closed dim RichLog entry in TUI. - TUI adds a dedicated Static(id="thinking-current") widget that shows the last ~200 chars of the active run, mirroring per-delta updates. Two-views-of-thinking decoupling per INV-004: chronological RichLog + always-visible widget. - CLI INV-005: when stdout text was streamed mid-line, text_written_since_newline triggers a stdout flush + \n before the next stderr terminal label — guarantees [done] / [error] / [cancelled] land on their own line in a TTY without breaking pipe-to-file scripted consumers. Formatting helpers (issue #12 INV-006 / INV-007): - _format_duration_ms — autoscale `347ms` / `5.5s` / `1.2m` - _format_usage — natural-language `6756 in -> 126 out (6882 total, 0 cached)` with arrow="->" CLI / "→" TUI Cross-frontier design pass (eitri-smithy-dev, althing 01KSBE52YZR5E3SPTKA672JE43) returned 16-of-16 confirmed decisions + 4 material divergences applied: - ASCII `. ` prefix in CLI (`·` is U+00B7, not ASCII) - RichLog one-closed-entry-per-run + Static per-delta updates (not inline-mirror as initially proposed) - presenter-state object instead of pure-function rendering - Framed as "contract semantics amendment", not "polish" Volva paraphrase round (5 prose-precision fixes applied to 12.contract.md): INV-001 "growing display" semantics; single hide mechanism for the Static widget (Textual reactive `display: bool`); [render_error] security clause (type-only, no exception message); text_written_since_newline `\n`-terminated text corner case; [create_session] integration path (bypasses state.render — not an SSE Event variant). Volva code-review round (5 findings applied): - F1 drift: render-exception fallback now writes BOTH a plain-label fallback line for the original event AND the `[render_error] <type>` line (was missing the fallback half). - F2 drift: dim Rich style applied to all demoted-telemetry RichLog writes via `rich.text.Text(..., style="dim")` (was plain str). - F3 drift: belt-and-braces widget clear+hide on EVERY terminal event (Done/Error/Cancelled), even when thinking_open was False. - F4 precision: _format_usage gains PRE-001 assertion on the four expected usage keys. - F5 precision: _run_turn signature amended in issue #3 contract to document the new `state: CliPresenterState | None = None` test- injection kwarg. [create_session] lifecycle line demoted to `. create_session:` (written directly by _amain; bypasses state.render since it's not a wire-level SSE Event variant). Pre-amendment _render_event / _render_event_to_log and their test classes removed under the no-backwards-compat rule. Issues #3 and #4 contracts amended in-place: #3 (CliPresenterState CLASS + FN block + helper FN blocks + _run_turn signature + _amain create_session demotion); #4 (TuiPresenterState CLASS + FN block + compose Static widget + _stream_turn_worker state construction). 209 tests GREEN; ruff clean. Bumps v0.1.0 → v0.2.0 (minor — output shape change breaks pre-amendment grep patterns like `[thinking] '`; no public API surface change beyond the rendering contract). Persistent-memory commit-along: captures the issue #12 decision, forward direction (require end_user_id for every access — declined worldtree-dev's requires_end_user_id offer because we'll send it universally), and the Heimdall scope-model foot-gun note (the "per-Tier-1-agent scope add" diagnosis was a phantom ask resolved by worldtree-dev's correction; agent.call:* baseline covers all Tier 1).
This commit is contained in:
+61
-42
@@ -32,61 +32,78 @@ separate dev team rather than an in-tree Worldtree tool.
|
||||
|
||||
## Current state / in-flight
|
||||
|
||||
_As of 2026-05-23 (end of day):_
|
||||
_As of 2026-05-23 (end of day, post-#12 implementation, pre-commit):_
|
||||
|
||||
**Status: issues #5 + #6 + worldtree-dev consumer-API follow-up all
|
||||
landed.** Six core issues complete (`sse_client` #1, `sessions` #2,
|
||||
`cli` #3, `tui` #4, `--end-user-id` #5, TUI startup error visibility
|
||||
#6) + robustness fix #7 (MalformedSseData + empty-skip).
|
||||
188/188 tests GREEN; ruff clean.
|
||||
**Status: issue #12 (presenter contract semantics amendment)
|
||||
TDD-complete, in working tree, awaiting commit.** Seven core issues
|
||||
complete (`sse_client` #1, `sessions` #2, `cli` #3, `tui` #4,
|
||||
`--end-user-id` #5, TUI startup error visibility #6, presenter
|
||||
contract semantics amendment #12) + robustness fix #7 (MalformedSseData
|
||||
+ empty-skip). 208/208 tests GREEN; ruff clean. pyproject.toml bumped
|
||||
to v0.2.0; `uv.lock` refreshed. Working tree has 9 modified files +
|
||||
the new `docs/contracts/issues/12.contract.md` (untracked); commit not
|
||||
yet authored.
|
||||
|
||||
`--send` validated end-to-end against personal Worldtree
|
||||
(`http://10.250.50.152:8081`, mimir on qwen3.6-35-a3b). Lofn smoke
|
||||
parked on infra-ops's `agents.call:lofn` scope add (althing thread
|
||||
`01KSBBHDWVZZ…`; infra-ops brokering to worldtree-dev because personal
|
||||
Worldtree exposes no public scope-mutation endpoint).
|
||||
Last commits on `main`:
|
||||
- `8282156` snapshot: persistent-memory Heimdall scope-model foot-gun (post-v0.1.0)
|
||||
- `804c2df` feat(sessions,cli,tui): issues #5 + #6 + worldtree-dev consumer-API follow-up (tagged v0.1.0)
|
||||
|
||||
**In-flight:**
|
||||
- **Lofn smoke** — blocked on the scope-add. Once infra-ops confirms
|
||||
`agents.call:lofn` is live, run `ratatoskr --new --agent lofn
|
||||
--end-user-id ratatoskr-tui --send "hello"` for end-to-end
|
||||
verification.
|
||||
`--send` validated end-to-end against personal Worldtree at v0.1.0
|
||||
(`http://10.250.50.152:8081`, mimir on qwen3.6-35-a3b, 2026-05-23 smoke
|
||||
returned `[done] turn_id=116 duration_ms=5467`). Lofn smoke is
|
||||
**auth-unblocked** as of 2026-05-23 — worldtree-dev confirmed our key
|
||||
(`c990f0be`) already covers Tier 1 agents via the `agent.call:*`
|
||||
baseline policy; the initial "scope-add needed" diagnosis was a phantom
|
||||
ask (see Tried-and-abandoned). The actual lofn fix shipped as issue #5
|
||||
(`--end-user-id` flag).
|
||||
|
||||
**Async cross-frontier activity in flight:**
|
||||
- Issue #12 code-review consult posted to volva 2026-05-23 (althing
|
||||
thread `01KSBH8GYH4G3H03T767X613W7`). Reply pending in inbox.
|
||||
|
||||
**Outstanding operator-side todos:**
|
||||
- **Commit issue #12 work** + tag v0.2.0 + push. 9 modified files +
|
||||
new `12.contract.md` ready.
|
||||
- **Post-v0.2.0 mimir smoke (the visual one)** — `source env.sh && uv
|
||||
run ratatoskr --new --agent mimir --send "test"` to eyeball the new
|
||||
rendering (`. thinking: ...` coalesce, `. worker_phase: ...` demotion,
|
||||
`duration=5.5s` formatting, `usage 6756 in -> 126 out (...)` shape).
|
||||
The v0.1.0 mimir smoke confirmed wire-level backwards compat but
|
||||
did NOT exercise the v0.2.0 rendering.
|
||||
- **Post-v0.2.0 lofn smoke** — `source env.sh && uv run ratatoskr
|
||||
--new --agent lofn --send "hello"` (env.sh ships
|
||||
`RATATOSKR_END_USER_ID="ratatoskr-tui"`). Now unblocked on auth.
|
||||
|
||||
**Pending issues filed but not started:**
|
||||
- **Issue #8 (startup agent picker)** — filed but unscaffolded.
|
||||
Worldtree-dev confirmed `GET /agents` requires no special scope
|
||||
(any authenticated key works); issue is unblocked on auth side.
|
||||
Depends on #5 composably (both thread through `ParsedArgs` →
|
||||
`_resolve_then_run`).
|
||||
`GET /agents` is free to call (worldtree-dev confirmed); auth side
|
||||
is unblocked. Depends on #5 composably (both thread through
|
||||
`ParsedArgs` → `_resolve_then_run`).
|
||||
- **Issue #9 (spec-pin refresh v0.19.0 → v0.22.1)** — filed
|
||||
2026-05-23. Documentation debt; pin lies about the surface we're
|
||||
committed to. Worldtree v0.20.0 made `end_user_id` the partition
|
||||
key; v0.21.0 added `memory_context` field; v0.22.0 strengthened
|
||||
the `[MEMORY:DATA]` envelope. None break our existing surface.
|
||||
2026-05-23. Documentation debt. None of the v0.20.0/v0.21.0/v0.22.0
|
||||
changes break ratatoskr's existing surface; the pin lies about
|
||||
what we've committed to.
|
||||
- **Issue #10 (subject:{type,id} migration)** — filed 2026-05-23 to
|
||||
track Worldtree #196's LOCKED-but-not-shipped breaking change.
|
||||
Worldtree-dev was explicit: don't pre-implement; deprecation
|
||||
warnings will fire per call as the heads-up when substrate ships.
|
||||
Don't pre-implement per worldtree-dev's explicit guidance.
|
||||
- **Issue #11 (AdminEvents pane auth prerequisite)** — filed
|
||||
2026-05-23. Future side-pane requires `admin.events.read` scope;
|
||||
documenting the gate so we don't forget when scheduling that pane.
|
||||
2026-05-23. Future side-pane requires `admin.events.read` scope.
|
||||
|
||||
Branch: `main` (clean after this commit). Remote:
|
||||
Branch: `main` (dirty with #12 work pending commit). Remote:
|
||||
`origin → git@gitea.phasefinal.com:vh/ratatoskr.git`.
|
||||
|
||||
**Next natural moves:**
|
||||
|
||||
1. **Mimir regression smoke (operator-side)** — `source env.sh && uv
|
||||
run ratatoskr --new --agent mimir --send "test"` (and the
|
||||
`--send`-less TUI form) to verify backwards compat holds after
|
||||
issues #5 + #6 land. env.sh now ships
|
||||
`RATATOSKR_END_USER_ID="ratatoskr-tui"`.
|
||||
2. **Lofn smoke** — when infra-ops confirms scope-add.
|
||||
3. **Issue #8 (startup agent picker)** — scaffold + contract, then
|
||||
TDD. Unblocked by both #5 (end_user_id wired through
|
||||
`_resolve_then_run`) and worldtree-dev's auth confirmation for
|
||||
`GET /agents`.
|
||||
4. **Side-pane issues** — Persona pane first (file-tail, cheap).
|
||||
5. **Issue #9 (spec-pin refresh)** — defer until we actually need a
|
||||
v0.20.0+ capability, OR refresh now if doc-debt is bothering us.
|
||||
1. **Triage volva's #12 code-review** when the reply lands in the
|
||||
inbox; apply tactical fixes inline, surface architectural calls.
|
||||
2. **Commit + tag v0.2.0 + push.**
|
||||
3. **Post-v0.2.0 smokes** — mimir (visual), lofn (newly unblocked).
|
||||
4. **Issue #8 (startup agent picker)** — scaffold + contract, then
|
||||
TDD. Composes with the forward end_user_id direction (see Recent
|
||||
decisions).
|
||||
5. **Side-pane issues** — Persona pane first (file-tail, cheap).
|
||||
6. **Issue #9 (spec-pin refresh)** — defer unless we need a v0.20.0+
|
||||
capability (e.g., `memory_context` for Phase 2.1).
|
||||
|
||||
## Recent decisions
|
||||
|
||||
@@ -117,6 +134,8 @@ decision. Captures rationale that won't be obvious from code alone.
|
||||
- `[2026-05-23]` **Issue #5 (`--end-user-id`) implemented via TDD.** Small surface change across three modules (sessions, cli, tui): `create_session(client, agent_id, *, end_user_id=None)` widens with optional kwarg; body conditionally adds the field when non-None (INV-002: omitting != sending empty); PRE-003 asserts non-empty. `ParsedArgs.end_user_id: str | None = None` field; `--end-user-id` CLI flag with non-empty validation (mirrors `--send` check). `_amain` and `_resolve_then_run` thread `end_user_id=args.end_user_id` to their `create_session` calls. Post-#6 adjustment: the contract originally named `on_mount` as the TUI threading site, but #6 had moved session resolution to `_resolve_then_run` — same shape, different function. 7 new tests across the 3 modules.
|
||||
- `[2026-05-23]` **Worldtree-dev consult landed authoritative consumer-API guidance** (althing thread `01KSBARG2B8M8C82H6AJGJWX1B`). Key takeaways shaped follow-on work: (1) `end_user_id` is a free-form partition key for long-term memory + persona/valence state; same value → same partition, different values → fully isolated. For Vuong-debugging-Worldtree the recommended posture is a project-stable default with `--end-user-id` override. (2) No programmatic `requires_end_user_id` discovery on `GET /agents` — "try and react to 422" remains the pattern. (3) Breaking-change #196 LOCKED but not shipped: `subject:{type,id}` replaces `end_user_id` at future v0.22.x or v0.23.0; don't pre-implement. (4) Spec pin (v0.19.0) is 3 minor versions stale (current v0.22.1); none of v0.20.0/v0.21.0/v0.22.0 break ratatoskr's surface but the pin lies about what we're committed to. (5) User-Agent header: send one (`ratatoskr/<version> (vh@phasefinal.com)`). (6) `agents.call:lofn` scope needed for lofn smoke. (7) `GET /agents` requires no special scope; issue #8 unblocked on auth.
|
||||
- `[2026-05-23]` **Follow-up acted on:** User-Agent header added to both `_amain` and `_resolve_then_run` httpx.AsyncClient constructions (with `importlib.metadata` version lookup + fallback to `0.0.0`); `RATATOSKR_END_USER_ID` env-var fallback added to `_parse_args` (resolution: flag > env > None); env.sh ships `RATATOSKR_END_USER_ID="ratatoskr-tui"` as project-stable default. Original issue #5 posture rejected env-var fallback as "papering over isolation"; revised after worldtree-dev's guidance that the realistic single-operator use case wants partition continuity. Issue #5 + #3 contracts amended in-place to document the env-var fallback. Infra-ops pinged via althing for `agents.call:lofn` scope (broker pattern; they forwarded to worldtree-dev). Three Gitea issues filed: #9 (spec-pin refresh), #10 (subject:{type,id} migration tracking), #11 (AdminEvents pane auth prereq).
|
||||
- `[2026-05-23]` **Issue #12 (presenter contract semantics amendment) implemented via TDD.** Headline: thinking deltas render as ONE coalesced growing line (CLI) / one closed RichLog entry per run + live Static(id="thinking-current") widget per-delta (TUI), not 50 lines per turn. Introduced stateful per-turn presenters: `CliPresenterState` (cli.py) and `TuiPresenterState` (tui.py), both `@dataclass(slots=True)` with thinking_buffer + thinking_open (+ text_written_since_newline for CLI). Editorial promotion line settled: load-bearing = Text/Done/Error/Cancelled (no prefix); demoted telemetry = WorkerPhase/Thinking/TextBoundary/ToolStart/ToolResult (CLI `. ` ASCII prefix; TUI `· ` Unicode dim prefix). CLI stdout/stderr newline-boundary INV-005: when text was streamed mid-line, flush a `\n` to stdout before writing terminal labels to stderr; `text_written_since_newline = not event.content.endswith("\n")` per Volva F4 fix. Helpers `_format_duration_ms` (`347ms` / `5.5s` / `1.2m` autoscale) and `_format_usage` (`6756 in -> 126 out (6882 total, 0 cached)` with arrow="->" CLI or "→" TUI). Per Vor (eitri-smithy-dev cross-frontier consult, althing 01KSBE52YZR5) + Volva paraphrase (5 contract-text ambiguities all fixed in #12.contract.md). `[create_session]` lifecycle line demoted to `. create_session:` (written directly by `_amain`, bypasses state.render). Old `_render_event` / `_render_event_to_log` functions and their TestRenderEvent/TestRenderEventToLog classes removed (no-backwards-compat rule). Contracts amended: #3 (CliPresenterState block + `_run_turn` thread state + `_amain` create_session demotion + `_format_*` helper blocks), #4 (TuiPresenterState block + `_stream_turn_worker` state construction + `compose` Static widget addition). 39 new tests; 19 obsolete tests removed; net 208 GREEN. v0.1.0 → v0.2.0 (minor; pre-amendment output shape broken intentionally — scripts grepping `[thinking] '` no longer work; that's the intended cleanup). Cross-frontier design pass with eitri-smithy-dev returned 16-of-16 confirmed decisions + 4 material divergences applied (ASCII `· ` factual fix, RichLog-one-entry-per-run vs inline-mirror, presenter-state object vs stateless, "contract semantics amendment" framing not "polish"). Calibration note: eitri-smithy-dev's value here was *architectural* (state-object pattern + chronological-vs-live decoupling) not just *tactical*; the framing rename alone justified the consult. Volva paraphrase round added 5 prose-precision fixes (INV-001 "growing display" semantics, TUI hide mechanism unification, render_error security/readability tension, newline-tracking corner case, [create_session] integration path).
|
||||
- `[2026-05-23]` **Forward direction: Ratatoskr will require `end_user_id` for EVERY access before too long.** Operator's call. Reasoning: even Tier 1 foundational agents (mimir, all Asgardians) that don't *require* `end_user_id` server-side currently fall back to a `_no_end_user` sentinel substrate partition — effectively pollution from a single-operator-debug-tool's perspective. The right shape is "every conversation has an explicit partition key." `RATATOSKR_END_USER_ID="ratatoskr-tui"` env-default in env.sh is the first step toward that posture; once we've validated the partition-isolation experience, the next move is making `end_user_id` mandatory (probably remove the `None`-default in `_parse_args`, fail-closed with a UsageError if neither flag nor env provides it). Consequence for cross-project asks: declined worldtree-dev's offer to ship `requires_end_user_id: bool` on `AgentInfoResponse` because we'd treat every value as true regardless; the try-and-react-to-422 pattern goes away from our side because we never send a request without the field. File a ratatoskr issue when scheduling the change — touches `_parse_args` validation + `_resolve_then_run` + `_amain` + tests + contract amendments to #3 / #5. Treat as a v0.2.0 minor (breaking: existing `--new --agent mimir` without env or flag would start failing). **Cross-frontier alignment (worldtree-dev ack 2026-05-23, althing 01KSBD9FPMCWJMBXNNS4B3MYBS):** the platform side agrees with this framing — `_no_end_user` is a substrate accommodation for identity-less transports, NOT a consumer model. The fallback's `_is_fallback=True` trap door (#185 INV-185-5/8) "could become operator-controlled later" per worldtree-dev, meaning Worldtree itself may tighten the substrate-fallback path. Ratatoskr's forward posture pre-empts that tightening — moving from "we send end_user_id when set" to "we never send a request without end_user_id" stays consumer-correct regardless of what Worldtree does with the fallback knob.
|
||||
|
||||
_For per-issue TDD implementation notes, Volva findings, and contract amendments, see the git log (commits `9703eb2..61c3941` carry the full per-issue trail with structured commit messages)._
|
||||
|
||||
|
||||
Reference in New Issue
Block a user