Implement `mark_superseded(ids, *, superseded_by=None, reason=None)` — the SOLE
supersession verb Worldtree #364's promotion-hygiene reconciliation calls to retire
contradicted facts (wire shape confirmed by worldtree-dev, bifrost_memory_store.py:293).
Live re-verify (2026-07-16) proved our provider 500-crashed on this call (unimplemented)
→ #364's retirement couldn't land + a retry-storm bloated the store; the readout only
passed via transient recency-eviction.
- `mark_superseded` mirrors the reference `_mark_lifecycle`: sets top-level
`superseded=True` (+ `superseded_by`/`superseded_reason` when non-None), increments
revision, NON-destructive (get still returns; recoverable). Unknown ids skipped.
- `_is_live` (INV-011) now short-circuits on `superseded is True`, so a retired chunk is
excluded from `scan` (person-prime) — durable retirement, not just recency-eviction.
search is unfiltered (matches reference; WT re-checks liveness client-side).
- Contract: un-defer mark_superseded (+ FN spec, INV-011); TDD 5/5 (retires-from-scan
tracer, non-destructive-get, unknown-id no-op, non-None-fields-only, parity #195).
- bifrost 1.1.1→1.1.4: hasattr-gate backstop for the maintenance verbs (unimplemented
verb → unsupported_capability 400, never AttributeError/500/retry-storm — the gap we
surfaced) + the 1.1.3 scan/cursor conformance harness. Full suite 644 green.
DEPLOY-BREAKER caught by driving the live bind (unit tests + worldtree-dev's
name-only parser + heid-bug-hunt all missed it). bifrost handshake_response
`SortableChunkField` requires BOTH `name` and `type` (additionalProperties:false).
We advertised `[{"name":"updated_at"}]` (no `type`), so the handshake_response
failed wire-schema validation → `bifrost.schema_validation_failed` → the ENTIRE
Bifrost bind (memory + affect) broke, not just the sort. Advertise
`{"name":"updated_at","type":"timestamp"}` (matches the reference; `type` is
advisory-only). Regression guard added to the caps test (asserts required name+type,
no extra keys). Full suite 639 green.
Operator accepted offset-cursor for v1 (person-prime single-page is
conformant). INV-010 now documents the KNOWN DEVIATION: multi-page
continuation diverges from bifrost's protocol snapshot-cursor contract
(dispatch drops sort on continuation, ScanCursorExpired normative) — our
offset cursor doesn't snapshot (dup/drop under concurrent write) and never
expires. Durable fix routed to bifrost-dev as a conformance-coverage gap
(scan/cursor is untested); ratatoskr will adopt reference snapshot-cursors
if bifrost rules them normative.
heid-bug-hunt panel (Gróa + Hulda, confirmed-from-code) caught that a truthy
non-dict `sort` (e.g. sort="updated_at" or sort=["updated_at"]) reached
`(sort or {}).get(...)` and crashed with AttributeError instead of the
InvalidArguments PRE-003 promises for malformed caller-controlled input. Add an
isinstance guard before field extraction. Test covers str/list/int sort values.
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.
Replace the terse three-question setup preset with the immersive holodesk
scene-open (Sindra already present, sets the same three settings — intensity /
mood / willingness — in character). Matches the persona shipped in the
soong-lab Sindra redefine. Stays under the 8192-byte authored_content budget;
test_first_message green.
Implement the memory-store `scan` verb — a query-LESS, LIVE-only, globally
ordered top-N-by-recency read — and advertise `sortable_chunk_fields=
[{updated_at}]` at the Bifrost handshake. Advertising the cap is what lights
up Worldtree's #349 person-prime turn-1 durable-fact injection (Branch-A
`"updated_at" in caps.sort_fields_supported`); the fix is ZERO Worldtree
change — the running provider announcing the cap is the trigger.
scan is:
- LIVE-only server-side (INV-009): superseded/tombstoned excluded — a dead
fact can never inject; person-prime's `lifecycle_state=live` does not ride
the scan wire, so server-side is authoritative.
- Globally ordered before pagination (INV-010): the full scope-filtered live
set is ordered by (sort.field, direction) globally; missing value LAST,
chunk_id tiebreak. Backed by an expression index on
json_extract(record_json,'$.updated_at') to stay in the 500ms budget.
- Cursor = offset into the global order; emits a next cursor only when a
further match exists (no empty trailing page — matches the reference).
Sort is dispatch-gated: an unadvertised sort.field raises InvalidArguments,
never a silent unsorted fallback.
Contract amended: un-defers scan, adds the FN spec + INV-009/INV-010 +
sortable_chunk_fields to INV-006. TDD 7/7 green (scan_recency tracer,
live_only, scope_isolation, unadvertised_sort, person_prime_record_shape,
cursor pagination, parity_vs_reference vs InMemoryMemoryStore #195). Full
suite 638 green.
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.
Single-pin sync of the drift-check meta-tooling to the corviduo-project-
template canonical (23271287 → 2659a17a). Consumer copy is byte-exact;
pin hash + pinned_at bumped. No runtime effect (meta-tooling, not product
code) → no version bump per SemVer skip-rule. The two tolerate_drift
worldtree prose pins (conversation-api-spec, affect-egress-consumer-
reference) are deliberately left STALE — their re-vendor is coordinated
with the R34/R35 eval's diff-review, not a blind sync.
bifrost-dev heads-up (althing 01KX6S4RFH…): 1.1.0's describe_store leaked
the v0.7-additive `sortable_chunk_fields` into a v0.6-negotiated
StoreCapabilities response, so a strict v0.6 client (additionalProperties:
false) rejects our :8392 server's handshake. This is the library-level fix
for the regression snapshotted in 8468c47 (worldtree-dev worked around it
by advancing their client to wire-v0.7 / b47; our served handshake was
still latently v0.6-incorrect for any v0.6 peer).
1.1.1 gates additive capability fields on the NEGOTIATED wire (ADR-0008),
so a v0.6 handshake stays v0.6-clean. Wire schemas + pins UNCHANGED —
serialization-correctness only; our v0.7 handshake with Worldtree b47 is
unaffected. Zero code change (same shape as the 1.1.0 repin). Reference-
impl adopt-canonical: dependency owner recommends the repin, we adopt.
Suite 631 green on 1.1.1 (clean env). Provider restart + live WT-bound
re-verify pending — non-urgent (no v0.6 peer currently handshakes us).
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.
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).
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.
Additive over the frozen v0.6 (adds memory.scan sort directive +
describe_store sortable_chunk_fields; build_combined_app surface
unchanged). v0.6 stays frozen + valid; we don't use scan-sort (a gated
verb we advertise-unsupported), so this is reference-impl currency, not a
needed feature. Verified: full suite 631 green against 1.1.0, including
the #195 real-lib parity + combined-app build/dispatch tests. Repin +
uv lock only; zero code change. Reported by bifrost-dev via althing.
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.
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).
The bg-removed source (ImageMagick corner floodfill) the inlined web favicon +
brand marks derive from. 1024², alpha-transparent; downscale for any reuse.
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).
- 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).
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).
- 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).
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).
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).
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.