Files
rune/docs/runtime-objects.md

4.4 KiB

Runtime Objects — Interstitial Contextual Messaging (P1-07, #30)

Summary

Runtime objects are the observable plan/action/result state of a Rune turn, carried as typed, stable-id data on the operator's response envelope and projected into the TUI's work pane. They are the same events the chat log already narrates; the work pane is the operational index, the log remains the narrative record (two-view principle).

No chain-of-thought ever rides a runtime object: only what was actually admitted, run, observed, verified, or concluded.

Object set (src/rune/tool_protocol.py)

All objects are frozen dataclasses with to_dict() / from_dict() round-trips and ISO-UTC ts. The pre-existing ToolRequest / ToolEvent got the same round-trip surface and are part of the set.

Object id field Meaning
MemoryQuery query_id One context-broker memory-selection pass: admitted ({block_name: tokens}), dropped ([{name, tokens}]), memory_tokens.
Observation observation_id Observable runtime event: kind (tool_result / approval / compaction / degraded / note), one-line summary, bounded detail (<=300 chars).
VerificationResult verification_id Objective verification of a task outcome: method (exit_code / test / diff / manual / none), passed, bounded evidence. Never self-report.
Outcome outcome_id Final classified task outcome: status (completed / failed / superseded / cancelled), verification_id link ("" when none), degraded.

Emission points (src/rune/rune_operator.py)

Inside _execute_on_model's tier loop, on the final response path (before _finalize_runtime_response):

  1. MemoryQuery — one per broker assemble, built from _last_trace (admitted blocks → {name: tokens}, non-admitted blocks → dropped).
  2. Observations — one per broker compaction event (kind=compaction, summary carries the tokens_before -> tokens_after delta) and one per degraded subsystem name (kind=degraded, summary subsystem unavailable: <name>).
  3. VerificationResult + Outcome — emitted when the turn finishes without a pending approval. Method rule: exit_code when a run_command tool event completed successfully, test when run_tests completed, otherwise no VerificationResult is emitted at all (method=none is never claimed). passed = final response success AND no failed tool event. The Outcome's status is back-filled from the final task transition in send_task (completed / failed), and the verification link is set iff the verification passed and the task completed.

Emission is degrade-not-crash: a failure to build runtime objects records a runtime_objects subsystem failure (tier 2) and never fails the turn.

The objects flow to the TUI as dicts via the additive runtime_objects field on OperatorResponse (_response_envelope uses asdict, so the key survives automatically; default [] keeps pre-broker failure paths clean).

Work pane (src/rune/rune_tui.py)

WorkPane(Vertical) is the third column in #channels (id="work-pane", collapsible via Ctrl+3). CSS: .work-pane { width: 1fr; border-left: solid $secondary; }.

  • Data methods are headless-testable: add_object(obj) / update_object(obj) (in-place by id, no duplicates) / object_count(); render() builds the display. Tests exercise the unmounted instance.
  • Sections render newest-first: Outcomes, Verification, Observations, Memory queries, Tool calls. Each line: kind glyph + stable id + one-line summary (+ bounded detail).
  • Tool calls keep their existing ToolCallWidget in the chat log AND get a one-line mirror in the pane (the pane is the operational index; the log keeps the interaction).
  • Visibility policy: hidden on the chat channel, visible on task; Ctrl+3 toggles the expansion. _consume_runtime_objects feeds the pane from every response envelope (including approval resumes), degrade-not-crash.

#33 handoff

Outcome is the OutcomeLedger's future record shape. This stage ships the typed object and its rendering only; ledger wiring (persisting outcomes) is the #33 dependency that adopts this shape. VerificationResult is the Verifier strategy's typed claim; the verifier itself is also #33 — until then the operator derives it from observable tool results (never self-report).