# Persistent memory — ratatoskr _Last updated: 2026-07-13_ This file captures durable intent and supporting evidence (goals, decisions, foot-gun warnings, in-flight state) across context resets. Read it at session start; treat it as one input alongside `CLAUDE.md` and the auto-memory system, not as the single source of truth. When durable state shifts enough to warrant capture, run `/snapshot` and commit alongside the next commit per the persistent-memory commit-along rule in `CLAUDE.md`. --- ## Repo purpose Ratatoskr is a **dev-grade debug-observability TUI** for Worldtree's Conversation API. The product IS the observability surface; chat is the input mechanism. Devs run Ratatoskr against a local Worldtree to watch a turn flow through every layer of the system, side-by-side, in one terminal: agent SSE stream, persona/Vili affect dispatch, tool calls, Bifrost handshake state, admin lifecycle events, optional raw server log. Named after the squirrel that runs up and down Yggdrasil carrying messages between layers. On-the-nose Worldtree resonance (Yggdrasil = the World Tree). **Second identity (since 2026-06-14): the v1 Bifrost Tier-3 consumer/provider** — the durable persistence Worldtree writes Tier-3 agent affect (PAD/persona, `:8390`) + memory (`:8391`) into. Lives in `src/ratatoskr/provider/`, depends on `bifrost` (`provider` optional-extra), separate from the conversation-API spec pin. So ratatoskr now owns BOTH ends of the Bifrost round-trip — the lens #17 exploits. **v0.15.0+ sibling browser surface** (`ratatoskr.web`, `ratatoskr-web` console script): same five-pane debug surface over the same SSE wire, LAN-viewable. Internal-LAN trust model — `0.0.0.0`, no auth/TLS/CORS (operator direction). Disciplined regardless: transcript HTML-escapes assistant content (INV-004); upstream API key stays server-side (INV-003). ## Current state / in-flight _As of 2026-07-13 (this session):_ **✅ THIS SESSION — WT #355 validation CLOSED + environment cleaned for a Sindra run.** The #355 re-trigger loop-in obligation is **DISCHARGED**: the fully-instrumented re-drive ran and the fix is **CONFIRMED** (detail in the #355 Recent-decisions entry). Personal `:8081` is now on **b61** — carries BOTH the #355 STICK fix (b60) and the orthogonal over-budget *trigger* fix (b61); the resume-durability gap surfaced during the drive is tracked as **WT #356**. Then, operator-directed *"clean up everything + prep for a Sindra run"*: provider stores **RESET to 0/0** (`reset-sindra-stores.sh`, rolling backup in `db-reset-backup/`), the throwaway `ratatoskr:memprobe` agent **DELETED** (reverses the prior KEEP), `ratatoskr:sindra` verified present + persona-intact on b61. **Environment is Sindra-run-ready:** web `:8765` up, combined provider `:8392` up + empty (single healthy instance), `:8081` healthy. Operator is driving the run interactively; the ratatoskr althing monitor is armed. **Foot-gun for the run:** a fresh Sindra session returning `agent_not_available` = the #356 resumed-session-snapshot gap (fix: fresh session / retire stale) — see Tried/abandoned. _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 `