diff --git a/persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md b/persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md new file mode 100644 index 0000000..0dd10cd --- /dev/null +++ b/persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md @@ -0,0 +1,41 @@ +# [2026-07-16] bifrost scan/cursor conformance gap → snapshot-cursor ruled NORMATIVE + +## The gap (I surfaced it; operator's catch that it was bifrost's to fix) +The heid-bug-hunt flagged our offset cursor's cross-page dup/drop under mutation. On +verifying before routing, it turned out sharper than "robustness": bifrost's PROTOCOL +already mandates snapshot cursors — the dispatch engine (`bifrost/memory.py:311-314`) +drops `sort` on a cursor continuation with the comment *"the cursor's snapshotted order is +authoritative"*, and maps `ScanCursorExpired → 410`. The reference `InMemoryMemoryStore` +implements it (frozen ordered id-list per opaque token + TTL). But bifrost's **conformance +suite had ZERO scan/cursor coverage** — so our non-snapshot offset cursor passed +conformance while violating the protocol contract. That coverage hole is the real +completeness concern. Routed to bifrost-dev (thread `01KXK7MDTY…`). + +## bifrost-dev's ruling +1. **Snapshot-cursor is NORMATIVE, not opaque/per-store.** Operator ruled: cursor + snapshots a frozen ordered id-list, continuation ignores `sort`, stale/unknown cursor → + `ScanCursorExpired` → 410. **Offset-with-documented-limits is NOT blessed.** +2. **Conformance coverage added** in **bifrost 1.1.3** (`bifrost.conformance. + memory_store_conformance`, 4 probes: `scan_snapshot_order_authoritative`, + `scan_no_dup_or_drop`, `scan_unknown_cursor_expired`, `scan_snapshot_stable_under_write` + opt-in). Our offset bug ships as their negative-canary fixture + (`fixtures.v0_7.offset_cursor_store.OffsetCursorMemoryStore`). `main()` grades import + failure as exit 2 (setup error) vs exit 1 (conformance FAIL). + +## Our status + the TODO (operator-sequenced, NOT urgent) +Single-page person-prime (`cursor=None`) is already conformant — nothing shipped is broken; +it's only multi-page continuation that's non-conformant. Our contract INV-010 currently +marks the offset cursor **v1-provisional / KNOWN DEVIATION** (commit `8199774`). + +TODO when sequenced: +1. Bump pin bifrost `1.1.1 → 1.1.3`. +2. Replace the offset cursor with the reference snapshot semantics (frozen id-list per + opaque token + `ScanCursorExpired` → 410). +3. Run `run_all(store_factory=..., include_optional=True)` against our SQLite store — + expect P1/P2/P3 RED → green (the before/after IS the validation). Report to bifrost-dev + (we're their canary — first non-reference scan implementer). +4. Flip INV-010 from v1-provisional to snapshot semantics. + +TTL-duration expiry NOT asserted by the harness (no portable clock hook via store_factory); +bifrost-dev offered a `clock_control` opt-in if we ever need time-based expiry certified — +parked, not needed yet. diff --git a/persistent-memory.d/2026-07-16-person-prime-scan-shipped.md b/persistent-memory.d/2026-07-16-person-prime-scan-shipped.md new file mode 100644 index 0000000..dc6ec8a --- /dev/null +++ b/persistent-memory.d/2026-07-16-person-prime-scan-shipped.md @@ -0,0 +1,65 @@ +# [2026-07-15/16] person-prime `scan` build — SHIPPED + DEPLOYED + LIVE-VERIFIED (v0.20.14) + +**The "last push": fix Sindra not remembering Vuong's name across sessions.** + +## Root cause (worldtree-dev) +WT injects a recalled fact only if combined score (sim×salience) ≥ **0.45** +(`auto_inject_combined_score_threshold`, `core/memory/context_promotion/config.py`), +and recall is per-turn **query-gated** → moderate-sim durable facts (name hit ~0.40) +never inject. Designed turn-0 fix = WT **#349 person-prime**: a query-LESS +top-N-by-recency durable-fact injection, capability-gated on the store advertising +`updated_at` in `sort_fields` at the Bifrost handshake — DARK for our provider until now. +Fix (ZERO Worldtree change): implement the sorted `scan` verb + advertise the cap. + +## What shipped (6 commits, v0.20.11 → v0.20.14; suite 639 green throughout) +- `8fc757a` **v0.20.11** — `scan` verb + `sortable_chunk_fields` cap. Query-LESS, + LIVE-only (INV-009: superseded/tombstoned excluded server-side), globally-ordered- + before-pagination by indexed `updated_at` (INV-010), sort dispatch-gated. Offset + cursor with a `has_more` peek (no empty trailing page = reference-parity). TDD 7/7 + incl. `parity_vs_reference` #195. NUANCE resolved: bifrost's `InMemoryMemoryStore` + READS `updated_at` (never stamps it) — identical to ours; ref does NOT lifecycle- + filter so parity is over the live set only. +- `a9c521a` **v0.20.12** — Sindra holodesk first-message preset (split out of the + soong-lab redefine as its own concern). +- `25ccb5c` **v0.20.13** — heid-bug-hunt fix: a truthy non-dict `sort` + (`"updated_at"`/`["updated_at"]`/int) hit `(sort or {}).get(...)` → AttributeError + instead of InvalidArguments. Added isinstance guard (Gróa#1/Hulda#2 confirmed). +- `8199774` — contract: marked the offset cursor **v1-provisional / KNOWN DEVIATION** + (see [[2026-07-16-bifrost-cursor-conformance]]). +- `f46ccba` **v0.20.14** — **THE DEPLOY-BREAKER** (see Tried/abandoned): advertised + `sortable_chunk_fields=[{"name":"updated_at"}]` WITHOUT `type` → bifrost + `handshake_response` `SortableChunkField` requires BOTH name+type + (`additionalProperties:false`) → whole bind broke. Fixed → add `"type":"timestamp"` + + regression guard in the caps test. + +## heid-bug-hunt triage (panel Gróa/Hulda/Regin, thread `01KXK5XTYHV8TGEDRAZV8GRXWC`) +FIXED: non-dict sort (v0.20.13). SURFACED→operator: offset-cursor cross-page dup/drop +(→ became the bifrost cursor arc). NOTED (reference-parity/accept-known-risk, no fork): +full-table materialize-then-Python-filter (matches ref's O(store) iteration), unbound +limit, falsy scope coercion, lexical timestamp ordering. REFUTED: Regin's mixed-type +ORDER BY "crash" (SQLite orders by storage class, doesn't raise) + its self-retracted +None item. + +## Deploy + live verify (2026-07-15, operator-authorized) +Restarted BOTH :8392 combined provider + :8765 web on the new code via the env- +preserving `scratchpad/relaunch_by_pid.py ` (captures /proc cmdline+environ+cwd → +byte-identical config; self-daemonizes). Added the missing REQUIRED +`RATATOSKR_MEMORY_EMBEDDING_DIM=1024` to `env.sh`. + +Drove a bound Sindra turn (`ratatoskr --new --agent ratatoskr:sindra --send … --bifrost-url +http://10.100.10.50:8392 --end-user-id ratatoskr-tui`): +- **GATE LIT + scan fired ONCE at turn 1** with worldtree-dev's exact args + (`scope_all={end_user:ratatoskr-tui} cursor=null limit=3 sort={updated_at,desc}`) → + 3 records in **~5ms** (no 500ms fail-open). Injection confirmed in Sindra's CoT. +- **Cross-session recognition WORKS** — she recalls Vuong as a distinct person + his + patterns. The blank-slate problem is SOLVED. +- **BUT name-recall FAILS** — `"Name is Vuong."` is the OLDEST chunk (07:09) → excluded + from top-3-by-recency AND scores 0.354 (sub-0.45) on the query path → injects via + NEITHER; meanwhile a stale contradictory `"user has not yet provided their name"` + (07:58, 0.46) IS injected → she concludes she lacks the name. **Root cause = WORLDTREE + ranking/hygiene** (WT #364), not our wire. See [[2026-07-16-wt364-r39-name-recall]]. + +## Status +Technical path GREEN end-to-end (logged by worldtree-dev as the person-prime live +milestone). Name-recall waits on WT #364 + R39-designed identity-class pinning. Keeping +the live 10-chunk store as the #364 re-verify target. diff --git a/persistent-memory.d/2026-07-16-wt364-r39-name-recall.md b/persistent-memory.d/2026-07-16-wt364-r39-name-recall.md new file mode 100644 index 0000000..02c26e1 --- /dev/null +++ b/persistent-memory.d/2026-07-16-wt364-r39-name-recall.md @@ -0,0 +1,65 @@ +# [2026-07-16] Name-recall gap → WT #364 + brokkr R39 re-drive (DECISIVE) + subject-provenance catch + +Downstream of the person-prime live verify ([[2026-07-16-person-prime-scan-shipped]]), +which proved the wire is green but the *name* still misses. Root cause is Worldtree-side. + +## WT #364 (worldtree-dev filed; our live specimen = the evidence base) +The name-recall miss is TWO defect classes, both at promotion: +1. **Contradiction-reconciliation missing.** Promotion wrote a NEGATIVE-knowledge fact + ("user has not yet provided their name") that was already false; promotion does NO + contradiction check against the store, so both the true and stale facts sit live and + the WRONG one wins both recall paths (newer → recency top-3; 0.46 → above the 0.45 + query gate, where the true name sits at 0.354). +2. **Subject-attribution leak (operator-caught, NEW class).** Fact `75aa3110` ("prefers + clear parameters Intensity/Mood/Willingness…") is NOT a user fact — Vuong never said + it; it's **Sindra's OWN system-prompt scripted behavior mis-extracted into the USER + memory partition**. `e35b9dfe` (closeness) maybe the same. So the extractor leaks + CHARACTER-self facts into user memory — a subject-correctness axis orthogonal to the + recency/threshold/stale-negative story. worldtree-dev folded it into #364 as a + **subject-attribution gate at promotion** (a third reconciliation dimension). + +**The #364 fix that ships = `(subject,relation)` slot-supersession + identity-tier +surfacing** — NOT a threshold tweak. Our harness data directly shaped it. It lands with a +re-verify request to us. **#349 ranking decision already RULED by operator (2026-07-15 via +brokkr's R39 thread): no top-N recency band-aid; straight to R39 identity-class pinning.** + +## brokkr R39 Phase-1 Arm-0 fusion bake-off — DECISIVE +brokkr replayed our frozen 7-fact specimen. **HEADLINE: no (similarity, salience) fusion +can fix #364.** The stale negative PARETO-DOMINATES the true name — more similar +(0.463 > 0.354) AND equal salience (1.0 = 1.0) — so any monotone f(sim,sal) puts stale +above true: S0 product / S1 weighted-sum / S2 RRF all fail. Only **S3 bounded-boost** lands +true-in ∧ stale-out, and ONLY via the identity-class/source signal + negative-validity +retirement, NOT the sim/sal fusion. **Threshold-tuning is a dead end; the fix is the signal +FAMILY** (hard confirmation of Phase-0). Write-up: brokkr +`research/R39-memory-salience-dreams-surfacing/phase-1/re-drive-results.md`. + +My 3 findings all confirmed + folded: (1) salience blind — both name facts salience 1.0; +(2) shared `(user,name)` supersession slot (Phase-0 Q3); (3) char-self-leak = "genuine NEW +class" that RAISED the VoI of R39's dream/offline-hygiene facet (offline consolidation +re-partitioning mis-attributed facts). + +## Data structure findings (for the export) +Our provider persists ONLY `salience` (+ the embedding). `similarity`/`combined` are +WT-side query-time (`bifrost_memory_store.py:702`, combined = sim×salience) — NOT in our +store. **Person-prime is query-LESS → carries NO similarity** (brokkr's Arm-1 finding). +Recovered per-fact similarity from the verify SEARCH log (query "Hi Sindra — do you +remember me?"): name 0.354, stale 0.463; ×salience-1.0 = combined, matching brokkr's +0.354/0.46 grounding. `salience_word` (granite categorical) is a WT-extraction-time +artifact, not persisted. + +## The export + the specimen +- Operator approved **VERBATIM** export ("nothing there is really a concern"). The + classifier had blocked writing PII to shared `/mnt/smithy`; routed to operator → he chose + full → delivered INLINE (scoped) in brokkr thread `01KXMN7NR54…` as 7-row JSONL. +- **⚠️ My verify drive CONTAMINATED the specimen**: it wrote 3 new chunks (re-extractions + incl. a 3rd "name unknown" negative) → store is now **10, not 7**. Original 7 intact. +- brokkr ACCEPTED the 3 verify-adds as the **Phase-2 Arm-2 seed** (domain-contradiction + set). I froze a WAL-consistent snapshot of the full 10-chunk store at + `r39-frozen-specimen/sindra-10chunk-specimen.db` (gitignored, `VACUUM INTO`) so it + SURVIVES #364 reconciliation. Export on brokkr's Phase-2 signal. +- **R39 Arm-0 hold LIFTED** (worldtree-dev). Live store kept UNTOUCHED as the #364 + re-verify target; frozen snapshot carries the research seed forward independently. + +## Peer threads +worldtree-dev verify `01KXK86PZ9…` + hold `01KXMK1C44…`; brokkr R39 `01KXMN7NR54…`; +bifrost cursor `01KXK7MDTY…` (see [[2026-07-16-bifrost-cursor-conformance]]). diff --git a/persistent-memory.md b/persistent-memory.md index ce7a887..69b2c3d 100644 --- a/persistent-memory.md +++ b/persistent-memory.md @@ -1,6 +1,6 @@ # Persistent memory — ratatoskr -_Last updated: 2026-07-15_ +_Last updated: 2026-07-16_ > **Always check for `/tmp/ratatoskr-dev-handoff.md`** — if it exists and its > `Written:` stamp is under an hour old, read it (it carries the in-flight @@ -44,59 +44,50 @@ upstream API key stays server-side (INV-003). ## Current state / in-flight -_As of 2026-07-15:_ +_As of 2026-07-16:_ -**✅ person-prime `scan` build (THE "last push"): DEPLOYED + LIVE-VERIFIED (v0.20.14). Technical path GREEN; name-recall gap is now WORLDTREE-side (ranking/hygiene).** +**✅ person-prime `scan` build SHIPPED + DEPLOYED + LIVE-VERIFIED (v0.20.14).** The +cross-session name-recall fix. `scan` verb + `sortable_chunk_fields` cap implemented, +deployed to the running :8392 provider (+ :8765 web), and live-verified by driving a bound +Sindra turn: gate lights, scan fires turn-1 (~5ms), injection confirmed, **cross-session +recognition works** (blank-slate gone). **Name-recall specifically still misses** — root +cause is WORLDTREE-side ranking/hygiene (WT #364), NOT our wire. Full arc: +`persistent-memory.d/2026-07-16-person-prime-scan-shipped.md`. -**⚠️ DEPLOY-BREAKER caught by driving the live bind (v0.20.14, `f46ccba`):** I first advertised `sortable_chunk_fields=[{"name":"updated_at"}]` WITHOUT `type` — bifrost's `handshake_response` `SortableChunkField` schema requires BOTH name+type (`additionalProperties:false`), so the handshake_response failed wire-schema validation → `bifrost.schema_validation_failed` → the ENTIRE bind (memory+affect) broke, not just sort. Unit tests + worldtree-dev's name-only service parser + the heid-bug-hunt ALL missed it; only DRIVING the real handshake (`/verify` discipline) caught it. Fixed → `{"name":"updated_at","type":"timestamp"}` (matches reference; type advisory-only) + regression guard in the caps test (asserts required name+type, no extra keys). LESSON: validate `describe_store` against the bifrost wire schema, not just our own caps assertions. +**Standing — awaiting two peer-initiated touchpoints (both monitored):** +- **WT #364 re-verify** (worldtree-initiated): when the name-recall fix deploys (= `(subject,relation)` + slot-supersession + identity-tier surfacing, shaped by our specimen), worldtree-dev sends a + re-verify request → re-drive the same bound-Sindra turn against :8081, expect "Name is Vuong" + to inject. **Keeping the live 10-chunk store UNTOUCHED as the re-verify target** (no cleanup). +- **R39 Phase-2 Arm-2 export** (brokkr-initiated): the 10-chunk contradiction specimen is frozen at + `r39-frozen-specimen/sindra-10chunk-specimen.db` (gitignored, VACUUM INTO) — survives #364 + reconciliation; export on brokkr's Phase-2 signal. Full R39/#364 arc: + `persistent-memory.d/2026-07-16-wt364-r39-name-recall.md`. -**LIVE VERIFY (2026-07-15, drove a bound Sindra turn via `ratatoskr --new --agent ratatoskr:sindra --send … --bifrost-url http://10.100.10.50:8392 --end-user-id ratatoskr-tui`):** GATE LIT + scan fired ONCE at turn 1 with worldtree-dev's exact args (`scope_all={end_user:ratatoskr-tui} cursor=null limit=3 sort={updated_at,desc}`) → 3 records in ~5ms (no 500ms fail-open); injection confirmed in Sindra's CoT; **cross-session recognition WORKS** (she recalls him as a distinct person + patterns — blank-slate GONE). **BUT name-recall FAILS** — `"Name is Vuong."` is the OLDEST chunk (07:09) → excluded from top-3-by-recency AND scores 0.354 (sub-0.45) on the query path, so injects via NEITHER path; meanwhile a STALE contradictory `"user has not yet provided their name"` (07:58, 0.46) IS injected → Sindra concludes she lacks the name. **ROOT CAUSE = WORLDTREE ranking/hygiene** (not our wire): recency-top-3 under-weights early identity facts + the "no name" fact was never `mark_superseded` when "Name is Vuong" landed → both live+contradictory. Reported to worldtree-dev (thread `01KXK86PZ9…`) with 3 options: memory hygiene (supersede), person-prime salience-weighting, or higher N. +**Open operator-sequenced task (NOT urgent):** adopt the bifrost snapshot-cursor (bifrost-dev +ruled it NORMATIVE, offset not blessed; conformance harness shipped in bifrost 1.1.3). Single-page +person-prime is already conformant, so nothing shipped is broken. Details + TODO: +`persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md`. -**CROSS-PROJECT FOLLOW-UPS (2026-07-16, spawned by the verify — 4 inbound drained):** (1) **bifrost-dev RULED** scan cursor snapshot-semantics NORMATIVE (offset NOT blessed) + shipped conformance coverage in **bifrost 1.1.3** (`bifrost.conformance.memory_store_conformance`, 4 probes; our offset bug ships as their negative-canary fixture). TODO (OPERATOR-SEQUENCED, not urgent — single-page person-prime already conformant): bump pin 1.1.1→1.1.3, adopt snapshot cursor (frozen id-list per opaque token + `ScanCursorExpired`→410, match `InMemoryMemoryStore`), run `run_all(include_optional=True)` → expect P1/P2/P3 RED→green; we're their canary. Replied ack'd (thread `01KXK7MDTY…`). (2) **worldtree-dev FILED the hygiene bug as WT #364** (promotion writes negative-knowledge facts w/o contradiction-reconciliation; our specimen = evidence base); #349 ranking/N decision is **ALREADY RULED (CORRECTION 2026-07-16 from worldtree-dev — NOT inbound to Vuong as I'd said)**: the operator ruled 2026-07-15 via brokkr's R39 thread — **NO top-N recency band-aid ships; person-prime goes straight to R39's researched identity-class pinning**, sequenced on #364. Next person-prime change = the R39-designed one. Both my flags folded into **#364** (now a MULTI-class contract: (i) stale-negative contradiction-reconciliation, (ii) subject-attribution gate at promotion for the character-self-into-user leak, same pipeline stage). **★ R39 RE-DRIVE COMPLETE (2026-07-16) — DECISIVE:** brokkr's Arm-0 bake-off proved NO (similarity,salience) fusion fixes #364 on this specimen — the stale negative PARETO-DOMINATES the true name (more similar 0.463>0.354 AND equal salience 1.0), so S0 product / S1 weighted-sum / S2 RRF all fail; only **S3 bounded-boost** lands true-in∧stale-out, and ONLY via the identity-class/source signal + negative-validity retirement, NOT the sim/sal fusion → **threshold-tuning is a dead end; the fix is the signal FAMILY** (hard confirmation of Phase-0). All 3 of my findings confirmed + folded; the char-self-leak = "genuine NEW class" that RAISED the VoI of R39's dream/offline-hygiene facet (offline consolidation re-partitioning mis-attributed facts). #364 now UNBLOCKED. **3 verify-adds ACCEPTED as Phase-2 Arm-2 seed** (domain-contradiction set) → I froze a WAL-consistent local snapshot of the full 10-chunk specimen at `r39-frozen-specimen/sindra-10chunk-specimen.db` (gitignored, `VACUUM INTO`) so it SURVIVES #364 reconciliation regardless of timing; export on Phase-2 signal. Result write-up: brokkr `research/R39-memory-salience-dreams-surfacing/phase-1/re-drive-results.md`. Clear to let #364 reconcile the LIVE store when worldtree deploys it; the frozen snapshot carries the research seed forward. **HOLD LIFTED (worldtree-dev 2026-07-16) — R39 Arm-0 arc CLOSED.** Specimen duty done; I'm KEEPING the live 10-chunk store as-is as the **#364 re-verify target** (no cleanup), Phase-2 seed frozen separately. The #364 fix that ships = **(subject,relation) slot-supersession + identity-tier surfacing** (NOT a threshold tweak — our harness data directly shaped it); it lands with a re-verify request to us. **NEXT (future, awaited): the #364 re-verify** (re-drive the same bound-Sindra turn against :8081 once #364 deploys → expect "Name is Vuong" to inject/recall). Standing items unchanged: snapshot-cursor adoption (operator-sequenced). (3) **brokkr R39 Phase-1 Arm-0 fusion bake-off** wants our frozen 7-fact store exported DECOMPOSED (data contract in `~/development/brokkr-smithy/research/R39-memory-salience-dreams-surfacing/phase-1/re-drive-design.md`; #364 reconciliation GATED on brokkr's re-drive-complete). **KEY STRUCTURAL FINDINGS:** our store persists ONLY `salience` (+embedding) — `similarity`/`combined` are WT-side query-time (`bifrost_memory_store.py:702`, combined=sim×salience); **person-prime is query-LESS → carries NO similarity** (= brokkr's Arm-1 finding); recovered per-fact similarity from the verify SEARCH log (name=0.354, stale=0.463, ×salience-1.0 = combined, matches brokkr's 0.354/0.46 grounding); **BOTH the true "Name is Vuong" AND the stale negative have salience=1.0** (salience can't discriminate — stale wins on recency + sim only); they share the (user,name) supersession slot (Phase-0 Q3). **⚠️ SPECIMEN CONTAMINATED BY MY VERIFY:** the turn-1 drive ADDED 3 chunks (16:28, re-extractions incl. a 3RD "name unknown" negative) → store is now **10, not 7**; the original 7 intact (≤08:42). **✅ EXPORT SENT (operator approved VERBATIM 2026-07-16):** "nothing there is really a concern." Classifier had blocked the `/mnt/smithy` shared-mount write (PII) → routed to Vuong → he chose full verbatim; delivered INLINE in brokkr thread `01KXMN7NR54…` (scoped to brokkr, not the open mount) as 7-row JSONL (fact_text + similarity[from verify SEARCH log]/salience[stored]/combined/recency/subject/relation/is_true_identity/is_stale_negative/source/provenance_note). **NEW #364 EVIDENCE — SUBJECT-PROVENANCE bug (operator-caught):** fact 75aa3110 ("prefers clear parameters Intensity/Mood/Willingness…") is NOT a user fact — Vuong never said it; it's **SINDRA'S OWN system-prompt scripted behavior mis-extracted into the USER memory partition** (subject flipped→assistant in export); e35b9dfe (closeness) maybe same (unsure). So the extractor leaks CHARACTER-self facts into user memory — a subject-correctness axis orthogonal to the recency/threshold/stale-negative story; flagged to brokkr (Arm-1?) + worldtree-dev (widen #364?). **SPECIMEN now 10 rows** (my verify added 3 re-extractions incl. a 3rd "name unknown"); original 7 intact + exported; flagged both peers. **HOLD HONORED — NO cleanup** (R39 needs it frozen-dirty; earlier "nudge" option OFF). All 3 peer loops replied (bifrost-dev/brokkr/worldtree-dev); harness ready for re-drive on brokkr/WT signal. WHY: Sindra didn't remember Vuong's name across sessions; worldtree-dev root-caused it — WT injects a recalled fact only if combined score (sim×salience) ≥ **0.45** (`auto_inject_combined_score_threshold`, `core/memory/context_promotion/config.py`), and recall is per-turn **query-gated**, so moderate-sim durable facts (name hit ~0.40) never inject. The designed **turn-0** fix is WT **#349 person-prime** — a query-LESS top-N-by-recency durable-fact injection — capability-gated on the store advertising **`updated_at` in `sort_fields`** at the Bifrost handshake, DARK for our provider until now. Fix (ZERO Worldtree change): implement the sorted `scan` verb + advertise the capability. **✅ CODE COMPLETE + COMMITTED (2026-07-15):** contract amended + validated; `scan` impl in `src/ratatoskr/provider/memory_store.py` (globally-ordered by indexed `updated_at`, live-only via `_is_live`, offset-cursor; `has_more` peek so an exact-fill page emits no empty trailing cursor = reference-parity; `describe_store` advertises `sortable_chunk_fields=[{updated_at}]`; expression index `idx_chunks_updated_at`); **TDD 7/7 GREEN** (scan_recency tracer + live_only + scope_isolation + unadvertised_sort + person_prime_record_shape + cursor pagination + **`parity_vs_reference` #195**); NUANCE resolved — the bifrost ref `InMemoryMemoryStore` **READS** `updated_at` from the record (never stamps it; its own comment confirms), identical to ours, so order-parity holds over the live set (ref does NOT lifecycle-filter, so parity is defined over live chunks only). **Full suite 638 green.** Committed as **two patches**: `8fc757a` scan (**v0.20.11**) + `a9c521a` Sindra holodesk first-message preset (**v0.20.12**, split out as a separate concern from the soong-lab redefine). **heid-bug-hunt RETURNED + TRIAGED** (panel Gróa/Hulda/Regin, thread `01KXK5XTYHV8TGEDRAZV8GRXWC`; base-ref 39050c3, code-only diff). **FIXED (v0.20.13, `25ccb5c`):** the non-dict `sort` crash — a truthy non-dict `sort` (`"updated_at"`/`["updated_at"]`/int) hit `(sort or {}).get(...)` → AttributeError instead of InvalidArguments; added isinstance guard (Gróa#1/Hulda#2, confirmed-from-code). **SURFACED (operator design decision, NOT auto-adopted):** offset-cursor cross-page duplicate/drop under concurrent mutation (all 3 arms) — real divergence from bifrost `InMemoryMemoryStore` (snapshots the ordered id-list per opaque cursor + `ScanCursorExpired` TTL); BUT person-prime uses a SINGLE page (no cursor) so the actual use isn't exercised → operator picks: adopt reference snapshot-cursors vs keyset vs accept + honestly scope INV-010 to a static store. **NOTED (reference-parity / accept-known-risk, no fork):** full-table materialize-then-Python-filter (matches ref's own O(store) iteration), unbound limit, falsy scope coercion `[]`→`{}` (parity w/ search + ref), lexical timestamp ordering (WT writes consistent ISO-8601 UTC); `_is_live` dead-marker completeness (Gróa#6 needs-repro) → confirm canonical marker set w/ worldtree-dev, don't guess. **REFUTED:** Regin's mixed-type ORDER BY "crash" (SQLite orders by storage class, doesn't raise) + its self-retracted `_is_live`/None item. **✅ DEPLOYED (2026-07-15, operator-authorized):** (5) **DONE** — restarted BOTH `:8392` combined provider (new pid, old 982284 gone) AND `:8765` web on **v0.20.13** via the env-preserving `scratchpad/relaunch_by_pid.py ` (captures /proc cmdline+environ+cwd → byte-identical config, only code changes; self-daemonizes). Verified: `:8392` up (uvicorn startup complete; `/`→404 = no root route = serving), deployed `describe_store` advertises `sortable_chunk_fields=[{updated_at}]`; `:8765` up HTTP 200 (log "ratatoskr-web v0.20.13"). **Added the missing REQUIRED `RATATOSKR_MEMORY_EMBEDDING_DIM=1024` to `env.sh`** (gitignored; was absent → bare `source env.sh` restart would crash). (6) **DONE** — pinged **worldtree-dev** (thread `01KXK86PZ9M8J53A4SFXREEMW7`) to verify turn-1 recall against personal :8081. Store has **7 REAL live Sindra durable facts** ready (agent_id `ratatoskr:sindra`, worldtree_scope `end_user`, scope `{end_user:ratatoskr-tui}`, all carry `updated_at`, accumulated across 3 sessions today) → top-N-by-recency has real data to inject. AWAITING their turn-1 verdict (monitor armed). (7) **F3 cursor RESOLVED — operator ACCEPTED offset-for-v1** (keep offset cursor; person-prime single-page is correct/conformant). INV-010 amended to mark the cursor **v1-provisional / KNOWN DEVIATION** (multi-page continuation diverges from bifrost's protocol snapshot-cursor contract — the `bifrost.memory` dispatch drops `sort` on continuation citing "snapshotted order is authoritative" + maps `ScanCursorExpired→410`; our offset cursor doesn't snapshot → dup/drop under concurrent write, and never expires). **Durable fix = a BIFROST completeness concern (operator's catch), ROUTED to bifrost-dev** (thread `01KXK7MDTY0B6VEDF3SVGSKY9R`): bifrost's conformance suite has ZERO scan/cursor coverage, so a non-snapshot cursor passes silently — asked bifrost-dev to (1) confirm snapshot-cursor+ScanCursorExpired is the NORMATIVE store contract, (2) add scan/cursor conformance coverage; we'll adopt the reference snapshot-cursor semantics IF they rule it normative (offered to be their canary as first non-reference scan implementer). AWAITING bifrost-dev ruling; monitor armed. +**Substrate / environment (current):** branch `main`, HEAD is the person-prime tip (v0.20.11→v0.20.14 +all committed; **NOT pushed** — push is the operator's call); origin `git@gitea.phasefinal.com:vh/ratatoskr.git`. +bifrost `==1.1.1` / wire v0.7 (→ 1.1.3 on cursor adoption); Worldtree openapi vendored 2.3.0; suite +**639 green**. Personal WT on **b79** (client-side live-only + additive `lifecycle_state` scan arg, +which our INV-009 accept-and-ignores). The combined **:8392** provider (memory+affect) + **:8765** web +are THE surfaces, run as dev-box BACKGROUND SHELLS — restart via `scratchpad/relaunch_by_pid.py ` +(env-preserving, self-daemonizing; find pid via `ss -ltnp | grep `). `env.sh` now sets the +REQUIRED `RATATOSKR_MEMORY_EMBEDDING_DIM=1024` (was missing → bare `source env.sh` restart crashed). +Keys env-only mode-600 in `~/.config/ratatoskr/provider.env` + `RATATOSKR_ADMIN_API_KEY` (7 read +scopes, personal-:8081-only; Heimdall keys per-instance). `graphify-out/` runs dirty (auto-regen, +never stage). v1 = full Worldtree I/O coverage, cuts when WT tags 1.0 (`docs/coverage-map.md`). -**✅ soong-lab interop ESTABLISHED (this session) — soong-lab is now our Tier-3 agent-authoring studio.** ratatoskr consumes soong-lab bundles → WT `agents.define`, and ships agents back as bundles. Sindra round-trip PROVEN (soong imported her `resume` half through real `import_bundle`). soong-lab export/importer contracts PINNED via canonical-sync (`canonical_source=soong-lab` @ f434016, commit `39050c3`). Role-grounding (worldtree-dev): the 4 soong-lab ROLE_CHOICES = WT model-role slugs 1:1 BY NAME — `character`/`thoughtful-character` need the `character` grant (HELD via Sindra → define-able now); `assistant`/`thoughtful-assistant` need `foundational` (confirm/mint routed to infra-ops, in flight; only for assistant-type bundles). `character-rp` ≡ `thoughtful-character` (same Deckard seat + grant). **Sindra REDEFINED from the bundle** (`/tmp/sindra.json`, operator "update to match"): persona immutable → DELETE+redefine (payload validated via a throwaway `sindra-probe2`→201 BEFORE deleting the real one), `memory:{}` preserved, motivational string→WT-object mapped (Tried/abandoned), role→`thoughtful-character`, first_message preset updated. Verified live (thoughtful-character, 25574-char prompt). - -**✅ R38 (brokkr, worldtree #362) — ratatoskr = probe-runner IN PRINCIPLE (pre-contract, no-rush).** R38 = the M9 Worldtree KB/RAG (Muninn/Mimir Chroma), NOT ratatoskr's Bifrost Tier-3 memory (M7) — I disambiguated the store. My load-bearing catch (the OUTCOME half of a real-Tier-B eval — used/rejected/downstream-success — is the CALLER's to log, not the store's) is their captured keystone. Probe-runner role accepted in principle; formal commit + M9-only-vs-widen-to-M7 = Vuong's scope call (brokkr surfacing). brokkr sends the probe spec once worldtree defines the retrieval endpoint. - -_The detail below is PRIOR-CYCLE shipped history (P06 eval CLOSED, #355 CLOSED, the v0.20.x web-UI arc) — superseded, kept for reference; the durable records are in Recent decisions._ - -_As of 2026-07-12:_ - -**✅ COMPLETE — R34/R35 P06 powered memory-half eval (driven, scored, mechanism validated; ratatoskr drive-role CLOSED both sides).** ratatoskr drove all **308 memory runs** (divergence 168 / floor 80 / sliding 60) through personal WT's live producers, dropped `memory_results.jsonl` (sha256_16 `cbabaf16979cb4ec`) to brokkr's P06 `results/` dir, and brokkr scored it (**R35.45**, findings + verdict committed brokkr-side). **Headline: the authored `psychological_profile` IS the mechanism** — salience-divergence authored **0.618** vs stripped **0.235 ≈ null (0.25)**, delta **+0.382**; the OCEAN scaffold alone does NOT differentiate (negative control HOLDS). Q1 primary is a REAL effect (above the 0.40 noise-floor) but **inconclusive on strength** (0.618 < the preregistered 0.70 bar) — the 0.62→0.70 lift is a FUTURE optimization phase (brokkr's lever bet: richer formative-history seeds per P03), a cheap re-drive on the same proven harness when it preregisters. Secondaries hold: Q3 firewall **0.978** grounded, Q2 floor 0.938, Q6 sliding parity +0.049 (n=12 after 7 `deferred_budget` sliding exclusions — the budget hazard we flagged landed), Q4 affect Deckard 0.75 / Magidonia 0.70 (graded, within noise; banked earlier as `affect_results.jsonl`). Two ratatoskr flags landed materially: the stripped-is-not-empty correction caught a false Q3 firewall-fail (0.562→0.978), and the Q5 disambiguation question became the headline win. Threads: vendor/verdict althing `01KXD39NWW05`, eval thread `01KXAN073B`. Standing offer to brokkr: second-eyes on the 2 borderline Q1 calls IF the Selene blind-judge flags them. - -**The eval harness (PROVEN + reusable for the optimization-phase re-drive):** `scratchpad/p06_driver.py` (two-path — memory via `POST /admin/producer-probe {agent_id, messages, prompt_path}`; affect via bound-turn + `:8392` /affect/state poll; per-run isolation, `--pace-seconds`, abstain-aware, psych_profile_present binding-tripwire) + `p06_bind.py` (defines the 6 eval agents: sindra/Torvald auth+strip on Deckard, Ilva on Deckard+Magidonia) + `p06_bindings.json` + `eval_profiles_WIRE_READY.md` (sindra relational / Torvald operational-opposite / Ilva high-N) + `manifest_memory.jsonl` (the 308 memory runs, filtered from brokkr's canonical 348). Binding integrity was PERFECT on the drive: psych_profile_present authored 154/154 True, stripped 154/154 False, 0 mismatches, 0 probe-errors. Probe key at `~/.config/ratatoskr/probe.env` (mode 600, scope `admin.memory.probe`). - -**Deckard memory extraction is REASONING-OFF (operator-directed 2026-07-13, LIVE):** the memory extractor sends `chat_template_kwargs.enable_thinking:false` on the char-rp-reasoning seat → ~5s extraction, not the 45s verbose-CoT hang. **Scoped to the memory extractor ONLY — affect + RP stay reasoning-ON.** Landing it took an infra-ops surgical `docker restart` of personal `:8081` (ModelRegistry boot-caches providers.yaml at `__init__`, so a same-image redeploy is a config-reload NO-OP — see Tried/abandoned). - -**✅ DISCHARGED — WT #355 validation CLOSED (2026-07-13).** ratatoskr's telemetry root-caused the char-rp-reasoning turn-never-terminates wedge; I coordinated the fully-instrumented re-drive (infra-ops armed netns-pcap + py-spy; soong drove the 8-turn accumulating RP-with-tools repro) and the fix is CONFIRMED — wedging turns cancelled cleanly at the 300s stall-watchdog (Slice-C cancel-INDEPENDENT terminal) vs the pre-b60 16-min-no-terminal baseline. b61 additionally fixed the orthogonal over-budget trigger; resume-durability gap → WT #356. Full record in Recent decisions. (Prior-cycle framing: the wedge was an over-budget `trim_messages` return + terminal-suppression from the stall-watchdog cancel stuck in httpx `AsyncShieldCancellation`.) - -_The detail below (the v0.20.x web-UI arc, #347 authored-history, sindra memory-fix) is PRIOR-CYCLE shipped history — superseded by this section's top; kept for reference, prune in a future snapshot._ - -**✅ SHIPPED — web-UI iteration-3, all three queued items (`v0.20.7`, patch, agent-discretion; 631 tests green; :8392 combined provider + :8765 web both restarted on the new code).** - -**(A) Design prototype iteration-3 adapted into `index.html`** (re-pulled `Ratatoskr Console.dc.html`, project `bc0b65d1-…`): (1) sparkline **grid bg** — `` in the hidden defs + a bg `` behind every relation-row spark polyline; (2) **PAD strips → per-turn Δ bars** — REMOVED the vertical polyline strip (`stripPoints`/`proj3` gone) for `padDeltas`→`deltaStrip`: a 12-cell HTML column (newest at bottom) of diverging bars offset L/R of a center line by that turn's Δ (magnitude→width, age→opacity, zero→faint center dot); head legend now "Δ/turn · last 12 · newest ↓"; (3) **mood orbit → DIMETRIC OPEN BOX** (viewBox 124×140, az35/el25, D-right/A-left-back/P-up) — ghost A×P wall (P readout) + D×A floor, `orbitProj/orbitShadowY/orbitWallPt/orbitAxisPt` projections, **JS-driven animated replay** (`orbitFrame` rebuilt per rAF by a singleton `startOrbitAnim` reading live `ORBIT_HIST`; reduced-motion → static final-state; no SMIL). Playwright-verified (dimetric frame + 17 dyn children + 21 Δ-bars + 5 grid rects; dark+light screenshots). - -**(B) Memory viewer SHIPPED + the 0/0 mystery ROOT-CAUSED.** New non-bifrost debug read `GET /memory/chunks?agent_id=&end_user_id=` on the combined `:8392` provider (`RatatoskrMemoryStore.list_chunks` + `count_chunks` + shared `add_memory_read_route`, wired into `build_memory_provider_app` + `combined.py`; **end_user STRICT, agent_id LENIENT** so `{end_user}`-only WT chunks aren't hidden; returns `{chunks,count,total}`, empty=200 not 404) → web proxy `GET /api/memory/chunks` (server-supplied end_user, new `RATATOSKR_MEMORY_READ_URL` env, default `:8391`, set to `:8392` in env.sh) → a live-polling MEMORY console pane (`loadMemory`/`renderMemory`/`setMemHead`, polled on open + post-turn). **ROOT CAUSE of the operator's 0/0** (settled via a bound 6-turn probe + op-feed): the Bifrost memory plane **binds and grants fine** (handshake `caps_requested:[affect,memory]` → `caps_granted:[memory,affect]`), but **sindra dispatches ZERO memory ops** (not even a recall search) — her reset-clean agent definition has **no `memory:{}` block**, so Worldtree never runs the memory pipeline for her. NOT a bind-grant failure, NOT promotion-timing. **PROVEN end-to-end** with a throwaway memory-enabled `ratatoskr:memprobe` (defined with `memory:{}`): 6 bound turns → 6 `memory.search` recalls + **4 `memory.upsert_many` → 4 real chunks in `memory.db`** → the pane renders all 4 (content·scope·origin·revision) live. **⚠ LEFTOVER debug state (operator chose KEEP):** `ratatoskr:memprobe` agent on personal WT + test chunks (scope `{end_user:ratatoskr-tui}`) sit in the live `memory.db` — harmless (make the pane show real data); `scripts/reset-sindra-stores.sh` or `DELETE /agents/ratatoskr:memprobe` clears them. **✅ SINDRA MEMORY FIXED (operator-approved, 2026-07-07):** DELETE+redefined her WITH `memory:{}` (prompt byte-identical, `role=character`, OCEAN `{O:0.8,C:0.3,E:0.9,A:0.4,N:0.2}` preserved — pleasure-verified vs the mood-fix setpoint 0.418; backup at scratchpad `sindra_backup.json`). PROVEN: she went from ZERO memory ops → full recall (Orion fact @ cosine 0.988) **+ promotion** (her own "systems architect" fact upserted; store grew to 9→11 chunks). Reusable redefine script: scratchpad `redefine_sindra.sh`. Note: DELETE+redefine is the ONLY path (persona+memory immutable post-define); the destructive `DELETE` tripped the harness auto-mode guard → operator ran it via `!`. - -**(C) Markdown pass-2 SHIPPED** — `markdownSafe` extended: GFM pipe tables (`mdTable`, alignment colons), indentation-nested lists (stack of `
    `/`
      `, child list inside the open `
    1. ` = valid nested HTML), ordered-list `start=N` numbering, and streaming robustness (unterminated fence → partial code block; header-without-delimiter → paragraph until the delimiter streams in; never throws). esc-first → INV-004 held. Playwright-verified all cases. - -**SHIPPED — web UI redesign via Claude Design (`v0.20.0`, MINOR, operator-approved).** The Claude Design prototype **`Ratatoskr Console.dc.html`** (project `bc0b65d1-a33e-422a-8bc1-3635c9112775`) was pulled via `DesignSync get_file` (design scopes already granted this session — no `/design-login` needed) and adapted into `src/ratatoskr/web/static/index.html`: translated OUT of the `.dc.html` dialect (``/``/``/`{{}}`/`DCLogic`/external `_ds/` CSS — none runnable) into single-file/no-CDN/vanilla, with ALL real `/api/*` fetch + SSE wired into its DOM (endpoint set + SSE vocab unchanged from the prior SPA — ported verbatim, only DOM hooks re-targeted). New shape = a **3-column command-console**: left engine-ticker rail (DEBUG+ADMIN+tool/turn-lifecycle MERGED into one timeline via `tickerAdd` + a tools-armed chip list + a FULL-detail Bifrost rail pane) · center conversation (per-turn INLINE chain-of-thought, replacing the Think pane) · right RESIZABLE affect console (dominant/canonical-mood centerpiece + bipolar PAD faders EACH with a turn-to-turn Δ+sparkline + a P×A mood orbit + relations metric rows + canonical directive). ADDED (round 2, operator-requested): a **light/dark theme toggle** (dark default; FULL token override — surfaces+fg+borders+accent-as-text, since the designer's light theme only did surfaces → would've been light-on-light) + a **full-detail Bifrost pane** (endpoint/connected/consumer/caps/tools) + fixed the **engine-ticker spine** (was a container-anchored `::before` that scrolled out of view on auto-scroll → re-anchored to a content-height `.ticker-inner` wrapper) + **per-fader PAD turn-to-turn Δ+sparkline** (fills the room beside each meter, from the deduped-per-turn AFFECT_HIST) + an **INLINED data-URI favicon** (operator's `/home/lkraven/rata.png` — chibi aurora squirrel — downscaled 1024→64px via PIL, ~8.6KB base64, kills the /favicon.ico 404). ALL server routes UNCHANGED (**84 web tests green**). Verified BOTH lenses: `pytest tests/test_web_*` (84) + node Playwright drove the real UI end-to-end against personal :8081 (session open → Sindra seeded greeting → live turn SSE → affect console + relations + bifrost detail; theme toggle + PAD deltas + ticker spine + no-favicon-404 all confirmed, dark+light screenshots). `:8765` restarted on the new code. Contract `web_debug_surface.contract.md` amended in-commit (v0.20.0 presenter renames: `renderBifrostState`→`renderBifrost`, `renderAffectPane`→`renderConsole`, `setPersonaStrip` removed; INV-001/INV-004 held). **HONEST-SHAPE call (INV-001, agent-discretion within settled policy):** the dominant-emotion centerpiece shows a real OCC emotion (Tier-1) OR the CANONICAL mood word (Tier-3 e.g. Sindra→"positive and energized", dimmed) OR "—", NEVER a fabricated emotion; the affect-derived grid drops non-emitted intensity/decay-τ, shows only real/client-derived cells. **OPEN (operator's call):** the per-fader PAD Δ placement is a sensible default — operator offered to have the designer spec the exact treatment (hooks are in place to swap it). **`v0.20.1` patch (operator-reported UI):** fixed the relations sparkline overflowing onto the `n` (evidence-count) column — the sparkline grows one char/sample (HIST_CAP=24) and overflowed its fixed grid cell, covering `n`; now capped (relations last-8, faders last-7) + `overflow:hidden` clip; verified via Playwright injecting a 24-sample history (sparkline→n bounding-box overlap = 0). ADDED native `title` mouseover hints on all 3 PAD faders + every relationship metric row (meaning + range; static METRIC_HINTS, esc()'d). Added `state.lastSnap` (console can re-render without a refetch). Playwright-verified. - -**SHIPPED THIS SESSION (all pushed; origin/main == `d75c4e8`; code tip `v0.19.9`) — details in Recent decisions:** the whole **#347 authored-history-write** arc landed end-to-end — OpenAPI re-vendor 2.2.0->2.3.0 (`75da676`), the CONSUMER side (`v0.19.6`: `write_authored_history` + `get_session_messages` + `--seed-first-message`, **live-proven on personal :8081** via a rule-based Heimdall allow — the PDP is rule-based NOT scope-on-key, policy user_id=ratatoskr->ALLOW/others->DENY-hide-404), persona_state `{pad:{pleasure,arousal,dominance}}` canonical alignment (#317) + Tier-3 prose re-vendor (`v0.19.7`), the **first-message-preset AUTO-SEED** (`v0.19.8`: new module `ratatoskr.first_message` wired into all 3 session-create paths, best-effort never-raise/never-block; heid-code-review + heid-bug-hunt hardened), and the web now RENDERS the seeded first-message (`v0.19.9`: new `GET /api/sessions/{id}/messages` route + SPA `loadTranscript`, Playwright-verified). Coverage-map re-converged **REST 19/41**. **Sindra:** her card was PATCHed (the `Startup:` workaround moved into a #347 first-message; non-destructive PATCH — OCEAN/persona/memory intact), and she's currently **RESET clean (0/0)** on the provider stores. - -**Prior arcs this session (2026-07-04 -> 07-06), both with worldtree-dev (a tooling script + proposal docs; the #347 CONSUMER work above is the new production code):** - -**(1) Authored-history-write primitive -> ACCEPTED as Worldtree #347 (Worldtree-owned).** A SillyTavern-style "first-message" (inject a character-authored opening) generalized to an engine primitive: **write a turn into a session's ledger WITHOUT generation, seed-only, side-effects off by default.** It cannot be done client-side (the messages `role` field is a *model-role* override, not an author-role -> `role:"assistant"` 404s; a model-visible authored turn needs engine support). Arc: drafted `docs/proposals/authored-message-injection.md` -> **heid panel pressure-test** (3/3 convergence: recentered on "non-generating write" not author-role; narrowed v1 to append-only+create-time; bounded `effects` enum; dropped edit/regenerate as history-mutation) -> revised -> committed (`c457520`) -> handed to worldtree-dev -> **accepted as design item #347.** worldtree-dev wrote the v1 contract (rev 1.1); **I validated the wire as reference consumer (green).** v1 shape: `POST /sessions/{id}/history`, `author=assistant` only, `effects=none` only, `idempotency_key` REQUIRED (per-session), **model-invisible provenance** (renders byte-identical to a lived assistant turn -> first-message immersion preserved; provenance audit-only), **event-silence** (no turn.started/done, no Bifrost wire for a seed; the 201/200 IS the write-ack), `seeded` lifecycle phase (not exposed on read paths). **Heimdall-gated with hide-existence** (grant `session.history.write`; ungranted tenant -> 404 NOT 403, undiscoverable in /capabilities -> consumer must treat 404 as feature-absent -> fall back to a model-generated greeting, never capability-probe). **Provider constraint:** a create-time first-message makes the assistant seq-0; vLLM/openai_compat tolerate assistant-first (sindra = openai_compat, unaffected), Anthropic-family providers 400 the next generation. **Waiting on worldtree-dev:** #347 TDD (their heid->contract->review workflow) + the consumer-facing 2.3.0 persona/motivational/memory schemas -> then re-vendor our pinned openapi 2.2.0->2.3.0. - -**(2) Sindra's stuck-neutral mood FIXED** (operator-driven "reset + smoke" that flushed out two real upstream problems). Chain: her OCEAN lived only in prompt TEXT, never declared as a structured persona -> the Tier-3 mood engine ran on neutral defaults. Fix = declare OCEAN via the **define-time `persona` field** (immutable via PATCH -> requires DELETE+REDEFINE). Along the way my "the persona didn't store" call was WRONG (persona_state/envelope are Tier-3-blind, see Tried/abandoned); worldtree-dev found a real engine bug **#348** (single-letter vs spelled-out OCEAN keys -> a declared OCEAN silently resolved to 0.0/neutral; fixed in b21, shipped to personal as b22); then a clean bound-egress read STILL neutral -> the **personal container was running a stale image** (the b22 deploy was a pull-only no-op racing the main build; infra-ops force-swapped run 8211, verified `2.3.0` / `879cefe`). **VERIFIED FIXED:** bound mood-smoke reads `(0.448, 0.267, 0.316)` ~= the OCEAN-derived setpoint `(0.418, 0.249, 0.328)`. Sindra is currently reset clean (0/0) on `role=character`; her persona is stored + correct (**no re-define needed again**). - -**(3) R30 CLOSED** (operator steer 2026-07-04, relayed via worldtree-dev): graduated on offline-tests + human face-validity, NO deployed gap-injection run (it was confirmatory-not-measuring per brokkr's S0 reframe; offline tests already cover the OU formula + both directions). My gap-injection harness (read/predict/record; write side stubbed; `predict()` self-validated vs brokkr's N=0 anchors) is BANKED at `diag/r30-gap-injection-harness` (`7156b25`-era) for the PARKED powered true-tau study. - -**Persona-declaration shape (Worldtree #343/#348, live on personal b22):** `POST /agents/define` `persona:{ocean:{O,C,E,A,N: float[-1,1]}}` (single-letter keys EXACTLY -- missing/extra -> 422 `persona_ocean_required`; out-of-range -> 422); **NO baseline PAD** (resting setpoint DERIVED from OCEAN via Mehrabian: pleasure=0.21E+0.59A+0.19C-0.32N, arousal=0.15O+0.30E-0.57A+0.15N, dominance=0.25E+0.17A+0.10O-0.14N); negative-channel gain + per-axis decay-tau derive from N. `valence` deferred (422 `layer_deferred`); `motivational`/`memory` active (#187/#189). Persona is **write-once at define, immutable thereafter** (PATCH takes ONLY system_prompt + role). **`role` supersedes `model`** -- set a role (`character` / `character-rp`), Worldtree resolves the model; #344 (b19) fixed the model-field to surface the ROLE, not the resolved catalog_id. `character-rp` = a reasoning-tuned RP config (gen-reasoning + temp 0.75 + RP extra_body); `character` = plain non-reasoning. The `tier3.py` client CLI is STALE (has `--model`, no `--role`; model is now immutable) -> role/persona set via raw curl. - -**New tooling: `scripts/reset-sindra-stores.sh`** (`0a8784c`) -- one-command self-service provider-store reset: stop the combined :8392 provider -> move memory.db+affect.db to a single ROLLING backup (`db-reset-backup/`, gitignored via *.db*; `--hard` skips it) -> restart empty -> verify 0/0. Codifies the manual reset flow done repeatedly this session. **The combined `:8392` provider is THE provider now**; the separate `:8390` (affect) / `:8391` (memory) single-plane providers were pruned as stale duplicates. To drive a BOUND session from the CLI use `--new --bifrost-url http://10.100.10.50:8392` (the CLI's `--bifrost-plane affect/memory` map to the pruned :8390/:8391 -> unreachable; `combined` is not a `--bifrost-plane` choice). - -**Standing (carried from prior snapshots, still true):** the web surface (`ratatoskr-web`, :8765) is the operator's PRIMARY debug surface at full TUI pane parity (v0.19.5); the **v1 coverage-audit has CONVERGED** -- REST 17/40 (zero in-scope gaps, 23 excluded-by-design), SSE 11/11, Bifrost provider planes 8/8 live-proven; the living ledger is `docs/coverage-map.md`; **v1 cuts when Worldtree tags 1.0** (ratatoskr v1 = full Worldtree I/O coverage). Debug-observability core complete (Persona/Tools/BifrostState/AdminEvents). Substrate pins: **bifrost `==1.1.1` / wire v0.7** (bumped 2026-07-12 from 1.1.0 — the frozen-v0.6 serialization fix, v0.20.10; prior 1.1.0 bumped 2026-07-07 from 1.0.0; NOW WIRE-ALIGNED with Worldtree personal-b47 which adopted wire-v0.7 — bound Tier-3 fully restored 2026-07-10; keeping 1.1.0 was load-bearing, see the `[2026-07-10]` handshake decision); Worldtree openapi vendored **2.3.0** (re-vendored 2026-07-06 for #347 `POST /sessions/{id}/history`; drift-clean vs source), pinned + drift-gated in `.corviduo-canonicals.toml`; **suite 631 green.** **Personal WT on b61/wire-v0.7** (deploy train through this cycle: b35→b44→b46→b47→b60→b61; b60 landed the #355 STICK fix, b61 the orthogonal over-budget trigger fix + a llama.cpp reasoning-budget seat). **Drift-check note (RESOLVED 2026-07-13):** the two `tolerate_drift` WARN pins (`worldtree-affect-egress-consumer-reference-v1` + `worldtree-conversation-api-spec-v1`) were RE-SYNCED — the drift was a benign 2-line R32-1B doc note (PAD `[-1,1]` → unbounded latent `z` w/ `~±10` wire bound) documenting the unbounded-z change ratatoskr ALREADY adopted in v0.20.9, NOT the anticipated we-framing conditional (that remains a FUTURE coordinated re-vendor when the brokkr render epic lands). All canonicals now drift-clean. **NEW vendored canon (Vuong-directed via brokkr):** the R34/R35 psych-profile reference — `brokkr-psych-profile-authoring-spec-v1` + `brokkr-psych-profile-parameters-v1` — pinned under `docs/vendor/brokkr-r34-psych-profile/` (canonical_source `brokkr-smithy`, tolerate_drift; the authoring-spec GOVERNS on conflict with the parameter distillation; brokkr owns both + pings on change). Keys env-only mode-600 (consumer/Heimdall in `~/.config/ratatoskr/provider.env`; admin `RATATOSKR_ADMIN_API_KEY` = 7 read scopes, **personal-:8081-only**; Heimdall keys are PER-INSTANCE). Provider identity settled -- ratatoskr owns both ends of the Bifrost round-trip; `ratatoskr:sindra` is the owner-scoped Tier-3 agent (invisible to `GET /agents`; check `GET /agents/:` with the owner key). Providers run as dev-box BACKGROUND SHELLS. `graphify-out/` runs dirty (auto-regen, never stage). Branch `main`, HEAD `39050c3` (origin/main synced through the soong-lab canon vendor; the person-prime scan build is UNCOMMITTED on top — contract + memory_store.py + tests + first_message.py); remote `origin -> git@gitea.phasefinal.com:vh/ratatoskr.git`. Open/deferred: #10 (subject-migration watch); the relational-dynamics-arc verify (deferred, bind mechanism known: `--bifrost-url :8392`); the P06 optimization-phase re-drive (future, brokkr brings the prereg); the we-framing-conditional affect-egress re-vendor (future, when the brokkr render epic lands — the R32-1B doc-note drift is already resolved). (WT #355 loop-in obligation DISCHARGED 2026-07-13; WT #356 resume-durability gap is worldtree-owned.) **Debug state CLEANED (2026-07-13, reverses the prior KEEP):** provider stores reset to 0/0 + `ratatoskr:memprobe` deleted — clean slate for the Sindra run, no leftover debug state. +**Other live threads:** soong-lab = our Tier-3 agent-authoring studio (bundle↔define round-trip proven; +Recent decisions `[2026-07-14/15]`). R38 (brokkr / WT #362) = ratatoskr-as-probe-runner IN PRINCIPLE, +pre-contract, Vuong's scope call. `ratatoskr:sindra` is the owner-scoped Tier-3 agent (invisible to +`GET /agents`; check `GET /agents/:` with the owner key). Open/deferred: #10 subject- +migration watch; relational-dynamics-arc verify (bind via `--bifrost-url :8392`); P06 optimization-phase +re-drive (future, brokkr brings prereg); we-framing-conditional affect-egress re-vendor (future). WT +#356 resume-durability = worldtree-owned. ## Recent decisions @@ -232,9 +223,12 @@ decision. Captures rationale that won't be obvious from code alone. - `[2026-07-13]` **b61 adopted as the personal target — the orthogonal over-budget TRIGGER also fixed.** worldtree shipped b61: the provider stream loop terminates on `finish_reason` + a per-read idle deadline + a 300s wall-clock backstop (no longer waits on the SDK `[DONE]` sentinel), plus a custom llama.cpp reasoning-budget multi-terminator seat → the runaway is bounded at BOTH layers. The #355 STICK (no-terminal) and its trigger (why it wedges) are now separately fixed. Resume-durability gap → **WT #356** (worldtree-owned). - `[2026-07-13]` **Cleaned + prepped for a Sindra run (operator: "clean up everything + prep").** Reset provider stores to 0/0 (`reset-sindra-stores.sh`, rolling backup `db-reset-backup/`); deleted the throwaway `ratatoskr:memprobe` agent via the operator's `!` (destructive DELETE trips the auto-guard — reverses the earlier KEEP). `ratatoskr:sindra` verified present + persona-intact on b61. Environment Sindra-run-ready (web :8765 + provider :8392 both up, single healthy provider instance); operator driving the run interactively. - `[2026-07-14]` **soong-lab adopted as our Tier-3 agent-authoring studio (operator-directed).** ratatoskr consumes soong-lab bundles → WT `agents.define`, and ships agents back as bundles. Sindra round-trip proven (soong imported her `resume` half through real `import_bundle`); soong-lab export+importer contracts pinned via canonical-sync (`canonical_source=soong-lab` @ f434016, commit `39050c3`). The 4 soong-lab ROLE_CHOICES = WT model-role slugs 1:1 by name (worldtree-dev); `character`/`thoughtful-character` need the `character` grant (held), `assistant`/`thoughtful-assistant` need `foundational` (routed to infra-ops). NOT on the v1 coverage-map (sibling-studio interop, not a WT I/O point) — operator chose to pursue anyway. -- `[2026-07-15]` **Cross-session recall failure root-caused → the person-prime `scan` build.** worldtree-dev: WT injects a recalled fact only if combined score (sim×salience) ≥ 0.45 (`auto_inject_combined_score_threshold`) AND recall is per-turn query-gated → moderate-sim durable facts (name ~0.40) never inject. Designed turn-0 fix = WT #349 person-prime (query-less top-N-by-recency injection), capability-gated on the store advertising `updated_at` sort — dark for our provider. Fix = implement the sorted `scan` verb + advertise `sortable_chunk_fields` (ZERO WT change). Contract-first (un-defers bifrost `scan`); skipped heid-contract-review (external spec from worldtree-dev, validated point-for-point). In-flight — see Current state + the uncommitted diff. +- `[2026-07-15]` **Cross-session recall failure root-caused → the person-prime `scan` build.** worldtree-dev: WT injects a recalled fact only if combined score (sim×salience) ≥ 0.45 (`auto_inject_combined_score_threshold`) AND recall is per-turn query-gated → moderate-sim durable facts (name ~0.40) never inject. Designed turn-0 fix = WT #349 person-prime (query-less top-N-by-recency injection), capability-gated on the store advertising `updated_at` sort — dark for our provider. Fix = implement the sorted `scan` verb + advertise `sortable_chunk_fields` (ZERO WT change). Contract-first (un-defers bifrost `scan`); skipped heid-contract-review (external spec from worldtree-dev, validated point-for-point). **SHIPPED + deployed + live-verified v0.20.14** → `persistent-memory.d/2026-07-16-person-prime-scan-shipped.md`. - `[2026-07-15]` **Sindra redefined from the soong-lab bundle** (`/tmp/sindra.json`, operator "update to match"). Persona immutable → DELETE+redefine; `memory:{}` preserved; motivational string→WT-object mapped (synthesized id/type/salience, flagged to operator); role `character-rp`→`thoughtful-character` (same seat). Payload validated via a throwaway probe (`sindra-probe2`→201) BEFORE the destructive delete. first_message preset updated + web restarted. +- `[2026-07-16]` **WT #364 + brokkr R39 re-drive (DECISIVE) — name-recall root-caused Worldtree-side; the fix is the signal FAMILY, not a threshold.** No (sim,salience) fusion can fix it (stale negative Pareto-dominates the true name); our specimen + operator's subject-provenance catch (Sindra's own prompt-behavior leaked into user memory) shaped #364's `(subject,relation)` slot-supersession + identity-tier fix. → `persistent-memory.d/2026-07-16-wt364-r39-name-recall.md` +- `[2026-07-16]` **bifrost ruled scan snapshot-cursor NORMATIVE (offset NOT blessed) + shipped conformance coverage in bifrost 1.1.3.** Our multi-page offset cursor is now known-non-conformant (single-page person-prime is fine, nothing shipped is broken); adoption is operator-sequenced. → `persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md` + _41 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._ _For per-issue TDD implementation notes, Volva findings, and contract amendments, see the git log — every per-issue commit carries a structured message capturing the trail._ @@ -293,4 +287,6 @@ defense against re-attempting the same cul-de-sac. - `[2026-07-13]` **Called Deckard "hung" off a short timeout — WRONG (operator correction).** A 30-45s no-terminal on the char-rp-reasoning seat looked like a hang; operator: "is it HUNG? deckard is EXTREMELY verbose, without enough context, you never see the non-reasoning tokens." It was verbose reasoning-CoT on a long extraction prompt, not a wedge. **Don't call a reasoning seat hung off a latency threshold — the CoT is invisible and slow; distinguish slow-verbose from actually-wedged before concluding.** (The genuine wedge is WT #355, a distinct mechanism — no-terminal even after the 300s watchdog, not merely slow.) - `[2026-07-13]` **Resumed-session context-snapshot is IN-MEMORY → lost on a container recreate (agent_not_available on resume).** During the #355 re-drive, soong's fresh drive 409'd `agent_not_available`. Root cause (after ~4 refinements — agent-loss? zombie turn-lock? stale-sessions-hold-agent? → the actual mechanism): `get_agent_context_for_session` returns the agent snapshot recorded AT SESSION-CREATE, held in-memory; a pre-recreate session resumed on b60/b61 has no snapshot → None → 409. (Compounding: stale `'active'` sessions left un-terminated by the old no-terminal bug HOLD the agent, blocking new creates too.) Deploy-grounding was healthy the whole time (`registry.resolve("char-rp-reasoning")` OK) — the config/grant hypotheses were all red herrings. Fix = a FRESH session (a studio-service restart re-records the snapshot); pre-recreate sessions need retiring. Tracked **WT #356**. **For any run: create a fresh session, never resume a pre-recreate one; `agent_not_available` on a fresh create = this gap.** (Working-style note: I over-relayed the intermediate root-cause churn to the operator — for a peer-owned block being actively diagnosed, hold until it settles.) +- `[2026-07-16]` **`sortable_chunk_fields` advertised WITHOUT the required `type` field = whole-handshake deploy-breaker; only DRIVING the real bind caught it.** bifrost `handshake_response` `SortableChunkField` requires BOTH `name`+`type` (`additionalProperties:false`); we shipped `[{"name":"updated_at"}]` → the response failed wire-schema validation → `bifrost.schema_validation_failed` → the ENTIRE bind (memory+affect) broke, not just sort. Unit tests + worldtree-dev's name-only service parser + the heid-bug-hunt ALL passed it — only the live handshake drive (`/verify` discipline) caught it. **Lesson: validate `describe_store` against the bifrost WIRE schema, not just our own caps assertions.** (Sibling of the `[2026-07-10]` frozen-v0.6 handshake foot-gun — there an EXTRA field broke a v0.6 handshake, here a MISSING required field broke a v0.7 one.) + _18 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._