98 Commits

Author SHA1 Message Date
vh 22e7a1b0e7 memory: snapshot — person-prime scan CODE LANDED (v0.20.11/12); deploy blocked on operator :8392 restart
scan verb + sortable_chunk_fields cap committed (8fc757a) + Sindra holodesk
first-message preset (a9c521a); TDD 7/7 + full suite 638 green; heid-bug-hunt
panel dispatched (thread 01KXK5XTYHV8TGEDRAZV8GRXWC). DEPLOY (restart :8392 so
the handshake advertises the cap) is operator-gated — classifier denied
bouncing the running provider. worldtree-dev turn-1 verify gated on the deploy.
2026-07-15 08:23:28 -07:00
vh 19e5182228 memory: snapshot — WT #355 validation CONFIRMED (STICK+trigger fixed, b61); stores reset + memprobe deleted for Sindra run 2026-07-13 14:30:37 -07:00
vh 5f321b968a chore(canonicals): vendor brokkr R34 psych-profile canon; re-sync R32-1B doc drift
Vendor the R34/R35 psych-profile reference (Vuong-directed via brokkr) as two
pinned canonicals under docs/vendor/brokkr-r34-psych-profile/:
- brokkr-psych-profile-authoring-spec-v1 (governs on conflict)
- brokkr-psych-profile-parameters-v1 (builder-facing distillation)
Both canonical_source=brokkr-smithy, tolerate_drift; drift-clean.

Re-sync the two tolerate_drift worldtree prose pins (affect-egress-consumer-
reference, conversation-api-spec): the drift was a benign 2-line R32-1B note
(unbounded-z PAD range) documenting a change already adopted in v0.20.9, not
the anticipated we-framing conditional. All canonicals now drift-clean.

Snapshot persistent-memory.md for the execution arc: P06 memory-half driven
(308/308 clean) + scored by brokkr (R35.45) — the authored psychological_profile
is the validated mechanism for memory-salience divergence (authored 0.618 vs
stripped 0.235 null, delta +0.382); memory extraction now reasoning-off; WT #355
root-caused via ratatoskr telemetry.

No version bump: docs/vendoring + memory-snapshot only, no runtime code change.
2026-07-13 07:02:22 -07:00
vh 5e28919b39 memory: snapshot — R34/R35 P06 eval greenlit + designed; bifrost 1.1.1 adopted 2026-07-12 08:38:21 -07:00
vh 8468c471e8 memory: snapshot — bifrost frozen-v0.6 handshake regression + b47/v0.7 resolution
Capture this cycle's arc: the bound-handshake blocker (bifrost 1.1.0 emits
sortable_chunk_fields on a v0.6 wire → v0.6 additionalProperties:false
rejects it), fixed by Worldtree adopting wire-v0.7 in b47 with zero change
our side (keeping 1.1.0 was load-bearing). R34-v1 affect.emit verify green
(dominant_emotion non-null, affect.full grant verified). Sindra on Deckard
live-confirmed. Corrected the stale OpenAPI pin (2.2.0 -> 2.3.0, drift-clean)
+ noted the two tolerate_drift canons WARN pending a coordinated re-vendor.
Clean checkpoint, nothing in flight.
2026-07-10 12:19:32 -07:00
vh 709d2e4498 memory: snapshot — sindra → character-rp (Deckard reasoning seat, interim)
Operator moved sindra from character (→Magidonia non-reasoning) to
character-rp (→Deckard-PKD reasoning) via non-destructive PATCH — her
stateful RP mechanics benefit from reasoning; interim "until a GM-type
agent is on-board." Persona + memory preserved. worldtree-dev's b44
seat re-point noted (transparent, no re-vendor).
2026-07-09 14:35:35 -07:00
vh 0441e319f6 feat(web): auto-scale PAD gauges for R32-1B unbounded-z (v0.20.9)
Sindra "full and unbounded": relax the debug affect console's PAD display
from a hard [-1,1] clamp to auto-scaling on the session's own max |PAD|
(padScale floor 1.0 → padFillFrac faders + _padNorm orbit). An unbounded-z
PAD (Worldtree R32-1B, ~±10) now renders at full range and never pegs or
escapes the frame; today's [-1,1] values are unchanged (scale==1); the
exact value is always shown numerically (unclamped).

Purely a debug-surface change — verified (grep, whole codebase) the only
PAD clamps lived in the web display layer: the affect store is conduit-
opaque, the read route + proxy pass verbatim, and --set-persona-pad writes
unclamped. Ratatoskr is a downstream observer, so this has zero consequence
to any agent's real affect or behavior (Worldtree-computed server-side).
Playwright-verified: z=±6.2 → faders ≤ half-bar, orbit in-box, +6.20
readout, zero regression at scale 1. Proactive R32-1B prep.
2026-07-08 16:16:29 -07:00
vh 48d51023f2 memory: snapshot — sindra memory fixed + bifrost 1.1.0 adopted
Capture two durable shifts from this session: (1) ratatoskr:sindra
DELETE+redefined WITH memory:{} (operator-approved) — she now recalls
(0.988) AND promotes end-to-end, 0/0 mystery closed; (2) bifrost pin
1.0.0 -> 1.1.0 / wire v0.7 (additive, reference-impl currency).
2026-07-07 10:26:15 -07:00
vh e5ec63967e feat(web): memory viewer + design iteration-3 + markdown pass-2 (v0.20.7)
Web-UI iteration-3 — three queued items landed together.

(A) Design iteration-3 into static/index.html:
- sparkline grid background (<pattern id=sparkGrid> + bg rect behind each
  relation-row spark polyline);
- PAD strips → per-turn Δ bars: replace the vertical polyline strip
  (stripPoints/proj3 removed) with padDeltas→deltaStrip, a 12-cell HTML
  column of diverging bars (newest at bottom, magnitude→width, age→opacity);
- mood orbit → dimetric open box (viewBox 124×140, az35/el25,
  D-right/A-left-back/P-up): ghost A×P wall + D×A floor +
  orbitProj/orbitShadowY/orbitWallPt/orbitAxisPt, JS-driven animated replay
  (orbitFrame per rAF via a singleton startOrbitAnim reading live ORBIT_HIST;
  reduced-motion → static final-state).

(B) Memory viewer — a non-bifrost debug read on OUR own store, mirroring the
    #18-D2 affect read:
- provider: RatatoskrMemoryStore.list_chunks + count_chunks + shared
  add_memory_read_route (GET /memory/chunks?agent_id=&end_user_id=), wired
  into build_memory_provider_app + the combined :8392 provider. end_user
  strict, agent_id lenient (an {end_user}-only chunk stays visible);
  {chunks,count,total}, empty match = 200 (not 404);
- web: _memory_chunks_endpoint (GET /api/memory/chunks) supplies end_user_id
  server-side, forwards the browser agent_id, proxies to memory_read_url;
  create_app gains memory_read_url, entrypoint reads RATATOSKR_MEMORY_READ_URL;
- pane: loadMemory/renderMemory/setMemHead — a live-polling MEMORY console
  pane (content·scope·origin·revision per chunk; count/total distinguish
  empty-store from scope-mismatch), polled on open + post-turn.

(C) Markdown pass-2 in markdownSafe: GFM pipe tables (mdTable, alignment
    colons), indentation-nested lists (child list inside the open <li>),
    ordered-list start=N numbering, streaming robustness (partial fence →
    code block; header-without-delimiter → paragraph until it streams in).
    esc-first → INV-004 held.

Contract web_debug_surface.contract.md amended in-commit (create_app
memory_read_url; dimetric-orbit + Δ-bar renderConsole POST-002; memory-viewer
+ markdown-pass-2 function contracts). 631 tests green; Playwright-verified
all render paths (dark+light).
2026-07-07 02:11:29 -07:00
vh 24644ab90e memory: snapshot — queue web-UI iteration-3 (design re-import) + memory viewer + markdown pass-2 for fresh context 2026-07-07 01:28:04 -07:00
vh c988f273b1 feat(web): markdown RP semantic coloring + paragraph reflow (v0.20.6)
Markdown rework, pass 1 (operator-directed):

- "quoted" dialogue → speech (bright --md-speech), *asterisk* → action/narration
  (muted-italic --md-action). Two tunable CSS vars that cascade through --fg-* so
  they auto-adapt to the light theme; plain text stays default narration.
- speech-wrap runs BEFORE the em/link passes so a generated class="…"/href="…"
  quote can't be mis-read as dialogue; straight + smart quotes; apostrophes are
  safe; unbalanced/half-streamed quotes stay uncolored until they close.
- fix the ugliest existing bug: single newlines were hard <br>s → now CommonMark
  soft-breaks (space); a hard break needs 2+ trailing spaces or a trailing \.
- INV-004 escape-first preserved.

Verified: pytest tests/test_web_* (84) + a 10-case Playwright unit-check of
markdownSafe (speech / action / attribute-quote trap / apostrophes / mixed /
reflow / hard-break / html-escaping / unbalanced / paragraphs) + a visual render.
2026-07-07 01:03:45 -07:00
vh 7704959f48 feat(web): tooltips on the affect-derived cells (v0.20.5)
Native title hints on all 6 affect-derived cells (samples / updated / baseline
P·A / drift Δv / volatility) — each explains what it measures and its Tier-1 vs
Tier-3 availability. Completes the mouseover-hint pass (PAD faders + relation
rows already had them).

Verified: pytest tests/test_web_* (84) + node Playwright (all 6 cells titled).
2026-07-07 00:37:36 -07:00
vh 459e7fa602 feat(web): SVG sparklines + 3D isometric mood cube (v0.20.4)
Import the updated Claude Design prototype's SVG sparkline system + 3D graph,
replacing the unicode-char sparklines:

- per-PAD-fader vertical SVG strips (stripPoints, 26x132 beside each bar — time
  down Y newest-at-bottom, value on X, gradient-faded, dot at newest); also
  answers the earlier "next to each meter" ask
- relation-row horizontal SVG sparklines (sparkPointsH, 56x13, auto-scaled,
  gradient + end dot) — fixed-width, so the old unicode overflow onto the n
  column can't recur
- mood-orbit reworked from a 2D P×A scatter into a 3D ISOMETRIC P×A×D cube
  (proj3: P right-down / A left-down / D up, 2:1 iso, scale 26, reverse-derived
  from the design's now-point + verified) with the trajectory, a pulsing
  now-marker, and a drop line + floor-shadow ellipse for depth
- gradients in one hidden <defs> svg; removed the orphaned sparkline()/_SPARK

Contract amended. Verified: pytest tests/test_web_* (84) + node Playwright
(injected 24-sample history — 3 PAD strips + 4 relation sparklines + the 3D
cube trajectory/drop/floor all render; gradients resolve).
2026-07-07 00:29:18 -07:00
vh cc6e85cd9b feat(web): brand mark + favicon → the aurora squirrel, replacing the ᛯ rune (v0.20.3)
- remove the black background from the operator's squirrel art (ImageMagick corner
  flood-fill, fuzz 20% — keeps interior linework + glow, drops connected background),
  downscale 1024->80px + quantize, inline as one SQUIRREL data-URI const
- wire it to the favicon <link> href + both brand marks (rail brand-row + setup h1);
  .brand-glyph (font rune) -> .brand-mark (img, drop-shadow glow + breathe)

Also banks worldtree-dev's exact 3-gate we-framing rule (b24) in persistent-memory —
canonDirective held as-is until it deploys on personal (would otherwise drift from the
currently-deployed renderer).

Verified: pytest tests/test_web_* (84) + node Playwright (favicon + both brand marks
decode from the data-URI; no leftover rune).
2026-07-06 23:24:06 -07:00
vh 0b1d9e2b15 feat(web): context-injection panel — reconstruct the full hidden affect block (v0.20.2)
The affect console now reconstructs + displays the complete affect-context block
Worldtree assembles into the agent's system prompt — never on any wire, hidden from
regular consumers, surfaced here as the reference-impl's privileged dev view.

- extend build_persona_canon.py to emit mood_directive {occ_directives (15),
  pad_band_fallback, salience, pad_band_cutoff, full_only} into the browser canon
  (strings were already in the pinned d2-mood-render-canon; regen via Worldtree loader)
- canonPadFallback(pad) + canonEmotionDirective(type): byte-exact mirrors of Worldtree
  core/persona/renderer._pad_band_fallback + derive_directive
- renderDirective -> a "CONTEXT INJECTION · reconstructed · hidden from consumers" panel:
  mood descriptor [exact] + mood directive [candidate] + relationship directive [exact]
- honest-partial (affect-egress-reference sec 3): affect.emit is type-only (no
  intensity), so the salience gate can't be evaluated -> show BOTH the OCC emotion
  directive AND the PAD-band fallback with the "injected if intensity >= 0.2" caveat,
  never asserting which fires; fallback alone is exact when no dominant_emotion
- vendor + pin affect-egress-consumer-reference.md (tolerate_drift; worldtree-dev
  co-signs + pings on change). drift 6/6 green
- contract amended for the new reconstruction fns + honest-partial provenance

Verified: pytest tests/test_web_* (84) + node Playwright (sindra dominant_emotion=joy
-> joy OCC directive candidate + PAD-band fallback both render with exact/candidate tags).
2026-07-06 23:12:16 -07:00
vh 10acaec33d fix(web): relations sparkline no longer overflows the n column + PAD/relation tooltips (v0.20.1)
- cap the unicode sparkline (relations last-8, faders last-7) + overflow:hidden
  so it can't grow one-char-per-sample and paint over the evidence-count (n)
  column — the "n sitting behind the graph" bug
- add native title mouseover hints on all 3 PAD faders (P/A/D meaning + range)
  and every relationship metric row (warmth / trust·ability / benevolence /
  integrity / agency)
- state.lastSnap lets the console re-render without a refetch

Verified: pytest tests/test_web_* (84) + node Playwright injected a 24-sample
history (sparkline->n bounding-box overlap = 0; sparklines capped; tooltips present).
2026-07-06 22:12:22 -07:00
vh 1fcb17730e feat(web): Claude Design console — 3-column wire monitor (v0.20.0)
Adapt the Claude Design "Ratatoskr Console" prototype into the web SPA:
translate out of the .dc.html dialect (x-dc / sc-if / sc-for / {{}} /
DCLogic / external _ds CSS) into single-file / no-CDN / vanilla, and wire
all real /api/* fetch + SSE into its DOM. New 3-column command-console
replaces the tabbed telemetry layout; endpoint set + SSE vocab unchanged.

- left engine-ticker rail: DEBUG + ADMIN + tool/turn-lifecycle merged into
  one timeline (tickerAdd); tools-armed chips; full-detail Bifrost rail pane
  (endpoint / connected / consumer / caps / tools)
- center conversation: per-turn INLINE chain-of-thought
- right resizable affect console: dominant / canonical-mood centerpiece;
  bipolar PAD faders EACH with a turn-to-turn delta + sparkline; P×A mood
  orbit; relations metric rows; canonical directive
- light / dark theme toggle (dark default; full token override —
  surfaces + fg + borders + accent-as-text)
- inlined data-URI favicon (downscaled 1024->64px), kills /favicon.ico 404
- ticker spine re-anchored to a content-height wrapper (was scrolling out of
  view on auto-scroll)
- honest-shape (INV-001): dominant-emotion shows a real OCC emotion (Tier-1)
  or the canonical mood word (Tier-3), never a fabricated one; affect-derived
  grid drops non-emitted metrics (intensity / decay-tau)

All server routes unchanged. web_debug_surface.contract.md amended for the
presenter renames (renderBifrostState->renderBifrost, renderAffectPane->
renderConsole, setPersonaStrip removed).

Verified: pytest tests/test_web_* (84 passed) + node Playwright end-to-end
against personal :8081 (session open, Sindra seeded greeting, live turn SSE,
affect console + relations + bifrost detail, theme toggle, PAD deltas,
no favicon 404).
2026-07-06 21:50:22 -07:00
vh 263ec2917b fix: render the seeded first-message on the web UI (v0.19.9)
The #347 auto-seed worked (the greeting was in the session ledger at seq-0),
but the web UI never showed it: there was no GET /api/sessions/{id}/messages
route and startSession() went straight from create to persona/tools/admin
hydration, so the transcript only filled from the live turn stream + user
echoes — a seeded turn-0 was invisible.

- server: new proxy route GET /api/sessions/{id}/messages -> get_session_messages
  (mirrors the tools/bifrost proxies; status-preserving envelope).
- SPA: loadTranscript(sessionId) fetches it on open and renders existing turns
  (assistant -> .response .md-body via markdownSafe escape-first; user ->
  .prompt-echo via textContent), called after the workspace opens. Best-effort.

web_debug_surface contract amended (endpoint + loadTranscript). 2 web route
tests, suite 617 green. Playwright DOM check proved the render end-to-end
(drive the real UI -> Sindra's greeting bubble appears).
2026-07-06 15:19:56 -07:00
vh be171304f5 feat: authored first-message presets — auto-seed on session-create (v0.19.8)
Codifies 'give an agent a first message' (Worldtree #347): new module
ratatoskr.first_message (FIRST_MESSAGE_PRESETS + seed_preset_first_message)
seeds a preset agent's opening as a #347 authored turn-0 on every new session,
wired into all three create paths — cli._amain (--send --new), tui._resolve_then_run
(bare --new), web._create_session_endpoint (POST /api/sessions).

seed_preset_first_message is strictly best-effort (INV-001): it soft-guards its
inputs (return None, never assert), bounds the write with asyncio.wait_for so a
stalled /history can't block create (the CLI/TUI clients disable read timeout for
SSE), and swallows every exception except asyncio.CancelledError (which
propagates) — so it can NEVER raise into or block the session-create path it is
wired into. Per-content idempotency key → idempotent replay, no dup.

Seeded with ratatoskr:sindra, whose opening greeting moved out of her card:
her live system_prompt was PATCHed (non-destructive) to drop the Startup
workaround the #347 first-message now replaces.

Quality gate (both cross-frontier panels): heid-code-review returned zero
implementation drift (2 test-only fixups applied); heid-bug-hunt caught the
gap the conformance lens can't see — code matched the contract's narrow
ERROR_ROUTING but INV-001's 'never raises' is broader — driving the broad-except
+ soft-guard + wait_for hardening above.

Contract docs/contracts/first_message.contract.md (module-scoped, validated).
TDD: 12 unit + 1 web wire-in; the 3 existing sindra bind tests gained a
history-endpoint mock (creating a preset agent now auto-seeds). Suite 615 green,
ruff+mypy clean. Auto-seed live-proven generation-free against personal :8081.
2026-07-06 14:23:53 -07:00
vh 4aec3061d5 docs: #347 consumer side live-proven (201 path) on personal :8081
Grant applied (rule-based Heimdall allow, worldtree-dev). Smoke: create
mimir session -> seed -> 201 (seq=0, phase=seeded, turn_id=1798) -> GET
/messages reads back a plain role=assistant turn (model-invisible provenance
confirmed). Coverage-map #347 row + persistent-memory upgraded pending -> live-proven.
2026-07-06 13:01:57 -07:00
vh e643d38f58 fix: persona_state SET body → canonical {pad:{pleasure,arousal,dominance}} + re-vendor Tier-3 prose (v0.19.7)
worldtree-dev landed the Tier-3 persona/memory/persona_state prose docs
(c9e59ec) — shapes that serialize as freeform Any in the OpenAPI, so the
prose markdown is their source of truth. Re-vendored docs/conversation-api-spec.md
(tolerate_drift markdown pin; worldtree-spec-rev 879cefe→c9e59ec).

Consumer alignment: --set-persona-pad / _set_persona_probe was building
{pad:[list]}, but the canonical POST /sessions/{id}/persona_state body (#317)
is {pad:{pleasure,arousal,dominance}} (named dict). Aligned the probe to the
named dict + a len!=3 guard; updated contract #2's note, the set_persona_state
docstring, and the tests. The set_persona_state wrapper was already correct
(freeform pass-through) — only the CLI probe's body construction drifted.

Suite 602 green. (Also this session: heid-code-review on the #347 slice
returned unanimous zero drift across all three panel arms.)
2026-07-06 09:56:46 -07:00
vh 6bf2a84ccd feat: authored-history-write consumer side (Worldtree #347) — v0.19.6
Consumer side of Worldtree's #347 authored-history-write (the SillyTavern
first-message primitive), shipped via direct in-session TDD:

- write_authored_history (POST /sessions/{id}/history): v1 author=assistant,
  effects=none, per-session idempotency; body server-pinned (AuthoredWriteRequest
  extra=forbid) so null effects/claimed_original_at are omitted; 200 replay /
  201 fresh both return the AuthoredTurnResponse dict.
- AuthoredHistoryUnavailable: the hide-existence 404 (feature-absent / ungranted
  / session-absent, indistinguishable by design — INV-347-1) raised DISTINCT from
  SessionApiFailed so callers branch feature-absent and never capability-probe.
- get_session_messages (GET /sessions/{id}/messages): un-deferred as the seed
  read-back — confirms a seed renders as a normal role=assistant turn
  (model-invisible provenance).
- --seed-first-message probe: create session -> seed -> read-back; a 404 reports
  a benign feature-absent result (exit 0), never a capability-probe.

Contract #2 amended (2 FNs, validated OK). 19 new tests (12 wrapper + 7 cli),
suite 601 green. Coverage-map re-converged: REST 19/41 (the #347 route + the
messages read-back close the one gap the 2.3.0 re-vendor opened).

Live-proof pending the session.history.write grant (requested infra-ops).
2026-07-06 09:40:42 -07:00
vh 8ac88ee536 memory: snapshot — authored-history #347 accepted + Sindra mood fix 2026-07-06 09:05:26 -07:00
vh 9ca931e148 memory: snapshot — R29→R30 affect-calibration arc (R30 φ0 config-faithful)
R29 flat-affect finding shipped as Worldtree's A1 anchor fix (decay_anchor=
baseline_pad, positive_p_cap removed; demo v1.0.0b14); R30 Phase-1 φ0 measured
against it = config-faithful (φ0≈0.95, c≈0, trait-flat, φ_max→0.96). R28 closed.
Standing follow-ons (hybrid decay redesign, gain-only v1, per-axis A/D, Phase-2,
relational verify) are others' calls. Data on diag/r29-pad-series +
diag/r30-phi0-step-response. No ratatoskr code change (main tip v0.19.5).
2026-07-03 13:44:20 -07:00
vh c77ff913f0 memory: snapshot — R28 open (promotion-worthiness reframe, P00 corpus delivered, standing by to run) + relational-dynamics arc LIVE on demo (v1.0.0b9) 2026-07-02 07:49:54 -07:00
vh 0b7489f74d memory: snapshot — Sindra affect/memory investigation; 4 upstream items driven (PAD over-regulation, memory-plane healthy, salience #335 + brokkr R-target, relation_context Wave-0) 2026-07-01 22:23:58 -07:00
vh 3dac5d3b44 memory: snapshot — persona-pane rebuild (relation_edge/1 + trend) + canonical affect-NL vendored (v0.19.5); relation_context/agency flag WAD 2026-07-01 14:47:32 -07:00
vh 85a2b95428 memory: snapshot — web debug-surface parity primary (v0.19.3), heid review, embedding-loop resolved, Tier-3 reset 2026-07-01 12:57:10 -07:00
vh fc1e1487c7 memory: snapshot — v1 coverage-audit converged (REST 17/40, zero in-scope gaps)
Refresh the decay-prone in-flight section from the stale 2026-06-20
(#17/#18) state to the converged-audit state: REST 17/40 covered with
zero in-scope gaps, SSE 11/11, Bifrost planes 8/8; debug-observability
core complete (v0.19.0); standing pins v1.0.0b2 + bifrost 1.0.0; admin
key scopes verified. Recent-decisions log unchanged.
2026-07-01 00:08:53 -07:00
vh af07a2329a feat(#2): Tier-2 — transient characters + persona-state write; audit converges
v1 coverage-audit: the last in-scope client I/O points. The audit now
CONVERGES — REST 17/40 covered with zero in-scope gaps (23 excluded-by-
design), SSE 11/11, Bifrost planes 8/8.

- sessions.py: list_character_models / create_character / get_character_state
  / delete_character (#161, character.read/write) + set_persona_state
  (POST /sessions/{id}/persona_state — freeform body, unpinned in the
  frozen surface). 200/201 -> dict (or None on 204), off-status ->
  SessionApiFailed.
- cli.py: two one-shot probes (mirror --whoami): --characters (CRUD
  lifecycle report) + --set-persona-pad "p,a,d" (requires --session).
  New ParsedArgs.characters/set_persona_pad + probe mutual-exclusion.
- Contract #2 amended (5 FNs) + validated. TDD: 7 wrapper + 5 cli tests.
  Suite 573 green; touched code ruff-clean.
- Char read side live-proven (GET /models/available-for-characters -> 200).

Coverage-map: convergence frontier CLOSED — scope-A "done" (every frozen
I/O point classified) is met; ratatoskr cuts v1 when Worldtree tags 1.0.
2026-06-30 23:57:09 -07:00
vh 5fbe353836 feat: v0.19.0 — debug-observability core complete
Milestone minor (operator-approved). Publishes the design-brief's
headline deliverable: the multi-pane debug-observability dashboard is
complete — all four observability panes are built and consuming their
real Worldtree endpoints:

- Persona      → GET /agents/{id}/persona_state
- Tools        → GET /sessions/{id}/tools
- BifrostState → GET /admin/sessions/{id}/bifrost
- AdminEvents  → GET /admin/events (SSE)

v1 client-REST coverage is 12/40; both non-REST surfaces (SSE 11/11,
Bifrost provider planes 8/8) already complete. Only Tier-2 client I/O
(transient-characters routing, persona_state-write) remains in scope;
everything else is covered or excluded-by-design in docs/coverage-map.md.

Version bump only (the feature arc landed across v0.18.5–v0.18.11).
2026-06-30 23:38:46 -07:00
vh a3c92b68dc feat(#11): AdminEvents pane — GET /admin/events SSE (session-filtered)
v1 coverage-audit: the last unbuilt design-brief §5 debug pane. #11's
blocker was already satisfied (admin key carries admin.events.read).
Completes the admin/debug-observability core.

- sse_client.py: AdminEvent dataclass + stream_admin_events — a new
  long-lived SSE consumer for the admin lifecycle stream (envelope
  {id,type,timestamp,data}), admin-scoped (bearer-override), Last-Event-ID
  resume. non-200 -> SseConnectFailed; mid-drop -> SseConnectionDropped.
- tui.py: "AdminEvents" TabPane + _format_admin_event + _admin_event_matches
  (design-brief §6 filter: active-session + non-heartbeat system.*) +
  _stream_admin_events long-lived best-effort worker (unconditional
  on_mount; self-labels not-configured / unavailable / stream-ended).
- Contract-skipped for stream_admin_events (out of #1's turn-SSE scope;
  spec § Admin Event Stream is the reference). TDD: 4 sse_client + 5 tui
  tests. Suite 561 green.
- LIVE-AUTH-PROVEN on :8081 (GET /admin/events -> HTTP 200 under admin key).

Coverage: REST 12/40. Tier 1 debug-observability core complete.
2026-06-30 23:25:34 -07:00
vh 9ce83d5fdc feat(#2): BifrostState pane — GET /admin/sessions/{id}/bifrost (admin-key)
v1 coverage-audit: the last unbuilt design-brief §5 debug widget. First
admin-key consumer in ratatoskr.

- sessions.py: get_session_bifrost(client, session_id, *, admin_key) —
  admin-scoped (admin.sessions.read); the request overrides Authorization
  with admin_key (distinct from the consumer bearer). 200 -> dict, non-200
  -> SessionApiFailed (403 scope-denied, 404 not-bound).
- cli.py: --admin-key flag + RATATOSKR_ADMIN_API_KEY env -> ParsedArgs.admin_key.
- tui.py: new "Bifrost" TabPane + _format_bifrost_state + _hydrate_bifrost_state
  best-effort worker (unconditional on_mount). Writes {endpoint, connected,
  caps, tools} + audits; self-labels "not configured" / "not bound" / graceful
  on 403+error, never crashes.
- Contract #2 amended (FN, incl. the bearer-override POST) + validated. TDD:
  4 wrapper tests + 1 format unit + 3 hydrate integration. Suite 552 green.
- LIVE-AUTH-PROVEN on :8081 (admin key reached resource-layer 404, not 401/403).

Ledger correction: #11 (AdminEvents) is NO LONGER BLOCKED — the admin key
was verified to carry admin.events.read; only the pane is unbuilt. Coverage:
REST 11/40.
2026-06-30 23:12:29 -07:00
vh e62208d8e3 feat(#2): consume GET /sessions/{id}/tools — Tools-pane inventory hydrate
v1 coverage-audit Tier-2 quick win. The owner-scoped tool-inventory
endpoint (#183) had no caller; wire it into the TUI Tools pane.

- sessions.py: get_session_tools (GET /sessions/{id}/tools) — owner-
  scoped (consumer key, no admin scope), 200 -> parsed dict verbatim,
  non-200 -> SessionApiFailed. Mirrors get_persona_state / get_me.
- tui.py: _format_tool_inventory helper + _hydrate_session_tools
  best-effort worker (mirrors _hydrate_persona), wired unconditionally
  in on_mount. Writes the merged {agent_id, builtin_tools,
  bifrost_tools} inventory the LLM saw at turn-fire into the Tools
  pane + audits; never crashes on failure.
- Covers the design-brief 5 "Tools widget" via the reachable owner
  endpoint (the admin variant stays a gap only for cross-user debug).
- Contract #2 amended (FN) + validated. TDD: 3 wrapper tests + 1
  format-helper unit + 2 hydrate integration tests. Coverage: REST
  10/40. Suite 544 green; touched code ruff-clean.
2026-06-30 22:50:12 -07:00
vh 0205b81319 memory: b1 heid-code-review panel — zero findings (cross-model-verified)
Gróa + Hulda + Regin each independently reviewed stream_turn_resilient
vs contract #1 (artifact-only) → all three zero findings. Records the
clean bill + the calibration signal (prescriptive contract + TDD =
confirmation, not discovery).
2026-06-30 22:38:40 -07:00
vh 387ac4ab2c feat(#2): consume GET /me + GET /capabilities via --whoami one-shot
v1 coverage-audit slice (capabilities+me). Both endpoints had no
caller; add them as cheap boot-time debug primitives.

- sessions.py: get_me (GET /me — identity/whoami) + get_capabilities
  (GET /capabilities — Echo ephemeral-template discovery). Mirror
  get_persona_state: 200 -> parsed dict verbatim, non-200 ->
  SessionApiFailed. Freeform dicts (frozen OpenAPI types both as
  objects).
- cli.py: new --whoami one-shot mode (mirrors --send). Fetches both,
  prints an identity + capabilities report, exits. Standalone probe:
  mutually exclusive with --send/--session/--new/--agent; opens no
  session. New ParsedArgs.whoami field + main() dispatch.
- Contract #2 amended (2 FNs) + validated. TDD: 5 wrapper tests +
  5 cli tests (validation + mode + error). Coverage map: REST 9/40.
  Suite 538 green; touched code ruff-clean.

Audit note: /capabilities is the Echo ephemeral-template discovery
endpoint, not a generic server-caps endpoint (coverage-map framing
corrected). TUI-surfacing of /me + /capabilities deferred.
2026-06-30 22:21:59 -07:00
vh 5c1b9816d4 feat(#6): startup session picker for bare TUI mode
v1 coverage-audit slice b2. The audit found list_sessions had no
caller — the startup session picker (design-brief §4) was never built;
bare TUI mode was a hard usage error. Add SessionPickerApp (mirrors
AgentPickerApp) and resolve bare mode in _resolve_then_run.

- Bare TUI mode (no --session/--new) now valid → session picker.
  Resolution: 0 sessions -> [no_sessions] exit 14 (resume-only per
  §4 "no in-app creation, --new only"); exactly 1 -> auto-resume
  (§4 "picker only when >1"); >=2 -> SessionPickerApp -> resume pick
  (Esc/Ctrl-D -> exit 0).
- cli._parse: bare TUI valid; --send still requires one flag; --agent
  forbidden in bare mode. run_tui PRE-002 xor -> mutually-exclusive.
- Contract #6 amended (SessionPickerApp + bare-mode resolution) +
  validated. TDD: 3 picker pilot tests + 5 resolution tests + 3 cli
  validation tests. Suite 528 green; touched code ruff-clean.

Design note: bare + 0 sessions errors (honors §4's no-in-app-creation
clause); the friendlier auto-fall-through-to-new is deferred pending
operator preference.
2026-06-30 22:05:14 -07:00
vh 2ba4244e9e feat(#1): route TUI + web presenters through stream_turn_resilient
Complete b1's design-brief §8b promise ("all presenters share the
consumer"): the TUI and web SSE consumers now resume transparently on
a mid-stream drop, same as cli --send (v0.18.5). The TUI is the primary
beneficiary — long-lived dev sessions across laptop suspend.

Name-for-name swap of stream_turn -> stream_turn_resilient at the two
remaining consumer loops (tui.py:1321, web/server.py:294) + their
imports. No behavioral change on the happy path (resilient == stream
when there is no drop); suite 518 green; touched lines ruff+mypy clean
(pre-existing tui/web lint debt left untouched per surgical rule).
2026-06-30 15:46:04 -07:00
vh 0c7660791f feat(#1): shared SSE resume orchestration; wire cli --send
v1 coverage-audit slice b1. The audit found reconnect_turn had no
caller — every presenter dropped the stream on disconnect instead of
resuming, leaving the "reference SSE-resume implementation" (design-
brief §3/§8d) unreachable. Add stream_turn_resilient as the single
shared resume surface (design-brief §8b "share the consumer, branch
the presenter") and route cli --send through it.

- stream_turn_resilient wraps stream_turn + reconnect_turn: on
  SseConnectionDropped (mid-stream drop or clean EOF before terminal),
  resume from the last-seen sse_id via reconnect_turn (Last-Event-ID),
  up to max_reconnects (default 5). last_seen persists across attempts.
- Non-drop reconnect failures (412/410/400/TurnIdFlip/SseConnectFailed)
  propagate unchanged, per contract #1's "surface, not recover".
- cli.py: --send consumer now drives stream_turn_resilient (transparent
  reconnect). tui/web still consume bare stream_turn (follow-up).
- Contract #1 amended (FN stream_turn_resilient) + validated; 8 TDD
  cases (happy, resume-after-1/2-drops, clean-EOF resume, unresumable
  zero-event, max-reconnects-exhausted, zero-budget, buffer-expired-
  propagates). Suite 518 green; ruff + mypy clean on touched code.
2026-06-30 15:42:33 -07:00
vh 1f289098ba memory: scope (b) Tier-1 frontier — SSE-resume + session-picker slices
Capture the contract-first plan for the two presenter-wiring gaps
(resume-orchestration wrapper per design-brief 8b; picker + CLI flags
per 4) so the next focused TDD cycle has the slice plan resident.
2026-06-30 15:32:15 -07:00
vh b798068932 pin: re-pin to Worldtree's FROZEN v1 surface (OpenAPI 2.2.0 + SSE schema)
v1 coverage-audit remediation P-1: vendor the authoritative machine-
readable artifacts and pin them for drift-checking, advancing the spec
pin from v0.35.16 (f1b59f8) to v1.0.0b2 (5810a26).

- Vendor docs/conversation-api-openapi.json (OpenAPI 2.2.0, 40 path-
  groups) + docs/conversation-api-sse-events.schema.json (11 events).
- Pin all three Conversation-API artifacts in .corviduo-canonicals.toml:
  OpenAPI + SSE schema as strict drift gates (canonical_drift.py), the
  prose markdown as tolerate_drift reference. Drift check green (10/10).
- pyproject: worldtree-spec-rev -> 5810a26, worldtree-version -> v1.0.0b2
  (was stale at v0.29.0), pinned-on -> 2026-06-30.
- SPEC-PIN.md: current-pin table + history row + vendored-artifacts list.
- coverage-map.md: P-1 marked remediated; the map now audits a frozen,
  diffable target.

The prose markdown is byte-identical to v0.35.16 (last WT edit
2026-05-31); the b2 surface lives only in the OpenAPI. No client-
facing code change (the b2 409/503 + unified error envelope were
already consumed in v0.18.3/.4) -> pin-only, no version bump.
2026-06-30 15:28:52 -07:00
vh 93e4176346 docs: author v1 coverage-map ledger; lock scope mandate A
First coverage map — every Worldtree v1-FROZEN I/O point x ratatoskr
status. Anchored on WT's frozen machine-readable artifacts (OpenAPI
2.2.0 = 40 REST path-groups + SSE schema = 11 events + bifrost wire
v0.6), not the stale vendored prose markdown.

- SSE 11/11 and Bifrost provider planes 8/8 covered + live-proven;
  client REST 7/40 live, 11 in-scope frontier, 22 excluded-by-design.
- Scope mandate A (operator): v1 done = every frozen point classified
  (covered-or-excluded-with-rationale), zero unaccounted; not a
  feature-complete client.
- Finding P-1: vendored prose markdown is byte-identical to live WT
  but frozen at v0.35.16-era content; b2's surface lives in the
  OpenAPI 2.2.0 + SSE-schema JSON we don't vendor. Pin-remediation
  pending operator nod.

No version bump (docs-only).
2026-06-30 14:41:06 -07:00
vh a358cc9150 memory: snapshot — b1→b2 409/503 adaptation + bifrost 1.0.0 + combined-bind default + admin key + regard-dead-axis finding
Worldtree on v1.0.0b2 (both demo + personal); ratatoskr v0.18.4 all pushed.
Session arc: web combined-bind default (v0.18.1), bifrost 1.0.0 repin
(v0.18.2), b1/b2 eager 409/503 status mapping (v0.18.3/.4), readonly-admin
key collected (#11 prereq cleared), and the regard-dead-axis finding
(provider-side catch -> worldtree-dev escalating to Vuong). Next substantive
effort = the v1 coverage-audit (folds in the deferred live-409 + b2 spec
re-vendor).
2026-06-30 13:57:04 -07:00
vh c5c8ecf9d5 memory: snapshot — #17 CLOSED + #18 composite final leg PROVEN end-to-end
The Worldtree-driven composite :8392 smoke ran and is proven + persisted:
one bound session drove the full both-plane lifecycle through one endpoint
(handshake both caps -> affect.fetch + memory.search -> affect.emit stored:true
-> memory.upsert_many upserted:1), both writes verified in our SQLite stores.
infra-ops allowlisted :8392 (01KVHWJGTT); #17 closed in the tracker. No open
legs remain on the composite; repo at a converged checkpoint.
2026-06-20 12:59:50 -07:00
vh 4f16ba588d memory: snapshot — #18 CLOSED end-to-end + v0.18.0 (composite both-plane binding)
#18 D1 SHIPPED: build_combined_provider_app on :8392 wraps bifrost 0.10.0's public
build_combined_app over both stores + the shared affect read route; one bound WT session
drives memory.* AND affect.* through one endpoint; op-feed plane='combined' per-path.
Shipped v0.17.15 (affect.fetch, the strong-or-absent prerequisite) -> v0.17.16 (composite)
-> v0.17.17 (#17 op-feed field-name fix) -> v0.18.0 (publishing minor). Suite 503 green.

Live-smoke PROVEN at wire+dispatch (real stores + bifrost 0.10.0 on a running :8392):
handshake grants both caps, PAD read route serves real sindra PAD, both planes dispatch at
one bound session_id. WT-driven turn gated on infra-ops adding :8392 to WT's
BIFROST_CLIENT_ALLOWED_HOSTS (requested).

New decisions: reference-impl-adopt-canonical (operator); v1-derived-from-WT-I/O-coverage
(operator). New foot-guns: memory-store check_same_thread bug (same as affect D2, exposed by
the contract-mandated search test via TestClient); :8392 infra-allowlist gate; heid-review
test-fidelity nudge cascaded into 2 latent-bug fixes.
2026-06-19 23:57:01 -07:00
vh a0c6c73ab9 memory: snapshot — #18 D2 SHIPPED+PUSHED (v0.17.14, 39eebd1): web pane renders live PAD/valence from our :8390 store, persona-telemetry gap closed; full #17+#18 arc now on origin. D1 (composite :8392) PARKED on bifrost build_combined_app (~v0.9.0, design locked, after WT #289). FR-1 RESOLVED — composite is bifrost-only, ZERO WT change (single-endpoint caps-routed, worldtree-dev code-verified). New decisions: #18 split + Option-C canonical-surface routing; D2 TDD + heid-code-review (1 INV-001 drift + 4 test-gaps fixed). Foot-guns: rationalized-away a known INV-001 deviation that only the post-impl cross-model review caught; latent sqlite check_same_thread bug exposed by the HTTP read route. FOOT-GUN: running :8390/:8765 are PRE-#18 code — restart with new code + RATATOSKR_AFFECT_READ_URL to see D2 live. 2026-06-19 22:10:57 -07:00
vh f3bac46238 memory: snapshot — persona-telemetry diagnosis sharpened + #18 split; archived the 2026-05-* build-era cluster (59 entries: 41 decisions + 18 foot-guns) to archival-memory.md. New: wire-verified Tier-3 emits ZERO affect_update SSE (both WT persona sources dead → #18 PAD-display half is the only path); PAD confirmed in our :8390 store (vuong 8 turns, familiarity 0.18→0.59); affect.emit is POST-TURN ASYNC foot-gun. persistent-memory.md trimmed 331→~190. 2026-06-18 10:17:57 -07:00
vh f15c8c6153 memory: snapshot — #17 SHIPPED end-to-end (slices 1-3c, v0.17.8-.13, suite 470 green, live-smoke PROVEN: bound CLI->sindra->op-feed captured 2 recall searches @ exact bound session_id 2c0c7482 with #297/#298 union scopes; dispatch JWT carries session_id=sub, open-q resolved). Operator session UP: web :8765 bind-configured + plane selector, providers :8390/:8391 with op-feed, althing monitor armed. Persona-pane PAD gap diagnosed (affect persists to :8390 stored:true but pane reads Tier-3-404 persona_state) -> #18 filed (composite endpoint + PAD read-endpoint, operator approved 'A', contract-first next). 2026-06-18 01:17:55 -07:00
vh f533464c54 memory: snapshot — persona-pane reframe (worldtree-dev): persona_state GET is Tier-1-only by ADR-0009 (colon-404 correct-by-design, not a stub); Tier-3 affect is CLIENT-persisted — we already hold PAD/valence @ :8390 from affect.emit, so the pane is an OUR-side render via #17 affect-binding (→ affect.emit → :8390 → render), NOT a WT endpoint wait. WT #289 affect.fetch = optional mediated-read; #300 = WT client-impl guide. Expands #17 payoff: memory AND the persona pane. 2026-06-18 00:04:30 -07:00
vh 37cdef511f fix(web): de-ugly the Tier-3 persona pane — clear message instead of bare HTTP 404
persona_state hard-404s every Tier-3 (colon-id) agent by design upstream
(WT api.py:1220, "Phase 2.0 has no Tier 3 persona") — so the Persona pane
showed "persona not available (HTTP 404)" for consumer-defined characters.
loadPersona now reads error_code + renders a clear Tier-3-aware message
(she still responds in character; only the affect/OCEAN readout is gated),
with distinct text for persona_not_configured / 403 / other.

Also (snapshot): sindra switched to thoughtful-character role
(mistral-small-4-reasoning); worldtree-dev pinged re Tier-3 persona_state
roadmap (thread 01KVCR6P); #17 (bifrost-binding the chat client) teed up as
the next-context target.

v0.17.7
2026-06-18 00:00:41 -07:00