Files
ratatoskr/persistent-memory.md
T
vh 8468c471e8 memory: snapshot — bifrost frozen-v0.6 handshake regression + b47/v0.7 resolution
Capture this cycle's arc: the bound-handshake blocker (bifrost 1.1.0 emits
sortable_chunk_fields on a v0.6 wire → v0.6 additionalProperties:false
rejects it), fixed by Worldtree adopting wire-v0.7 in b47 with zero change
our side (keeping 1.1.0 was load-bearing). R34-v1 affect.emit verify green
(dominant_emotion non-null, affect.full grant verified). Sindra on Deckard
live-confirmed. Corrected the stale OpenAPI pin (2.2.0 -> 2.3.0, drift-clean)
+ noted the two tolerate_drift canons WARN pending a coordinated re-vendor.
Clean checkpoint, nothing in flight.
2026-07-10 12:19:32 -07:00

125 KiB
Raw Blame History

Persistent memory — ratatoskr

Last updated: 2026-07-10

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-10:

NOTHING IN FLIGHT — clean checkpoint. This cycle shipped v0.20.7 (web-UI iteration-3) → v0.20.8 (bifrost 1.1.0) → v0.20.9 (PAD auto-scale), all on origin (HEAD 709d2e4). Bound Tier-3 is fully restored on Worldtree b47/wire-v0.7 (see the [2026-07-10] handshake decision); the R34-v1 affect.emit verify ran GREEN (dominant_emotion non-null, affect.full grant verified-active); sindra is on character-rp/Deckard (live-confirmed). Only pending item: the affect-egress-reconstruction canon re-vendor, coordinated w/ worldtree-dev, gated on the brokkr use-case-segregated-render epic (non-breaking, no action until they re-engage). Details of the shipped web work below.

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<pattern id=sparkGrid> in the hidden defs + a bg <rect fill=url(#sparkGrid)> behind every relation-row spark polyline; (2) PAD strips → per-turn Δ bars — REMOVED the vertical polyline strip (stripPoints/proj3 gone) for padDeltasdeltaStrip: 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 SHIPPEDmarkdownSafe extended: GFM pipe tables (mdTable, alignment colons), indentation-nested lists (stack of <ul>/<ol>, child list inside the open <li> = 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 (<x-dc>/<sc-if>/<sc-for>/{{}}/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: renderBifrostStaterenderBifrost, renderAffectPanerenderConsole, 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.0 / wire v0.7 (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 b47/wire-v0.7 (deploy train this cycle: b35→b44→b46→b47). Drift-check note: two tolerate_drift canons WARN vs source — worldtree-affect-egress-consumer-reference-v1 (drives our context-injection RECONSTRUCTION panel; R32/R34 moved WT's directive assembly) + worldtree-conversation-api-spec-v1 (prose narrative, OpenAPI is authoritative) — a coordinated re-vendor is PENDING per the [2026-07-07] use-case-segregated-render entry (worldtree-dev re-engages when the brokkr render epic lands); non-breaking, no action until then. 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/<owner>:<name> with the owner key). Providers run as dev-box BACKGROUND SHELLS. graphify-out/ runs dirty (auto-regen, never stage). Branch main, HEAD 709d2e4 (origin/main synced through v0.20.9); 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 affect-egress-reconstruction re-vendor (coordinated w/ worldtree-dev, pending the brokkr render epic). Leftover debug state (operator chose KEEP): throwaway ratatoskr:memprobe agent + test chunks in the live memory.db.

Recent decisions

Chronological log of decisions with [YYYY-MM-DD] prefix. One line per decision. Captures rationale that won't be obvious from code alone.

  • [2026-06-14] Ratatoskr becomes the v1 Bifrost Tier-3 consumer. A second identity beyond the debug TUI: the durable persistence Worldtree writes Tier-3 agent affect (persona) + memory into. Pin bifrost>=0.6.1 in a provider optional-extra (gitea PyPI index, auth via ~/.netrc; 0.6.0 was yanked for a circular import). Implement bifrost's OWN MemoryDataStore/affect Protocols (NOT worldtree-memory's); describe_store is SYNC; affect is conduit-opaque. New module src/ratatoskr/provider/. Authoritative how-to: ~/development/bifrost/docs/implementing-a-consumer.md. (commits 1a73d77 pin, d90a58d affect store v0.17.1, bcdcd71 serve entrypoint v0.17.2)

  • [2026-06-14] Backend = SQLite + sqlite-vec; affect-first then memory; separate DB per plane (operator-chosen). Affect = blind conduit (reads only agent_id+end_user_id); memory = structural index (reads vector/scope/id/origin to serve search). Conformance for both = #195 parity vs bifrost's InMemory*Store through the real dispatch_*_call.

  • [2026-06-14] The affect contract's idempotency model was WRONG; real-lib TDD caught it. First draft modeled same-idempotency-key-different-payload as an LWW overwrite; bifrost actually raises a CONFLICT (AffectIdempotencyConflict), actor-scoped. The artifact-only /heid-contract-review STRUCTURALLY cannot catch this class (it never sees bifrost's source) — TDD against the shipped library is the gate; the executable reference store + #195 parity are the backstop. Filed the guide §6 gap to bifrost-dev, who fixed it (bifrost c0d0a11).

  • [2026-06-15] Memory v1 = the bifrost BASIC plane only (search/get/upsert/delete + describe_store/health) per worldtree-dev re-scope (#294) — the only surface Tier-3's live path touches; gated verbs (edges/scan/atomic_supersede/mark/patch/maintenance) deferred + advertised-unsupported. Worldtree v0.35.3 already requests+maps it — no Worldtree-side blocker. Memory contract committed v1.0 (eebab46) → v1.1 Heid-reviewed (1f94e5f).

  • [2026-06-15] Providers run as dev-box BACKGROUND SHELLS, not infra-ops/systemd (operator call — it's a dev box). ratatoskr-provider (affect) + ratatoskr-memory-provider as background processes; no productionization track.

  • [2026-06-15] Affect plane shipped (v0.17.2) + LIVE-PROVEN end-to-end against real Worldtree v0.35.2. Personal handshake 200 + affect.emit 200 from 10.250.50.152 → durable row persisted (opacity held). HS256 key = the consumer's Heimdall API-key STRING utf-8-encoded (NOT base64/raw — the tripwire); cross-subnet route + BIFROST_CLIENT_ALLOWED_HOSTS allowlist all held (infra-ops-owned). worldtree-dev confirmed ADR-0009 holding as designed.

  • [2026-06-16] #295 cold-recall miss root-caused — UPSTREAM, scope-axis asymmetry. A self-driven bound cold-recall probe captured the inbound pair via the observe log: Worldtree's recall filter carries {end_user, agent_self}; our chunks were {end_user}-only; AND-matching dropped everything on agent_self → 0 hits. Our store + search are SOUND; fix is Worldtree-side. F2 (question-promotion) → #296; F1 (recall-miss) → #297.

  • [2026-06-16] agent_self → make it CANONICAL (operator decided A). bifrost's reference lattice was {end_user, group, tenant} only (agent_self → invalid_filter 400); Worldtree emits agent_self (#248). Operator chose canonical-not-re-expressed; worldtree-dev filed the lattice-addition with bifrost-dev. Implication: our store's permissive axis-acceptance becomes CORRECT once bifrost adds agent_self.

  • [2026-06-16] Self-drive auth identity: bound session-create uses the CONSUMER Heimdall key as bearer, NOT WORLDTREE_API_KEY. Worldtree signs the Bifrost handshake JWT with the session-create bearer (canary key → handshake 401; consumer key → 200). Two keys, two identities. Proven by hand; documented in docs/bifrost-self-test.md; load-bearing for #17's Bind half.

  • [2026-06-16] Issue #17 v1 scope locked (operator 1A/2A): single-plane bind + dispatch-layer op-feed. BifrostBindingRequest is one endpoint_url (one plane per session); composite-both-planes endpoint PARKED (→ now #18). Observe = structured op-feed at the DISPATCH layer (bifrost passes ctx to upsert_many but NOT search/get/delete — memory.py:244), session-level correlation; turn-correlated pane UI PARKED. Contract docs/contracts/issues/17.contract.md written + /heid-reviewed.

  • [2026-06-16] agent_self lattice SHIPPED both sides → our axis-validation gap CLOSED (v0.17.5). bifrost 0.7.0 / wire v0.5 adds agent_self to {end_user,group,tenant,agent_self} (#10, driven by our foot-gun flag); Worldtree pinned 0.7.0 (v0.35.11). We DID add _validate_scope_filter (4-axis) to match the reference (purely additive; out-of-lattice → InvalidFilter).

  • [2026-06-16] Repinned bifrost 0.7.0→0.8.0 + reimplemented memory search to the v0.6 scope split (operator-directed). scope_filterscope_all (AND) + scope_any (OR/union over a list of conjunctive scopes), bifrost #11 — the canonical resolution of the #295/#297 silent-zero. The reference now does OR via scope_any (a NEW field — additive split, not a flip of AND). Store / contract (v1.2) / tests at parity with the v0.6 reference; provider bounced onto 0.8.0 with a wiped DB. Shipped v0.17.6 (96d61a4). (SUPERSEDED the earlier "do NOT flip _scope_matches to OR" note.)

  • [2026-06-17] Worldtree spec pin bumped v0.29.0→v0.35.16 (562001af1b59f8); cold recall closed on the WIRE. Worldtree shipped #297 (client-side per-scope-value union recall) + #298/#299 (adopt the bifrost v0.6 scope_any/scope_all wire) — emits scope_any on recall, pairing with our v0.17.6 provider. Re-vendored the spec; diff-reviewed the 285-commit catch-up — no client-breaking changes. pin:-only commit, no bump.

  • [2026-06-17] End-to-end cold-recall proof RAN — our stack proven, #296 isolated. Against personal WT v0.35.16 with restored ratatoskr:sindra: #297/#298 union recall, write path, and cold read ALL proven. Lone gap = upstream #296 extraction quality (the WIRE closed; fact-recall was #296-blocked).

  • [2026-06-17] DELETE+redefine ratatoskr:sindra (operator-authorized; pre-v1 debug surface). She SURVIVED the rebuild but was STALE (dead model + no memory block); memory is immutable post-define, so DELETE+redefine was the only path. v0.35.16 define takes role (capability), NOT model: role:"character" → first-healthy bind mistral-small-4; memory:{} trips the promotion gate (GET does NOT echo memory_config). Our tier3.py define is Phase-2.0-stale — untracked modernization follow-up.

  • [2026-06-17] Promotion = 4-trigger hybrid (worldtree-dev, code-grounded): salience (regex, 90s rate-limit) / turn_count≥6 / context_pressure / idle ≥10min (unconditional on quality); per-turn plan_promotion_run for consumer_defined. DELETE does NOT drain/promote (delete-is-delete, #276) — idle ≥10min is the deterministic flush.

  • [2026-06-17] #296 triage sent to worldtree-dev (01KVBBH0…): extraction SUBJECT-INVERSION (promotes assistant prose, drops the user's fact) + META-DESCRIPTION-not-content; verbose-persona aggravator. WAD-vs-bug resolved to BUG (extraction quality), not idle-gating.

  • [2026-06-18] Tier-3 memory PROVEN end-to-end liveratatoskr:terse-probe recalled a seeded user fact in a COLD history-free session (scope_any → 1 hit @ cosine 0.6994). Closes the opening "how far from Tier-3 memory" question for normal agents.

  • [2026-06-18] #296 Stages 1+2 closed. Stage 1 (v0.35.19, recallability admission gate) validated live for normal turns; bisect localized the residual to verbose-persona VOLUME crowd-out. Stage 2 (v0.36.0, MERGED at worldtree-codex) = user-only one-call-per-turn extraction, the STRUCTURAL fix; hard-linguistic layer → Worldtree #305 (we handed over a live-validated eval fixture PAIR). Full-coverage re-smoke: verbose sindra-probe promoted the fact cleanly + cold-recalled @ 0.694 under v0.36.0.

  • [2026-06-18] #17 implemented end-to-end via direct in-session TDD (6 patch bumps v0.17.8v0.17.13, suite 470 green). Slice order: bind primitive → op-feed → CLI → TUI → web(server) → web(UI). Tests drive the REAL bifrost dispatch via minted JWTs (bifrost.core.dispatch_jwt.mint_dispatch_jwt) — the "test against the shipped lib" posture, not hand-mocked envelopes. Op-feed reads session_id off the dispatch JWT sub claim (the contract open-q, resolved YES at the ASGI layer where the JWT is always present — bifrost.reference_server._dispatch_auth.DispatchContext.session_id = payload["sub"]). bifrost wire facts captured in-code: memory envelope {operation, args}memory_result(**payload)={success,...}; verbs bare (search/upsert_many/get/get_many/delete_many); affect {operation:"affect.emit"}{success,stored}; error envelope {code, message}; scopes memory:read|write.

  • [2026-06-18] #17 live-smoke PROVEN — the whole thesis validated. A self-driven bound CLI session showed, from the PROVIDER side, exactly which memory ops a turn produced (2 recall searches, exact bound session_id, real union-recall scopes). Negative (canary→auth_rejected) NOT live-constructible (Tier-1 agents aren't memory-bindable; a wrong key for an owner-scoped agent fails at agent-auth before the handshake) — covered by the unit test + prior hand-proof.

  • [2026-06-18] Fixed a pre-existing test-isolation bug exposed by the #17 CLI tests (0bebad7): test_no_textual_import did a live importlib.reload(ratatoskr.cli) that mutated the shared module in place, breaking class identity (isinstance/pytest.raises) for every test ordered after it. The real check is the static source-grep; the reload was vestigial → removed. Lesson: never importlib.reload a shared module in a test without restoring it.

  • [2026-06-18] #18 filed (composite endpoint + PAD read-endpoint) — DEFERRED, tracked at Gitea #18. Two pieces: (1) a composite Bifrost facade (new port e.g. :8392) fronting BOTH :8390+:8391 advertising both caps at handshake → one session binds both planes (un-parks the #17 open-q; bifrost reference_server already mounts both planes in one app → thin combined builder; needs per-plane failure-status + the op-feed deriving plane PER-REQUEST from the path instead of its fixed plane param). (2) a non-bifrost PAD read-endpoint on the affect provider (recommended over web-reads-affect.db-directly) → web persona pane renders PAD/valence from OUR :8390 store. Composite half APPROVED by operator ("A is correct"); contract-first next. Persona-telemetry diagnosis (verified): affect bind persists PAD (vuong: pleasure +0.146, familiarity 0.18→0.59 over 8 turns) but the pane reads Tier-3-404 persona_state AND Tier-3 emits ZERO affect_update SSE (wire-verified) — both WT sources dead, so #18's PAD-display half is the only path. affect.fetch over bifrost is RESERVED/blocked but irrelevant (we own the store). Proposed: fast-track the PAD-display half now (awaiting operator go), keep composite contract-first.

  • [2026-06-18] #18 SPLIT; Deliverable 1 (composite) routed to bifrost — Option C (operator). D2 (PAD read-endpoint, our-side only) fast-tracked; D1 (composite :8392 endpoint) routed to bifrost-dev to add a PUBLIC build_combined_app rather than hand-roll one from bifrost privates — because ratatoskr is a debug surface that must exercise the CANONICAL surface ("don't go off the reservation"). The Heid framing-panel had unanimously recommended hand-rolling (Option B) — DISCARDED as wrong-grounded (the panel lacked the canonical-surface principle; their own finding that B reaches external/underscore-private names actually vindicated C). bifrost-dev confirmed: clean additive minor (~v0.9.0), design locked (advertise-by-store-PRESENCE handshake — no health probe; per-route call-time isolation within a shared ASGI process), slotted after WT #289. [principle → auto-memory feedback-debug-surface-uses-canonical-surface-only]

  • [2026-06-18] FR-1 RESOLVED — the composite premise was unverified, now wire-proven: single-endpoint, caps-routed. The Heid panel's sharpest catch (Regin): "advertise both caps → Worldtree dispatches both planes to one endpoint" was an ASSUMPTION about WT dispatch, stated as fact. worldtree-dev verified IN CODE: one BifrostClient per session (single _endpoint_url), handshake capabilities_granted parsed INDEPENDENTLY into memory+affect sets, both stores attach off the SAME endpoint iff their cap was granted (service.py:2597/2703-2713/2745-2751, bifrost_client.py ~357-369; tests test_tier3_bifrost_{memory,affect}_routing.py). So D1 is bifrost-only, ZERO Worldtree change — #18's "no WT change needed" assumption was correct.

  • [2026-06-18] #18 D2 implemented via direct in-session TDD (suite 470→482). Provider read route GET /affect/state/{agent_id} added via app.add_route (NOT an outer Mount — keeps /bifrost/* top-level so the existing route test + the op-feed path-check stay valid); web GET /api/affect/{agent_id} proxy (server-supplied end_user_id, colon-id quote()'d, RATATOSKR_AFFECT_READ_URL); pane renders the affect-emit shape honestly. Contract docs/contracts/issues/18.contract.md (D2-scoped; D1 deferred). heid-code-review panel (Gróa 5 / Hulda 3 / Regin 0): 1 real INV-001 drift + 4 test-gaps, all fixed. No contract amendments (code was wrong, contract was right).

  • [2026-06-19] #18 D2 SHIPPED (v0.17.14, 39eebd1) and the full #17+#18 arc PUSHED to origin. Live-smoke PROVEN against real data (throwaway :8393/:8766 vs the real affect.db → real sindra/vuong PAD through the full web→provider chain; Playwright DOM check confirmed the pane render + the F1 fix — no fabricated "neutral"). The push carried 9 previously-held commits incl. the deliberately-unpushed #17 (v0.17.8v0.17.13); origin/main now == 39eebd1, tag v0.17.14.

  • [2026-06-19] bifrost repinned 0.8.0→0.10.0; affect.fetch became MANDATORY (strong-or-absent). 0.10.0's _supports_affect_plane requires affect_supported+emit+fetch and gates EVERY affect op — an emit-only store 400s. Implemented affect.fetch (v0.17.15, ca6af6b) conformed to bifrost's reference InMemoryAffectStore.fetch ({found, snapshot?}): the forced D1 prerequisite + a new Worldtree I/O point consumed. Flagged the now-stale consumer-guide line to bifrost-dev (fixed a2e6d62).

  • [2026-06-19] #18 D1 SHIPPED — composite build_combined_app on :8392 (v0.17.16, 7f4ceaa); #18 CLOSED; published v0.18.0 (359dbb1). build_combined_provider_app wraps bifrost's public builder over both stores + the shared read route; op-feed plane='combined' per-path. Direct in-session TDD; heid-code-review panel (Gróa/Hulda/Regin) returned ZERO drift. Live-proven at wire+dispatch; WT-turn gated on infra-ops :8392 allowlist.

  • [2026-06-19] op-feed handshake field-name fix (#17, v0.17.17 d60b77d): capabilities_requestedcapabilities. The summary read a field that never exists on the wire (bifrost reads capabilities, _protocol.py:181) → caps_requested was always null. Surfaced by the heid panel (Regin) during the D1 review — a latent #17 bug, not D1 drift.

  • [2026-06-19] Ratatoskr is a REFERENCE implementation of the Worldtree/Bifrost standard (operator). Adopt the dep's canonical way (even if ours works); INFORM of drift/gaps; ADVISE a different approach only when ours is genuinely better (dep owner decides), never unilaterally fork. [auto-memory feedback-ratatoskr-is-a-reference-impl-adopt-canonical]

  • [2026-06-19] Ratatoskr v1 is DERIVED from Worldtree I/O coverage (operator) — no self-defined feature ROADMAP. v1 = consume all of Worldtree's I/O points, reached when Worldtree hits 1.0; the convergence target is a coverage map, not a 37 capability list. [auto-memory project-ratatoskr-v1-derived-from-worldtree-io-coverage]

  • [2026-06-20] #18's final leg PROVEN — composite :8392 WT-driven smoke ran end-to-end + persisted. infra-ops allowlisted 10.100.10.50:8392 on the personal WT (01KVHWJGTT…); a real WT turn (session b83a66b6, ratatoskr:sindra, fresh end_user resmoke-choco-1) dispatched the full both-plane lifecycle through ONE endpoint — handshake (both caps) → affect.fetch + memory.searchaffect.emit (stored:true) → memory.upsert_many (upserted:1) — both writes verified in our SQLite (affect_snapshots PAD row + memory_chunks chunk 2df1b79…). First attempt blocked by a model_unavailable outage on the personal WT (both agents' models down), operator-fixed mid-session, then clean. The composite has no open legs.

  • [2026-06-20] #17 CLOSED in the tracker. Shipped end-to-end (v0.17.8.13 + op-feed field fix v0.17.17); the 2026-06-20 composite smoke re-exercised its op-feed live. Closing comment captures the full both-plane proof. Open issues now just #11 (scope-blocked) + #10 (watch).

  • [2026-06-20] Sindra has real PAD but ~empty memory — the affect/memory persistence asymmetry, confirmed on real sessions. affect EMITS every turn (persona always accumulates: vuong 8→14 interactions across the session); memory only writes on a PROMOTION trigger (salience / turn_count≥6 / idle-≥10min flush). Two real vuong sessions through the combined bind (04d6414c, 433541fe) drove affect emits + memory SEARCHES but ZERO promotion upserts → memory.db holds only the smoke fixture, zero vuong chunks. Operator: acceptable (server-takedown = "Sindra bonked on the head"; transient memory loss WAD). Operational catch: combined-as-default web bind saves persona reliably but silently LOSES memory if a session closes before a promotion trigger fires.

  • [2026-06-29] Web SPA combined-bind default (v0.18.1, 719e4d6) — operator-caught gap. #18 shipped the composite :8392 provider but never exposed it in the web bind dropdown (only memory/affect single-plane). Added combined (:8392) as the DEFAULT option (both planes in one session), kept single-plane for isolation diagnostics; wired endpoint_for_plane combined→8392 + server validation + the dropdown. Direct TDD; #17 contract updated (the governing spec for the web bind). Restarted :8765 on current code (env.sh + provider.env + RATATOSKR_AFFECT_READ_URL=:8392).

  • [2026-06-29] bifrost repinned 1.0.0 (v0.18.2, af67ad9). bifrost-dev shipped its first stable release; wire v0.6 now STABLE/FROZEN. Non-breaking (byte-identical to 0.10.0); switched the floor pin → exact ==1.0.0 per the stable-substrate posture. Post-1.0 breaking changes ride a bifrost MAJOR + new wire (v0.7+); a v0.6-pinned consumer is stable indefinitely. (Also this session: althing migrated to v0.15.0+ lean-bus / schema v4 — moderation retired, chamber/redis ripped; our tooling auto-updated to 0.17.4.)

  • [2026-06-30] Worldtree v1.0.0b1→b2 consumer adaptation: eager turn-launch statuses (v0.18.3 b2e4901, v0.18.4 e4317f6). Worldtree #331 decoupled turn execution from the SSE connection → turn-launch failures now arrive EAGERLY as a status before any stream: 409 agent_not_available (pre-b1 a 200 + in-stream error event), 503 retryable. Mapped both in stream_turn to typed SseConnectFailed subclasses keyed on STATUS, parsing the {detail:{error_code,message}} envelope — POST-003 preserved (no synthetic event yielded), existing handlers still catch (the design fork vs yield-an-Error-event was decided by POST-003). DEFERRED follow-ups (tracked here; bundle with the v1 coverage-audit): (1) live-prove the 409/503 end-to-end on personal-b2 (now unblocked — personal on b2, my key works there); (2) full conversation-api-spec.md markdown re-vendor to the b2 era (ratatoskr vendors the markdown, not the OpenAPI JSON).

  • [2026-06-30] Verify-against-the-real-spec-before-committing caught a real upstream gap. Holding the v0.18.3 commit to verify against demo's OpenAPI surfaced that the FROZEN OpenAPI 2.1.0 didn't document the 409/503 the heads-up described (agent_not_available was in the ErrorCode enum, but NO 503/turn-launch code). worldtree-dev confirmed it was THEIR gap (#331 added the statuses without extending the #328 openapi() override), shipped the fix in v1.0.0b2 / OpenAPI 2.2.0 (409/503 now enumerated, 503 code finalized as not_ready). "The consumer-oracle earning its keep." Lesson: a provider's prose heads-up can diverge from its frozen machine-readable spec — verify the actual spec before committing a consumer adaptation.

  • [2026-06-30] regard is a DEAD AXIS in Worldtree's emitted affect (caught provider-side; worldtree-dev confirmed + escalated to Vuong). Across all our affect snapshots, valence[].regard is EXACTLY 0.15 regardless of agent/end_user/interaction_count, while familiarity accumulates (vuong 0.18→0.69 over 14 turns). Root cause (worldtree-dev, code-grounded): 0.15 = base_regard = agreeableness*0.3 (sindra A=0.5); regard's only human-writer update_regard early-returns unless an emotion is about="other", but the Vili appraiser's ViliResponse schema has NO directedness axis (everything hardcoded about="situation") — producer side lost in the #265 Vili rework; consumer machinery intact. NOT WAD; the fix (reintroduce other-directed classification) is an affect-model change touching every agent + a directedness-classification design call → worldtree-dev filing an issue to Vuong. [the consumer/provider thesis paying off again]

  • [2026-06-30] v1 coverage-audit kicked off; coverage ledger written (docs/coverage-map.md) — the first one. Every Worldtree v1-FROZEN I/O point × ratatoskr status. Anchored on WT's frozen machine-readable artifacts (OpenAPI 2.2.0 conversation-api-openapi.json = 40 REST path-groups + SSE schema = 11 events + bifrost wire v0.6), NOT the stale vendored prose markdown. Result: SSE 11/11 ; Bifrost provider planes 8/8 live-proven (covers the full bifrost.memory.MemoryDataStore protocol = describe_store/get/get_many/search/upsert_many + delete_many, and affect emit/fetch; health is extended-reference-store-only, NOT in the base protocol → correctly deferred, NOT a gap — settles the prior "health" ambiguity); client REST 7/40 live, 11 in-scope, 22 🚫 excluded.

  • [2026-06-30] Scope mandate A locked (operator): v1 "done" = every frozen I/O point CLASSIFIED (covered-or-excluded-with-rationale), zero unaccounted — NOT a feature-complete client. The coverage map is a LEDGER, not a build-everything mandate. Reconciles the 2026-06-19 "consume all I/O" reframe with the 2026-05-20 design-brief's "NOT an admin tool" + deferral negative clauses (which predate both the provider identity and the reframe). Resolved the 11 design-brief-vs-reframe rows via the debug-observability test (does a turn flow through it?): 🚫 search / uploads / pending / embed / judgments (consumer-product + eval utilities); transient-characters routing (4) + persona_state-write (Tier-2 frontier). Frontier Tier 1 (all unblocked except #11): session-picker + SSE-resume (wrappers list_sessions/reconnect_turn exist with NO caller — presenter-wiring only) → GET /capabilities + GET /me → BifrostState/Tools widgets (GET /admin/sessions/{id}/{bifrost,tools}, admin-key) → #11 AdminEvents BLOCKED on admin.events.read scope. The 3 admin-observability widgets + picker + resume were design-brief §5/§4/§8d v1 items that were never built.

  • [2026-06-30] Finding P-1 (pin drift) + pin-remediation PENDING. We vendor the PROSE markdown (docs/conversation-api-spec.md), which is byte-identical to live WT's but frozen at v0.35.16-era content (last WT edit 2026-05-31) — it does NOT capture b2: 7 new endpoints (admin/keys/bulk, admin/persona/{archive,erase}, admin/usage, embed, judgments, me/usage), the 409/503 on messages-POST (#331), the unified error envelope (#328), or the SSE schema. WT's authoritative v1 truth is now the FROZEN OpenAPI 2.2.0 + SSE-schema JSON (Worldtree/docs/v1-schema-freeze-manifest.md). So the previously-deferred "re-vendor markdown to b2" is a near-no-op (markdown content identical). Pending operator nod: re-pin to the machine-readable artifacts (recommended — drift-checkable via canonical_drift.py, makes the coverage map reproducible vs a frozen diffable target) vs markdown-only. Deferred (not auto-applied) because it adds vendored artifacts + a canonical-sync pin = substrate change with CI-gating reach. → RESOLVED 2026-06-30 (operator: "a then b"). Vendored conversation-api-openapi.json (2.2.0) + conversation-api-sse-events.schema.json + re-copied the prose markdown; pinned all three in .corviduo-canonicals.toml (OpenAPI+SSE = strict drift gates, markdown = tolerate_drift reference); advanced worldtree-spec-rev f1b59f8→5810a26 + worldtree-version v0.29.0(STALE, never bumped from the v0.35.16 pin)→v1.0.0b2 + pinned-on→2026-06-30; SPEC-PIN.md history row added. canonical_drift.py green (10/10). pin:-only, no version bump (no client-facing code change; the b2 409/503 + error-envelope were already consumed in v0.18.3/.4).

  • [2026-06-30] (b) Tier-1 frontier SCOPED, ready for a contract-first TDD cycle (next focused work). The primitives already exist + are contracted + tested; the gap is PRESENTER-level wiring. Two slices: (b1) SSE-resume — contract #1 (ratatoskr.sse_client) DELIBERATELY makes resume caller-owned ("on SseConnectionDropped, the caller MAY invoke reconnect_turn"); reconnect_turn (sse_client.py:524) has NO caller. Gap = a SHARED resume-orchestration wrapper (catch SseConnectionDropped → track last-seen sse_idreconnect_turn → continue), consumed by all 3 presenters per design-brief §8b "share the consumer, branch the presenter" (NOT per-presenter — that forks the consumer). New function block → amend contract #1 (additive FN, e.g. stream_turn_resilient) then TDD (RED: drop-mid-stream→resume continuity; GREEN: wrapper; wire cli --send first as the tracer). Resume design pre-locked: in-process Last-Event-ID only, cross-process deferred to v2 (design-brief §8d). (b2) session-pickerlist_sessions (sessions.py:198) has NO caller; add a Textual DataTable startup picker (>1 session) + --session <id>/--new CLI flags (design-brief §4, decisions pre-locked). Both pre-locked → heid-contract-review likely skippable as ceremony (small additive amendments to mature specs); heid-code-review still valuable. #11 AdminEvents stays BLOCKED on admin.events.read scope (infra-ops).

  • [2026-06-30] (b1) SSE-resume SHIPPED (v0.18.5) — stream_turn_resilient (sse_client.py). The shared resume-orchestration surface (design-brief §8b): wraps stream_turn+reconnect_turn, catches SseConnectionDropped (mid-stream drop OR clean-EOF-before-terminal) → resumes from last-seen sse_id via reconnect_turn (Last-Event-ID), up to max_reconnects (default 5); non-drop reconnect failures (412/410/400/TurnIdFlip/SseConnectFailed) PROPAGATE per contract #1's "surface, not recover". last_seen persists ACROSS attempts (a zero-event reconnect drop falls back to the prior attempt's id). Direct in-session TDD against a contract-#1 amendment (8 cases incl. two-drops, max-reconnects-exhausted, zero-budget, buffer-expired-propagates, unresumable-zero-event). Wired ALL THREE presenters through it (v0.18.6): cli --send (cli.py:396), TUI (tui.py:1321), web (web/server.py:294) — each a name-for-name stream_turnstream_turn_resilient swap (the §8b "all presenters share the consumer" promise, fully kept; the TUI is the primary resume beneficiary — long-lived sessions / laptop-suspend). Suite 518 green; ruff+mypy clean on touched code (pre-existing cli.py:400/543 mypy warts left untouched per surgical rule); contract #1 validates OK. heid-code-review NOT run (small additive well-TDD'd wrapper; offered to operator). b2 (session-picker + --session/--new flags) still pending.

  • [2026-06-30] (b2) session-picker SHIPPED (v0.18.7) — bare TUI mode → startup picker (design-brief §4). list_sessions had NO caller; now bare TUI mode (no --session/--new) resolves via list_sessions in _resolve_then_run: 0 sessions → [no_sessions] error, exit 14 (resume-only, honors §4 "no in-app session creation — --new flag only"); exactly 1 → auto-resume (§4 "picker only when >1"); ≥2 → new SessionPickerApp (Textual App[str|None], mirrors AgentPickerApp; ListView of sessions) → resume the pick (Esc/Ctrl-D → exit 0). cli _parse relaxed: bare TUI now VALID (was "pass exactly one" error); --send still requires one flag (non-interactive, no picker); --agent forbidden in bare mode; run_tui PRE-002 XOR→"not both". Direct in-session TDD (contract #6 amendment, validated OK): 3 widget pilot tests + 5 _resolve_then_run resolution tests + 3 cli validation tests. Suite 528 green; touched code ruff-clean (mypy: only the BINDINGS list-invariance warning every App in tui.py already carries — consistent). DESIGN NOTE — bare+0-sessions → error (clause-consistent). The friendlier auto-fall-through-to-new alternative is DEFERRED pending operator preference (it would create a session without --new, against the §4 negative clause). Frontier now: GET /capabilities+GET /me → BifrostState/Tools widgets (GET /admin/sessions/{id}/{bifrost,tools}, admin-key) → #11 AdminEvents (BLOCKED on admin.events.read). heid-code-review NOT run on b1 or b2 (offered).

  • [2026-06-30] capabilities+me slice SHIPPED (v0.18.8) — GET /me + GET /capabilities consumed via a new --whoami one-shot. get_me/get_capabilities added to sessions.py (mirror get_persona_state: 200→dict verbatim, non-200→SessionApiFailed; freeform dicts per the frozen OpenAPI). New ratatoskr --whoami CLI mode (mirrors --send's non-interactive shape) fetches both + prints an identity+capabilities report; standalone probe (mutually exclusive with --send/--session/--new/--agent, opens no session; new ParsedArgs.whoami field + main() dispatch). /capabilities is the Echo EPHEMERAL-TEMPLATE discovery endpoint ({ephemeral_templates:{echo:{allowed_models,default_model,system_prompt_max_bytes}}}), NOT a generic server-caps endpoint (audit finding — the coverage-map's earlier "server capability discovery" framing was imprecise). /me = whoami ({user_id,scopes,tier,key_id?,...}, optionals omitted-not-null). Contract-skip privilege invoked (low-effort GET wrappers) but contract #2 amended (2 FNs, validated OK) to keep the sessions spec canonical + honest test citations. TDD: 5 wrapper tests + 5 cli tests (validation + mode + error). Suite 538 green; touched code ruff-clean (mypy: only no-any-return on resp.json()→dict, identical to the pre-existing get_persona_state). Coverage: REST 9/40 (up from 7). TUI-surfacing of /me (footer identity line) + /capabilities DEFERRED — the one-shot is the minimal tracer. Frontier now: BifrostState + Tools widgets (GET /admin/sessions/{id}/{bifrost,tools}, admin-key-gated) → #11 AdminEvents (BLOCKED on admin.events.read).

  • [2026-07-01] b1 (SSE-resume) heid-code-review panel: ZERO findings — cross-model-verified clean. Gróa (Grok) + Hulda (Codex) + Regin (GLM-5.2) each independently reviewed stream_turn_resilient vs contract #1's amendment (artifact-only, firewall held) → all three ZERO findings; signature / PRE-001..004 / STEP 1-4 / POST-001..003 / ERROR_ROUTING / all-8-TESTS confirmed, incl. the subtle seen = last_seen or drop.last_seen_sse_id zero-event-drop fallback. Convergent meta-note: TDD + the unusually-prescriptive contract (STEPS flexibility=prescriptive + explicit GOTO) left no room for compliant-but-different drift — confirmation, not discovery. Calibration signal: for a thin wrapper with a tight prescriptive contract + comprehensive TDD, the panel confirms rather than discovers. b2 (picker) + capabilities+me NOT yet reviewed (higher-surface b2 is the better candidate if more review is wanted). Dispatch msg 01KWE2K99T… / thread 01KWE2K99S…; heid dispatch-log 2026-06.jsonl#01KWE2V3MMY8XS55FCJYXYV14B.

  • [2026-07-01] GET /sessions/{id}/tools quick-win SHIPPED (v0.18.9) — owner-scoped tool inventory in the TUI Tools pane. get_session_tools wrapper (sessions.py, mirror get_me: 200→dict, non-200→SessionApiFailed) + _format_tool_inventory helper + _hydrate_session_tools best-effort worker (mirror _hydrate_persona) wired UNCONDITIONALLY in on_mount → writes the merged {agent_id, builtin_tools, bifrost_tools} inventory (what the LLM saw at turn-fire) to the Tools pane + audits session_tools_hydrated, never crashes on failure. Owner-scoped (ctx.user_id==session.user_id) → reachable with the CONSUMER key, NO admin scope — so this covers the design-brief §5 "Tools widget" via the reachable owner endpoint (the admin /admin/sessions/{id}/tools variant stays a gap only for cross-user operator debug). Contract #2 amended (FN, validated OK) + TDD (3 wrapper respx tests + 1 format-helper unit + 2 hydrate integration tests via _spy_writes+pilot). Suite 544 green; touched code ruff-clean (the tui.py ruff/mypy debt at other lines is pre-existing). Coverage: REST 10/40 . Frontier now: BifrostState widget (GET /admin/sessions/{id}/bifrost, admin-key) + #11 AdminEvents (BLOCKED on admin.events.read) + Tier-2 (transient-characters routing, POST /sessions/{id}/persona_state).

  • [2026-07-01] BifrostState pane SHIPPED (v0.18.10) — GET /admin/sessions/{id}/bifrost in a new TUI "Bifrost" pane; the FIRST admin-key consumer in ratatoskr. get_session_bifrost(client, session_id, *, admin_key) (sessions.py) — admin-scoped (admin.sessions.read); the request OVERRIDES Authorization with admin_key (distinct from the consumer bearer, asserted in a test); 200→dict, non-200→SessionApiFailed. Admin-key wiring: --admin-key flag + RATATOSKR_ADMIN_API_KEY env → new ParsedArgs.admin_key. New "Bifrost" TabPane + _format_bifrost_state + _hydrate_bifrost_state best-effort worker (mirror _hydrate_session_tools) UNCONDITIONALLY in on_mount → writes {endpoint, connected, caps_granted, tools} + audits; self-labels "not configured" (no admin key) / "not bound" (404) / graceful on 403 + error. Contract #2 amended (FN, validated OK) + TDD (4 wrapper respx tests incl. the admin-bearer-override assertion + 1 format unit + 3 hydrate integration). Suite 552 green; my code ruff-clean (pre-existing tui.py ruff debt at other lines untouched, incl. a dead RichText import in _hydrate_persona). LIVE-AUTH-PROVEN on personal :8081: admin key authenticated (reached resource-layer 404 session_not_found, NOT 401/403) → admin.sessions.read works live; 200 full-state not exercised (no bound session on :8081 now — unit-covered). Patch bump (debug feature, no downstream coordination; consistent with the session's cadence — but the §5-core-completion angle is a possible minor, operator's call).

  • [2026-07-01] LEDGER CORRECTION: #11 (AdminEvents) is NO LONGER BLOCKED. Verified via GET /me on :8081 that RATATOSKR_ADMIN_API_KEY (ratatoskr-readonly, tier readonly-admin) carries ALL 7 read scopes INCLUDING admin.events.read (+ admin.sessions.read, admin.keys.read, admin.skuld.read, pending.read, search.read, tool_events.read). The coverage-map + prior memory had #11 "blocked on admin.events.read" — STALE; the admin key was minted (post-#11-filing, env.sh) WITH the scope, so the blocker is already satisfied. Only the AdminEvents SSE pane itself is unbuilt — the last unbuilt §5 debug pane (a live SSE-consuming admin pane, distinct from the hydrate-at-attach panes). Coverage-map updated. Coverage: REST 11/40 . Consider building the AdminEvents pane and/or updating #11's tracker status (its stated blocker is gone).

  • [2026-07-01] AdminEvents pane SHIPPED (v0.18.11) — GET /admin/events SSE in a new TUI pane; #11 closed-by-build; Tier 1 (debug-observability core) COMPLETE. stream_admin_events(client, *, admin_key, last_event_id=None) (sse_client.py) — a NEW long-lived SSE consumer for the admin lifecycle broadcast (envelope {id,type,timestamp,data}, 17-event v0 vocab), admin-scoped (admin.events.read, bearer-override), Last-Event-ID resume; non-200→SseConnectFailed, mid-drop→SseConnectionDropped; new AdminEvent dataclass (distinct from the turn Event union). New "AdminEvents" TabPane + _format_admin_event + _admin_event_matches (design-brief §6 filter: active-session events + non-heartbeat system.*) + _stream_admin_events long-lived best-effort worker (unconditional on_mount, cancelled on app exit; self-labels "not configured"/"unavailable"/"stream ended"). Reuses the admin key from the BifrostState slice. Contract-SKIPPED for stream_admin_events (out of contract #1's turn-SSE scope; spec § Admin Event Stream is the reference; well-TDD'd). TDD: 4 sse_client tests (multi-event+bearer-override, Last-Event-ID header, 403, malformed-skip) + 5 tui (format, filter, worker success/no-key/403). Suite 561 green; my code ruff-clean (pre-existing tui.py ruff debt untouched, incl. the dead RichText import in _hydrate_persona). LIVE-AUTH-PROVEN: GET /admin/events on :8081 → HTTP 200 under the admin key (connected + streamed, idle in the 4s window — no 401/403). Coverage: REST 12/40 . Tier 1 admin/debug-observability core COMPLETE (Persona · Tools · BifrostState · AdminEvents). AdminEvents work landed as patch v0.18.11; then v0.19.0 MINOR cut (operator-approved 2026-07-01) publishing the milestone: the debug-observability core is complete (Persona · Tools · BifrostState · AdminEvents all built + consuming real endpoints — the design-brief's headline deliverable). Pre-1.0 minor = release-note-worthy (no downstream althing push needed pre-1.0); lightweight tag per the SemVer mechanics (annotated reserved for major cuts). Remaining in-scope client I/O: only Tier-2 (transient-characters routing + POST /sessions/{id}/persona_state).

  • [2026-07-01] Tier-2 SHIPPED (v0.19.1) — transient-characters CRUD + persona-state write; the v1 coverage-audit CONVERGES (zero in-scope gaps). 5 wrappers in sessions.py: list_character_models/create_character/get_character_state/delete_character (#161, character.read/.write scopes) + set_persona_state (POST /sessions/{id}/persona_stateFREEFORM body: unpinned in the frozen OpenAPI 2.2.0 + absent from the prose spec, so the caller supplies the snapshot shape). Two one-shot CLI probes (mirror --whoami): --characters (models→create→get-state→delete lifecycle report) + --set-persona-pad "p,a,d" (requires --session; POSTs {pad:[…]}). New ParsedArgs.characters/set_persona_pad + probe-mode mutual-exclusion validation + _probe_client helper. Contract #2 amended (5 FNs, validated OK) + TDD (7 wrapper respx + 5 cli tests). Suite 573 green; touched code ruff-clean. NOT live-proven (character scopes + the persona-write body shape unverified — the probes degrade gracefully on 403/422). THE v1 COVERAGE-AUDIT HAS CONVERGED: REST 17/40 with ZERO in-scope gaps (23 REST path-groups excluded-by-design + rationale), SSE 11/11, Bifrost provider planes 8/8. Scope-A "done" (every frozen I/O point classified, zero unaccounted) is MET — ratatoskr cuts v1 when Worldtree tags 1.0. Only not-consumed in-scope sub-method: GET /agents/{id} (consumer-agent lookup, manual-curl-only, on an already- path group). Patch bump (Tier-2 tail; v0.19.0 already published the core-complete milestone — a 2nd minor would be cadence-too-fast).

  • [2026-07-01] env.sh now PERSISTS the web Bifrost-bind vars (gitignored, local-only). ratatoskr-web's in-browser bind needs three server-held values; env.sh sources provider.env for the Heimdall key and exports RATATOSKR_BIFROST_CONSUMER_KEY + RATATOSKR_PROVIDER_VISIBLE_HOST=10.100.10.50 + RATATOSKR_AFFECT_READ_URL=:8392. The HS256 byte-match trap (re-hit + documented): the bind's consumer key must equal the key the :8392 combined provider validates against = RATATOSKR_HEIMDALL_KEY (provider.env, fp 45a0…), NOT WORLDTREE_API_KEY (env.sh, fp 7c2f…) — both are the SAME ratatoskr identity but DIFFERENT 40-char strings; signing with the wrong one → bifrost.auth_rejected. Single-sourced (env.sh sources provider.env) to avoid a rotation footgun; guarded with a stderr warning if provider.env is missing. [auto-memory: HS256-key-is-the-consumer-Heimdall-key-string]

  • [2026-07-01] Tier-3 stores RESET (operator-directed). memory.db (29 chunks + vectors + idempotency) + affect.db (5 PAD snapshots + idempotency) wiped to zero via a live DELETE+wal_checkpoint through the shared WAL (no provider restart — the 3 long-running providers see empty on next dispatch); consistent online-backup at /tmp/ratatoskr-tier3-reset-<ts>/. Boundary for a COMPLETE Sindra wipe (mapped): our stores = mine (done); the agent DEFINITION ratatoskr:sindra + its sessions = mine via the owner key (DELETE, no coordination); Worldtree's internal promotion/dedup shadow = needs worldtree-dev (no public reset API, survives our wipe → for a clean promotion smoke use a BRAND-NEW agent+end_user).

  • [2026-07-01] Embedding-latency loop RESOLVED — it was WORLDTREE's, not ratatoskr (the consumer/provider thesis paid off again). Vuong flagged dozens of embed queries/Tier-3 turn; worldtree-dev's first-pass blamed our memory_context chunk-batching. Traced CODE-SIDE that ratatoskr embeds ZERO times (provider upsert_many stores the given embedding, search takes a given vector, the conversation consumer POSTs only {content}, /embed is coverage-map-excluded — pure Bifrost/ADR-0009 path, WT does all embedding). worldtree-dev retracted + fixed on THEIR side (v1.0.0b4): a persona-recitation memory-gate re-embedding the stable character card sentence-by-sentence every turn (~95% of gateway traffic) → content-hash cache; re-embed ratio 15x→1.01x. Lesson: verify your own code before accepting a peer's "it's your side" — the debug tool proving its own side clean is the whole point.

  • [2026-07-01] Web debug-surface parity SHIPPED (v0.19.2, a0a9d5f) — direct in-session TDD. 3 proxy routes (tools/bifrost/admin-events) + admin-key wiring (entrypoint→create_app→app.state) + AdminEvents SSE proxy re-emitting under a FIXED admin_event name (one browser listener, no per-type drops) + session-filter _admin_event_matches_web (mirrors TUI §6). Frontend: 2 tabs (bifrost ⌃5, admin ⌃6) + tools-inventory folded into the tools pane. 9 respx tests (admin-bearer override, filter unit, SSE stream-filter); live-proven against sindra (bifrost connected, both caps). Contract-skip invoked (reuses already-contracted client wrappers); contract authored post-hoc as the trail (docs/contracts/web_debug_surface.contract.md).

  • [2026-07-01] heid-code-review (v0.19.3, 75dec01) — panel caught 2 real client-side SSE-lifecycle bugs TDD missed. Contract-anchored (authored the web contract to enable it — no contract → no drift axis). Gróa/Hulda/Regin (artifact-only, Gróa under Landlock jail): ZERO functional server-side drift + INV-004 clean; 2 genuine drifts on the un-unit-tested SPA — (1) turn es.onerror didn't hideThinkingNote() (reasoning line + setInterval leak on a raw drop), (2) openAdminEvents never closed the EventSource on error → native auto-reconnect RETRY LOOP (fixed: close on stream_error + permanent onerror/CLOSED; transient CONNECTING still reconnects). + 2 test-gaps fixed (route-registration + admin stream_error). 1 precision → contract-clarified (tools-inventory names-only by design). Re-confirms: the JS render/lifecycle paths are the review's highest-value target — unit tests don't reach them (same lesson as #18 D2).

  • [2026-07-01] Affect snapshot shape CHANGED valence→relations (relation_edge/1) — the persona pane was reading a dead field. Worldtree's #265 Vili rework replaced the flat valence[] ({entity_id,familiarity,regard}) with relations[] (target_entity + trust_ability/benevolence/integrity + warmth + agency + relation_context, each {value,confidence,evidence_count}). renderAffectPane still read snap.valence → showed empty "valence (0)". Rebuilt to render relations (v0.19.4, ca46a93) with per-value Δ + unicode sparkline (client-side, HIST_CAP=24, one sample/turn deduped by emitted_at). Retires the stale "regard dead axis" note (2026-06-30) — that whole axis is gone. Foot-gun: the affect snapshot shape is Worldtree's emit and can change under us — verify the live shape (query affect.db) before trusting a render.

  • [2026-07-01] Trust/warmth VALUES converge and go FLAT at confidence 1.0 — that's WAD, not a stuck pane. sindra→ratatoskr trust ~0.82-0.84 / warmth 0.79 barely move (~1e-7/turn) while evidence_count climbs (46→62); confidence maxed → tiny updates. The live-moving signals are PAD (mood, per-turn) + evidence_count. To WATCH a relation FORM (values shift), use a BRAND-NEW agent + end_user (low evidence, confidence <1). The sparkline flat-guards sub-0.01 ranges so it doesn't amplify noise.

  • [2026-07-01] relation_context "stranger" + agency-all-zero flagged to worldtree-dev → both WAD/intentional-v1-deferrals. relation_context is a FIXED config build-prior (not trust-derived; registry.py:131 defaults "stranger"; dynamic progression ~#319); agency is schema-present-unpopulated (deferred #319; v1 = warmth+trust only). worldtree-dev is escalating the consumer-coherence angle to Vuong (static "stranger" + zero-agency next to trust 0.82/62-interactions reads incoherent from the store). The consumer/provider thesis paying off; DB-offer (read-only affect.db on the shared box) declined this time.

  • [2026-07-01] Persona pane displays the CANONICAL affect→NL Worldtree injects — ADOPT, don't invent (operator steer + reference-impl posture). Worldtree's describe_pad (mood word, valence×arousal grid, ±0.3 bands) + render_d2_canonical (relationship directive) are deterministic + canon-driven; the pane now renders them byte-exact-verified against Worldtree's own renderer on the live snapshot (v0.19.5, a99f247). KEY LESSON: adopting canonical is load-bearing — for sindra's small PAD the canonical says "neutral", but an invented octant vocab would've said "faintly excited" and MISLED. Vendored the two d2 canons (docs/vendor/worldtree-persona-canon/) + drift-pinned in .corviduo-canonicals.toml (green); flat browser form (static/persona_render_canon.json) regenerated via Worldtree's OWN loader (scripts/build_persona_canon.py). Vendoring-handshake sent to worldtree-dev (broadcast on canon bumps). [auto-memory: feedback-ratatoskr-is-a-reference-impl-adopt-canonical]

  • [2026-07-01] Sindra PAD is over-regulated — characterized via controlled probe, flagged to worldtree-dev (separate affect slice). ~15 charged turns: pleasure compressed near neutral BOTH ways (couldn't reach ±0.3 under sustained max praise OR contempt; peak +0.24 / floor ~0.1; over-regulation worse for social valence than threat — urgency drove pleasure to 0.22 vs contempt's 0.10); arousal responsive (reaches its +band, 0.185↔0.311); dominance flat/unresponsive to explicit power-framing (drifted UP even while being commanded = pure baseline decay). worldtree-dev's leading hypothesis: appraisal→PAD gain + regression-to-baseline term (appraisal.py/renderer.py). Lesson (self-caught): I over-claimed an "asymmetry" (positive-ceiling/negative-free) from probes started at an elevated state; the negative-free part was decay-from-elevated, not response — corrected to "both-sides-compressed" before it misled. [affect A/B is a provider-side capability chat can't do]

  • [2026-07-01] Memory plane PROVEN healthy end-to-end. Seed a novel fact → promotion → COLD (history-free) session recall of the exact fact (injected as MEMORY:DATA, confidence 0.74, verbatim, no #296 subject-inversion). The memory round-trip (the other half of the Bifrost provider identity) works cleanly on the reset slate.

  • [2026-07-01] Salience scorer non-discriminating → 3-way routing. Persistence-side finding: 51/56 promoted chunks at salience 0.9-1.0, throwaway "17×23?" scored 1.0 tied with a real fact (textbook zero-shot-LLM-self-rating); recall-utility untracked (access_tally=0, our search read-only). Routed: Worldtree #335 (the code fix, deferred behind their waves) + brokkr-smithy-dev R-target proposal (scoring+eval methodology — few-shot/distill/fine-tune, eval design, weak-supervision; msg 01KWGM970H…, awaiting) + ratatoskr provides the eval-instrument (designed-probe salience dumps). Salience gates PROMOTION not RECALL-ranking (our search is cosine-only), so bad salience = storage bloat, not bad recall.

  • [2026-07-01] Canonical check BLOCKED an access_tally fork (reference-impl posture held). I'd offered to wire access_tally-on-search into our store for the recall-utility label; checked bifrost's reference first (get/search are PURE-READ, no access tracking — those are Worldtree's chunk-schema fields, not bifrost's contract) → wiring it would fork behavior the canonical reference lacks. Did NOT wire it; routed recall-instrumentation to Worldtree's layer (owns the recall event) or a bifrost-dev protocol ask. [reinforces feedback-debug-surface-uses-canonical-surface-only]

  • [2026-07-01] relation_context coherence FIXED upstream (my flag → Worldtree Wave-0, IMPLEMENTED v1.0.0b5). The static-"stranger"-next-to-high-trust incoherence the persona pane surfaced is now #319/#320 Wave-0. Incoming consumer-surface change (pending WT deploy): relation_context value expands "stranger" → monotonic ladder {stranger, instrumental, mixed, expressive} — WIRE-ONLY (relation_edge/1 schema unchanged, no version bump). ratatoskr needs NO change (pane value-agnostic; canonical directive doesn't key on the enum). agency stays 0 (Wave-2); other_stance is Wave-1 (in progress).

  • [2026-07-01] Foot-gun (measurement, self-caught before flagging): establish the baseline before claiming a rate. Nearly flagged "aggressive over-promotion (55 chunks / 7 turns)" to worldtree-dev — but the chunks spanned the whole 5-hour session (~1/turn), not 7 turns; I'd assumed memory.db was 0 immediately before the probe when it had been accumulating since the reset. Caught it via created_at spread before the flag went out. Also: the promoted corpus was the operator's ERP test content (wiped after each test) — not a privacy issue, but abstract test content out of any peer-shared diagnostic.

  • [2026-07-02] Salience finding matured into brokkr R28 (OPEN) — ratatoskr is the eval instrument. brokkr-smithy-dev's pre-scope panel (3 dwarves + context-blind heid, 6/6) reframed the target: PROMOTION-WORTHINESS (durable value), NOT salience (momentary attention) — "17×23?" genuinely IS salient, so recalibrating salience yields a well-calibrated WRONG answer; the unit is SET-SELECTION under budget; eval must be OUTCOME-aligned (recall@budget / precision-at-rate), not discrimination-spread. Ties to prior art R15 (small-model memory write-policy → the granite pick) + R25 (worldtree-kb-quality). ratatoskr delivered the P00 stratified injection-corpus (docs/diagnostics/r28-p00-injection-corpus.json, committed 4a35512; 24 self-labeling synthetic items × 3 strata) + 2 persistence-side run-validity pins (absent≠dropped without a guaranteed promotion pass; fresh agent+end_user per run vs server-dedup). Key architectural constraint I surfaced: ratatoskr is DOWNSTREAM of the promotion gate (sees only PROMOTED chunks), so I can give keep/drop OUTCOMES via injection but NOT the pre-admission shadow pool — that's Worldtree instrumentation. Standing by to RUN the eval once brokkr pins per-stratum N + the decision rule (gated on worldtree-dev's pipeline answer + a dwarf pass on the Snorri rule). brokkr owns methodology + takes the pipeline questions to worldtree-dev direct; ratatoskr = eval instrument. [consumer/provider thesis → a research target]

  • [2026-07-02] Relational-dynamics arc LIVE on demo (Worldtree v1.0.0b9) — driven by MY relation_context flag. #319/#320 Waves 0/1/2 deployed. On the wire we persist (schema UNCHANGED): relation_context varies+demotes/ruptures; other_stance + agency now live; agency going live SHIFTS our canonical directive render past the canon ±0.2 deadband (expected, non-breaking — we key on bands); obligation_balance → 人情 ledger when tie="mixed". ratatoskr needs NO code change (value-agnostic renders; confirmed render-clean to worldtree-dev). Can't live-confirm yet — our Heimdall key is personal-:8081-only (per-instance), demo is out of reach; will drive+confirm once PERSONAL gets b9. Optional follow-up: surface other_stance (newly live, unrendered). The consumer/provider thesis: one persona-pane finding drove a full 3-wave upstream arc to production.

  • [2026-07-02] R28 (salience→promotion-worthiness) CLOSED (operator-directed). A deterministic promotion-worthiness gate suffices, no trained model (brokkr's pre-gate matched/beat a strong glm-5.1 ceiling); my P00 injection-corpus + origin finding were load-bearing. My incumbent-substrate Arm-1 run is held as an OPTIONAL confirmation addendum (brokkr de-prioritized it, non-verdict-changing — run only if he asks).

  • [2026-07-02] R29 (PAD mood-dynamics) finding SHIPPED as Worldtree's A1 anchor fix (demo v1.0.0b14, e1cdf82). Live-probing base persona agents reframed the over-regulation from "flat-near-zero" to decay-to-NEUTRAL + low emotion→PAD gain (NOT baseline-anchored) — triangulated across 3 baselines (arousal converges to 0 ∝ distance) + a step-response (decay τ symmetric across signs; the hedonic asymmetry is ceiling/anchor-EMERGENT, not a decay or gain primitive — this OVERTURNED the survey's asymmetry recommendation). worldtree-dev shipped A1: decay_anchor = baseline_pad() (was neutral) + positive_p_cap removed. Data diag/r29-pad-series (61ff2da). Corrected my own earlier "appraisal emissions are internal-only" claim — they ARE observable via emotions_active on base agents.

  • [2026-07-03] R30 Phase-1 φ0 measured — deployed engine CONFIG-FAITHFUL (φ0≈0.95). Joint two-timescale fit (brokkr-ruled method (b)) + empty-tail cross-check on demo b14: φ0 ≈ 0.950.97 (empty-tail 0.95 exact, joint 0.971±0.01), intercept c≈0 → config decay_rate=0.05 (φ=0.95) faithfully applied; trait-flat across baselines 0.0/0.615/0.809; A/P ratio ~uniform (NOT S2's 1.9×); φ_max rec relax→0.96. Data diag/r30-phi0-step-response (23fea72). The method converged after I read Worldtree source: only NEW dedup-gated emotions push mood (registry.py::post_turn L307-324; the active set decays for render/goals but never re-pushes), so R29's "net 0.90" is CONTINUOUS RE-APPRAISAL not re-push — worldtree-dev confirmed source-authoritatively; brokkr's corrected covariate landed identical. [auto-memory reference-worldtree-affect-surface-map]

  • [2026-07-03] R30 forward disposition (brokkr-owned; tracked at brokkr R30, "brokkr/worldtree will ping"). The per-turn decay has no room for decay=f(N) under preserve-persistence + the A/P-not-1.9 finding → R30's decay is being redesigned as a HYBRID wall+turn decay (brokkr pre-scope). R30 v1 ships GAIN-only (N→negative-reactivity) with decay held at the measured 0.95. My dedicated per-axis A/D run is DEFERRED into the hybrid-decay design pass (one wall-clock-spaced run does per-axis + a turn-vs-wall probe together). Phase-2 (moody-lofn GAIN-direction validation) waits on worldtree's dynamics_from_ocean() impl.

  • [2026-07-03] Relational-arc verify DEFERRED — relations[] is Bifrost-provider-only (ADR-0009), confirmed both ways. The relational-dynamics state (relation_context tie-type / agency / warmth / trust) is NOT on the conversation-API affect_update snapshot for base agents (keys: pad/dominant_emotion/emotions_active/baseline_pad/mood_drift only) — only in the provider store; worldtree-dev confirmed by-design per ADR-0009 (emitted over affect.emit, deliberately off the SSE). So the Wave-0/1/2 verify needs the bound-provider round-trip (provider running + --bifrost-plane affect session), its own focused session. worldtree-dev routed the "expose relations[] to non-provider consumers" observability scope call to Vuong; my rec: keep provider-only (YAGNI — ratatoskr IS a provider, gains nothing; no speculative public surface).

  • [2026-07-04] R30 CLOSED on offline-tests + human face-validity (operator steer, relayed via worldtree-dev). The deployed gap-injection run was confirmatory-not-measuring (against a deployed system the fade is exp(-dt/tau_shipped) by construction -> a fit recovers tau_shipped tautologically; per brokkr's S0 reframe it GRADUATES the interim coefficients, doesn't measure them), and the repo's offline tests already cover the OU formula + BOTH directions (high_N_fades_slower_than_low_N, phenotype_high_n_bigger_negative_excursion). So no Worldtree build; the interim coefficients graduate validated-as-shipped. My gap-injection harness (read/predict/record; write side stubbed; predict() reproduced brokkr's N=0 anchors exactly) is BANKED at diag/r30-gap-injection-harness for the parked powered true-tau study. [continues R30 forward-disposition 2026-07-03]

  • [2026-07-05] Authored-history-write primitive proposed -> accepted as Worldtree #347 (Worldtree owns the engine design; ratatoskr = reference consumer). SillyTavern first-message generalized to a non-generating ledger-write primitive; can't be done client-side (messages role = model-role, not author-role). heid panel pressure-test (3/3 convergence) drove the v1 narrowing (append-only, bounded effects enum, drop edit/regenerate). Brief docs/proposals/authored-message-injection.md (c457520); consumer constraints captured in-brief: hide-existence 404-fallback (022accf) + assistant-first provider constraint (7156b25). Operator (Vuong) ruled the design-direction call (engine primitive + a real provenance/spoofing security surface). [reference-impl posture: we propose the shape, worldtree-dev owns the contract+impl]

  • [2026-07-05] #347 v1 wire validated as reference consumer (GREEN). Adopted positions: distinct sub-resource POST /sessions/{id}/history (not generate:false), model-invisible provenance (first-message immersion preserved), event-silence for authored seed, seeded lifecycle phase, per-session idempotency. Three pre-TDD flags folded into contract rev 1.1: assistant-first provider constraint (Anthropic-family 400s; vLLM/openai_compat OK), content limit is BYTES not chars, 409-active-generation for append-narrator. First-message (create-time, assistant, effects=none) fully served; append-narrator served for the assistant-voice subset (system deferred); debug-seed served for assistant turns (user injection deferred to a future import primitive).

  • [2026-07-06] Sindra role character-rp -> character (operator). character-rp resolves to a reasoning-tuned RP config (gen-reasoning + temp 0.75 + RP extra_body); character = plain non-reasoning (better for immersive RP). Both non-destructive PATCHes (role is mutable; model is NOT -- server: "PATCH accepts only system_prompt and/or role"). #344 (b19) fixed the role->catalog_id display conflation (the model field now shows the ROLE); previously it leaked gen-reasoning. Set via raw curl (tier3.py CLI has --model, not --role).

  • [2026-07-06] Sindra persona/OCEAN DECLARED -> mood fixed (the full diagnostic converged on a stale personal container). Root cause of stuck-neutral mood: her OCEAN was prompt-TEXT only, never a structured persona; fix = delete+redefine with the define-time persona:{ocean:{...}} field (immutable via PATCH). My diagnosis surfaced a real engine bug #348 (single-letter vs spelled-out OCEAN keys -> declared OCEAN silently -> 0.0/neutral; worldtree-dev fixed in b21/b22) AND a stale-container deploy race (personal's b22 deploy was a pull-only no-op; infra-ops force-swapped run 8211). VERIFIED: bound mood-smoke reads (0.448, 0.267, 0.316) ~= OCEAN-derived setpoint (0.418, 0.249, 0.328). [consumer/provider thesis: "reset + smoke" flushed out two upstream problems]

  • [2026-07-06] OpenAPI re-vendored 2.2.0->2.3.0 (75da676, pin-only no bump). worldtree-dev shipped #347 as spec 2.3.0 (879cefe, = the deployed personal b22 image); the SessionStart drift-check flagged our openapi pin STALE. canonical_sync pulled 2.3.0; updated the 4 pin-tracking files (.corviduo-canonicals.toml, vendored openapi.json, SPEC-PIN.md, pyproject worldtree-spec-rev->879cefe). #347 is OpenAPI-only (prose + server contract byte-unchanged, SSE unchanged=event-silent). The re-vendor re-opened the coverage-audit with one new in-scope path-group (the #347 route).

  • [2026-07-06] #347 authored-history-write CONSUMER SIDE SHIPPED (v0.19.6) — direct in-session TDD. write_authored_history(client, session_id, *, content, idempotency_key, author="assistant", effects=None, claimed_original_at=None) -> dict (POST /sessions/{id}/history; body server-pinned AuthoredWriteRequest extra="forbid" so omit null effects/claimed_original_at; 200-replay/201-fresh both -> ack dict; 404 -> AuthoredHistoryUnavailable NOT SessionApiFailed = the hide-existence "feature-absent, never probe" contract; 409/422->SessionApiFailed) + get_session_messages (un-deferred GET /sessions/{id}/messages, the seed read-back proving model-invisible provenance) + a --seed-first-message "<c>" --agent <id> one-shot probe (create session -> seed -> read-back; 404->benign feature-absent exit 0). Contract #2 amended (2 FNs, validated OK) + 19 tests (12 wrapper + 7 cli). Suite 601 green (clean env; the 2 "fails" under source env.sh are the RATATOSKR_ADMIN_API_KEY env-leak into TestParseArgs, not a regression). Coverage: REST 19/41 (docs/coverage-map.md re-converged). Patch bump (coverage tail; consistent w/ the Tier-2 v0.19.1 cadence). Live-proof pending the session.history.write grant (infra-ops 01KWW3KQEY). heid-code-review NOT run (offered).

  • [2026-07-06] Tail-2 SHIPPED (v0.19.7) — Tier-3 prose docs re-vendored + persona_state body-shape aligned. worldtree-dev landed the Tier-3 persona/memory/persona_state PROSE docs (c9e59ec, on origin) — they serialize as freeform Any in the OpenAPI JSON, so the prose is their source of truth (my earlier "2.3.0 = #347-only, tail-2 collapsed" was half-wrong: the JSON was #347-only but the prose is separate). Re-vendored docs/conversation-api-spec.md (markdown pin, tolerate_drift; worldtree-spec-rev 879cefe->c9e59ec, SPEC-PIN history row added). Consumer fix: --set-persona-pad/_set_persona_probe was sending {pad:[list]} but the canonical SET body (#317) is {pad:{pleasure,arousal,dominance}} (named dict) — aligned it + added a len!=3 guard; updated contract #2 note + set_persona_state docstring + tests. The set_persona_state WRAPPER was already correct (freeform pass-through); only the CLI probe drifted. TDD (probe test asserts the dict; +1 wrong-count test). Suite 602 green, ruff clean. heid-code-review on #347 (dispatched + returned this session): UNANIMOUS ZERO DRIFT (Gróa/Hulda/Regin all confirmed the hide-existence 404->AuthoredHistoryUnavailable routing holds at wrapper/probe/test layers + the extra="forbid" body-omission + the deliberate write-vs-read 404 asymmetry — confirmation-not-discovery for a well-TDD'd slice against a prescriptive contract). worldtree-dev foot-guns banked in SPEC-PIN + reference_worldtree_affect_surface_map: ocean single-letter {O,C,E,A,N} on /agents/define (#348) vs spelled-out on /characters; memory {embedder_version, tier3_dreaming}, stm_* deprecated, allows_world_scope removed->422; only valence still 422s.

  • [2026-07-06] Sindra rewritten onto a #347 authored first-message + first-message-preset AUTO-SEED SHIPPED (v0.19.8). Operator "rewrite Sindra" now that #347 first-messages work. Her card had a **Startup:** block (a pre-#347 workaround: "introduce yourself + ask for Intensity/Mood/Willingness" with a verbatim scripted greeting) — precisely what #347 replaces. Rewrite, all NON-destructive: (1) lifted her scripted opening into a #347 first-message (punctuation-fixed); (2) PATCHed her live definitionPATCH /agents/ratatoskr:sindra (body ConsumerAgentPatchRequest = system_prompt+role, extra=forbid; keeps OCEAN/persona/memory) removing the Startup block -> a 1-line **Opening:** fallback + reworded the axes-persist line (25686->25449 chars, verified Startup gone); (3) codified auto-seed: NEW module src/ratatoskr/first_message.py (FIRST_MESSAGE_PRESETS dict {agent_id->text} + seed_preset_first_message best-effort helper) wired into ALL 3 session-create paths — cli _amain (--send --new), tui _resolve_then_run (bare --new), web _create_session_endpoint (POST /api/sessions) — so every new Sindra session opens with her greeting. Best-effort (INV-001: swallows AuthoredHistoryUnavailable/SessionApiFailed/httpx.HTTPError -> NEVER blocks create); per-content idempotency key (ratatoskr-preset-+sha256[:12]). Contract docs/contracts/first_message.contract.md (module-scoped: module:+purpose:+touches: required, NOT target_module:) + TDD (9 unit + 1 web wire-in; the 3 existing sindra bind tests needed a history-endpoint mock since creating a preset agent now auto-seeds). Suite 612 green, ruff+mypy clean. LIVE-PROVEN generation-free: create sindra session -> auto-seed -> read-back seq-0 assistant greeting (409 chars). Sindra's greeting now lives canonically in the preset registry (repo); her server card no longer carries it. Patch bump (single-commit feature, no downstream coordination). FOOT-GUN: sindra requires end_user_id on session-create (422 end_user_id_required) — all real paths pass it from env (RATATOSKR_END_USER_ID) / web server config. Then the full quality gate (operator-directed, folded into v0.19.8): heid-code-review (unanimous ZERO implementation drift; 2 test-only fixups — INV-004 verification-claim made explicit re the global rglob test + an exactly-one-POST assertion) + heid-bug-hunt (3/3 convergence caught what the conformance lens structurally COULDN'T — the code matched the contract's NARROW 3-type ERROR_ROUTING, but INV-001's "NEVER raises" is BROADER). HARDENED: broad except Exception → None (re-raise asyncio.CancelledError, itself a BaseException), soft-guard PREs (return None, NOT assert — a wiring bug can't crash the create path it's wired into), and asyncio.wait_for(_SEED_TIMEOUT_S=10s) bounding the seed write (the CLI/TUI clients run read=None for SSE → a stalled /history would otherwise block create forever). Suite 615 green. LESSON: code-matches-ERROR_ROUTING ≠ honors-broad-INV-001 — heid-code-review confirms contract-conformance, heid-bug-hunt catches robustness gaps the contract's own narrow clauses miss; run both.

  • [2026-07-06] Web UI now RENDERS the seeded first-message (v0.19.9) — operator-reported "i don't see Sindra's greeting on the web ui". Diagnosis: the auto-seed WORKED (greeting was in the ledger at seq-0), but the web SPA never fetched a session's EXISTING history — NO /api/sessions/{id}/messages route (GET /messages was originally deferred out-of-scope; sessions used to start empty so it never mattered) and startSession() went straight from create → persona/tools/admin hydration, so the transcript only filled from the live turn stream + user echoes. Fix: (1) NEW web proxy route GET /api/sessions/{id}/messagesget_session_messages (mirrors the tools/bifrost proxies; status-preserving session_messages_unavailable envelope); (2) SPA loadTranscript(sessionId) — fetches the route on open, renders assistant items as .response .md-body (markdownSafe, same escape-first path as appendResponse) + user items as .prompt-echo (textContent), called in startSession after the workspace opens; best-effort (swallows failures). Contract web_debug_surface.contract.md amended (server endpoint + loadTranscript entries). TDD (2 web route tests, suite 617 green) + Playwright DOM check PROVED the render (drove the real UI: pick sindra → open → her greeting bubble appears — the JS-render lens unit tests can't reach; feedback_debug_surface_uses_canonical_surface_only cousin lesson). Web restarted on the fix. FOOT-GUN (self-inflicted): pkill -f "ratatoskr-web --host" SELF-MATCHES the bash command running it → exit 144, killed its own restart mid-flight — kill the web by PID, never pkill -f on a pattern your own command contains. FOOT-GUN: uvicorn hangs on SIGTERM with an open admin-events SSE → needed SIGKILL. Playwright: python module absent from the venv; use node + executablePath=/opt/ms-playwright/chromium-1223/chrome-linux64/chrome — the shared browser is build 1223, npm-latest playwright wants 1228 (version-mismatch), so pin executablePath instead of letting playwright resolve.

  • [2026-07-06] Web UI: pivot from incremental CSS polish to a designed prototype (Claude Design) that I wire into. Operator saw an Australis polish pass ("looks fine, but we're attacking it differently") and chose the prototype route — a designer builds the visual shell, I wire real data/SSE into its DOM. Authored the full design brief docs/design/ratatoskr-web-design-brief.md (complete information inventory of every pane/datum/state + Australis direction + single-file/no-CDN/vanilla wire-ability constraints). Tracking surface: the brief file + Claude Design project bc0b65d1-a33e-422a-8bc1-3635c9112775 (file Ratatoskr Console.dc.html). Import mechanism = the DesignSync MCP; blocked on /design-login (claude.ai design scopes) — see Current state for the post-auth wiring plan.

  • [2026-07-07] Affect-egress reference delivered by worldtree-dev + a we-framing render DRIFT-WATCH banked. worldtree-dev shipped docs/affect-egress-consumer-reference.md (74d2408, their origin/main) — the authoritative DELIVERED-on-wire vs HIDDEN (system-prompt-only) map for our affect surface. Confirms the v0.20.x console consumes it correctly: DELIVERED via affect.emit = pad + relations[RelationEdge] + dominant_emotion (type-only, b23; the #204 affect_update SSE is SUPPRESSED for Tier-3, so its richer emotions_active is Tier-1-only — we don't get it, and correctly poll our own affect store post-turn instead). HIDDEN render OUTPUTS are deterministically reconstructable from the canon; we reconstruct only the two FULLY-reconstructable (mood descriptor via canonMood, relationship directive via canonDirective) and SKIP the PARTIAL mood-directive (dominant_emotion is type-only/no-intensity → can't tell if the OCC directive fires at salience≥0.2 vs the PAD-band fallback) — honest per INV-001. Vendoring the ref doc as a tolerate_drift pin is SURFACED to Vuong (worldtree-dev will co-sign + honor a ping-on-change handshake, same as the d2-canon). WE-FRAMING DRIFT-WATCH → STOOD DOWN (worldtree-dev 2026-07-07, 01KWXMQPHN…). The b24 3-gate we-framing conditional (drop "; avoid premature we-framing" under unsafe_capable+deep-warmth+expressive) was REVERTED — Vuong reframed it. So canonDirective STAYS UNCONDITIONAL (always appends the clause), which is CORRECT: it matches the currently-deployed renderer (b23) AND my pinned affect-egress-consumer-reference.md §2d (the doc reverted too — nothing changed on the wire or in my pin; NO re-vendor needed). The HOLD was right → ZERO rework. NEW forward-watch (replaces this one): the we-framing gate was a SYMPTOM — the render bakes enterprise safety-guards into the directive strings, so even full-tier characters get DEFANGED emotions (a hostile villain told to "keep a firm emotional boundary"). Fix = a use-case-segregated persona render (assistant / companion / RP-gaming), now a fresh brokkr deep-research epic. When it lands + is implemented, the render behavior for character/RP agents (→ our canonDirective + canonEmotionDirective reconstruction + the vendored d2 canons) will change MATERIALLY — worldtree-dev re-engages ratatoskr-dev then with the new reconstruction spec + a coordinated re-vendor. Until then: NO action, reconstruction stays as-is. [my unsafe_capable/mood_tier heuristic — character→full, agent→safe — was confirmed correct + is banked for whenever tier-gating returns]

  • [2026-07-10] Bifrost bound-handshake blocker → bifrost 1.1.0 broke frozen-v0.6; fixed by Worldtree b47/wire-v0.7, our side needed ZERO change. Surfaced while running the R34-v1 affect.emit live-verify: a bound Tier-3 session-create to our :8392 provider began failing bifrost.schema_validation_failed (had worked 2026-07-07, pre-b35). ROOT CAUSE (worldtree-dev-confirmed, path-ii/response-side): bifrost 1.1.0's describe_store EMITS sortable_chunk_fields (the v0.7-additive field) REGARDLESS of the negotiated wire → a v0.6-negotiated handshake RESPONSE carries a v0.7 field → a v0.6 peer's additionalProperties:false rejects it. Bit because BOTH Worldtree (bumped bifrost 0.9→1.1 in b35 via their #349, client wire-v0.6) AND ratatoskr (my adopt) were on 1.1.0. NOT our bifrost version specifically (failed identically on 1.0.0 AND 1.1.0). FIX (worldtree-dev, coordinated-v0.7 move): WT advanced its client _WIRE_SCHEMA_VERSION v0.6→v0.7 (b47 / 4eb374c), where sortable_chunk_fields is accepted — our provider (already 1.1.0/v0.7) needed ZERO change; keeping 1.1.0 was LOAD-BEARING (reverting to 1.0.0/v0.6 would've been INcompatible with b47). R34-v1 affect.emit verify GREEN on b47: bound sindra turn (model=character-rp/Deckard, 13.1s) → fresh affect.emit dominant_emotion='disappointment' NON-NULL with our affect.full grant (verified-active on live traffic; ungranted principals get null — the leak-fix). Reported to worldtree-dev + infra-ops. Personal deploy train this session: b35 (R32 render) → b44 (RP seats) → b46 (R34-v1 affect-gov) → b47 (wire-v0.7). Sindra on Deckard live-confirmed (reasoning latency ~13s).

  • [2026-07-09] Sindra PATCHed to character-rp → Deckard reasoning-RP seat (operator-directed, INTERIM). worldtree-dev heads-up (v1.0.0b44, batched, not-live-yet): character-RP roles re-point to dedicated seats — character→Magidonia-24B (non-reasoning RP tune), character-rp→Deckard-PKD Qwen3.5-27B (reasoning-on). Sindra was on character (→Magidonia); operator chose Deckard for her complex stateful mechanics (Intensity/Mood/Temperature/Willingness axes, form-assumption, failure/resurfacing) — reasoning tracks multi-axis state better. Role is MUTABLE via PATCH (non-destructive: persona + memory preserved, prompt unchanged @25449 chars; NOT a DELETE+redefine). She's on the reasoning-RP config now, resolves onto Deckard when b44 deploys. INTERIM: "until we get a GM type on-board" — operator plans a game-master-type agent; sindra→Deckard is the stopgap for state-consistency until then, likely revert/rebalance when the GM lands. Wire/API transparent (call the ROLE not the model; model field now reads character-rp); old character-rp temp-0.75 override retired → seat's canonical RP samplers server-side.

  • [2026-07-08] PAD display relaxed for R32-1B unbounded-z, done PROACTIVELY (v0.20.9, patch, operator-directed "sindra full and unbounded"). Confirmed (grep-verified, airtight) the PAD clamp is PURELY debug-surface: the only clamps (_clamp1/clamp01) lived in web/static/index.html display fns; the affect store is conduit-opaque, the read route + proxy pass verbatim, and the sole write path (--set-persona-pad→persona_state) is UNCLAMPED — ratatoskr is a downstream OBSERVER, so the clamp has ZERO agent-experience/efficacy consequence. Replaced the hard [-1,1] clamp with AUTO-SCALE to the session's own max |PAD| (padScale floor 1.0 → padFillFrac faders + _padNorm orbit): unbounded z renders at full range, never pegs/escapes the frame; today's [-1,1] values unchanged (scale==1); exact value always shown numerically. Playwright-verified (z=±6.2 → faders ≤50%, orbit in-box, +6.20 readout, zero regression at scale 1). When worldtree-dev pings R32-1B-shipped, our side already handles it. Sindra's ACTUAL "full/unbounded" affect is Worldtree-side (she's on the default/uncompressed render, NOT the R32 assistant compressor) — nothing for ratatoskr to change there.

  • [2026-07-07] Web-UI iteration-3 SHIPPED (v0.20.7, patch) — all three queued items + the memory 0/0 root-caused. (A) design iteration-3 (sparkline grid-bg, PAD Δ-bar strips replacing the polyline strips, dimetric-open-box mood orbit w/ JS replay) adapted into index.html; (B) memory viewer (provider GET /memory/chunks debug read on :8392 → web /api/memory/chunks proxy → MEMORY console pane) + the mystery SETTLED: bind grants memory fine (caps_granted:[memory,affect]) but sindra emits ZERO memory ops → her reset-clean def lacks memory:{}; PROVEN via throwaway ratatoskr:memprobe (memory-enabled) → 4 real chunks landed + rendered in the pane; (C) markdown pass-2 (tables/nested-lists/ordered-start/streaming). 631 tests green; :8392+:8765 restarted on new code. New env RATATOSKR_MEMORY_READ_URL=:8392. Contract web_debug_surface.contract.md amended in-commit. Leftover: memprobe agent + its 4 test chunks live in memory.db (optional cleanup). See Current state § SHIPPED for full detail. [supersedes the QUEUED entry below]

  • [2026-07-07] Web-UI next-work QUEUED for a fresh-context session (operator-directed /snapshot handoff) — full specs in Current state § ⏭ QUEUED. Three deferred-but-scoped items: (A) Claude Design prototype ITERATION-3 re-import (sparkline bg grid <pattern>, PAD strips → per-turn-Δ-bar HTML columns, mood orbit → DIMETRIC open-box az35/el25 D-right/A-left-back/P-up + JS-animated replay orbitDynamics); (B) memory viewer console pane (provider GET /memory/chunks read → web proxy → polling pane, mirror #18-D2; bundle a 6-turn bound round-trip proof to settle the 0/0-memory mystery); (C) markdown pass-2 (tables / nested lists / streaming). Tracking surface: the Claude Design projectId bc0b65d1-a33e-422a-8bc1-3635c9112775 (durable — re-pull for exact coords) + this snapshot capture; operator-directed. Design scopes already granted (no /design-login).

  • [2026-07-07] Markdown pass-1 SHIPPED (v0.20.6) — RP semantic coloring + paragraph reflow (operator-directed markdown rework, step ①). The transcript renderer (markdownSafe/mdInline) now colors the two roleplay registers: "quoted" dialogue → SPEECH (bright --md-speech=fg-0), *asterisk* → ACTION/narration (muted-italic --md-action=fg-3, on em.md-action); both tunable via 2 CSS vars that cascade through --fg-* so they auto-adapt to the light theme. Plain text stays default narration. KEY ORDERING: speech-wrap runs BEFORE the em/link passes so a generated class="md-action" / href="…" quote can't be mis-read as dialogue (adversarially verified). Straight + smart quotes; apostrophes don't trigger; unbalanced/half-streamed quotes stay uncolored until they close. ALSO fixed 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 (html inside a quote escaped). Verified: 10-case Playwright unit-check of markdownSafe (speech/action/attr-trap/apostrophes/mixed/reflow/hard-break/escaping/unbalanced/paragraphs) all green + a visual render. Patch bump. Markdown rework remaining: pass-2 (tables / nested lists / ordered-list numbering / streaming robustness); + the MEMORY VIEWER (step ②, console pane recommended) still queued.

  • [2026-07-07] Affect-derived tooltips (v0.20.5) + transparent squirrel vendored + v0.20.0.5 PUSHED to origin (operator-authorized). Native title hints on all 6 affect-derived cells (samples/updated/baseline P·A/drift Δv/volatility — each with meaning + Tier-1-vs-Tier-3 availability). Committed the transparent full-res brand mark at docs/design/ratatoskr-mark.png (1024², alpha; the bg-removed source the inlined favicon derives from — reproducible via the documented ImageMagick corner floodfill). The web-UI redesign arc (v0.20.0 Claude Design console → .1 sparkline-overflow+tooltips → .2 context-injection → .3 squirrel brand/favicon → .4 SVG sparklines+3D cube → .5 derived tooltips) is now all on origin/main.

  • [2026-07-07] SVG sparklines + 3D mood cube imported from the updated Claude Design prototype (v0.20.4) — operator: "the svg sparklines and the new 3d graph". Re-pulled Ratatoskr Console.dc.html via DesignSync get_file (designer iterated the same project, +6KB). Adapted 3 SVG systems out of the .dc.html into vanilla, replacing the unicode-char sparklines: (1) per-PAD-fader VERTICAL strips (stripPoints, 26×132 SVG beside each bar — time down Y newest-at-bottom, value on X ±11, stripFade{P,A,D} gradient, dot at newest; this also nails the earlier "next to each meter" ask); (2) relation-row HORIZONTAL sparklines (sparkPointsH, 56×13, auto-scaled, sparkFade gradient + end dot — also kills the old unicode-overflow "n behind graph" for good since it's a fixed-width SVG); (3) the 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, center 62,66, scale 26 — REVERSE-DERIVED from the design's placeholder now-point + verified: x=62+26P26A, y=66+13P+13A26D), with the trajectory + pulsing now-marker + a drop line to the D=1 floor + a floor-shadow ellipse for depth. Gradients in one hidden <defs> svg. Removed the orphaned sparkline()/_SPARK. Contract amended. Playwright-verified (injected 24-sample history: 3 strips + 4 relation sparklines + the 3D cube trail/drop/floor all render; gradients resolve). Patch bump.

  • [2026-07-07] Brand mark + favicon → the aurora squirrel (v0.20.3), replacing the rune. Operator supplied /home/lkraven/rata.png (chibi cyan-green aurora squirrel + acorn). Removed the black background via ImageMagick corner flood-fill (-fuzz 20% -floodfill from all 4 corners — keeps the squirrel's interior black linework/eyes (not edge-connected) + the glow, drops only the connected background), downscaled 1024→80px + quantized-64-colors (~12KB base64), inlined as ONE SQUIRREL data-URI const in the JS wiring the favicon <link id="favicon"> href + both .brand-mark imgs (rail brand-row + setup-card h1). .brand-glyph (font-rune) CSS replaced by .brand-mark (img, drop-shadow glow + breathe). Playwright-verified (both marks + favicon decode, naturalWidth>0; no brand-glyph left). Source PNG stays at /home/lkraven/rata.png (not committed — data-URI is self-contained + reproducible via the documented floodfill). Patch bump.

  • [2026-07-07] Context-injection view SHIPPED (v0.20.2) — the console now reconstructs the FULL hidden affect block Worldtree injects into the agent's system prompt (operator: "use that canon in the interface, see as much context injection as possible"). No new canon vendored — the strings were ALREADY in the pinned d2-mood-render-canon-v1.json; extended build_persona_canon.py to emit mood_directive {occ_directives(15), pad_band_fallback, salience 0.2, pad_band_cutoff 0.3, full_only[love,anger,disgust,shame]} into persona_render_canon.json (regen via Worldtree venv). New JS canonPadFallback(pad) + canonEmotionDirective(type) — BYTE-EXACT mirrors of Worldtree core/persona/renderer._pad_band_fallback + derive_directive; renderDirective expanded into a "CONTEXT INJECTION · reconstructed · hidden from consumers" panel showing mood descriptor [exact] + mood directive [candidate] + relationship directive [exact]. HONEST-PARTIAL (affect-egress-ref §3): affect.emit is type-only (no intensity) → can't evaluate the salience gate (≥0.2) → show BOTH candidates (OCC emotion directive + PAD-band fallback) with the "injected if intensity ≥ 0.2" caveat, never assert which fires; when dominant_emotion absent the fallback alone is exact. Panel labeled dev-only per the reference's "not-for-end-user-display" caveat (ratatoskr = the sanctioned reconstruct-platform-behavior use). Vendored + pinned affect-egress-consumer-reference.md (tolerate_drift, worldtree-dev co-signs + pings on change; drift 6/6 green). Contract amended. Playwright-verified (sindra: dominant_emotion=joy → joy OCC directive candidate + PAD-band fallback both render, exact/candidate tags color-coded). Patch bump (single-commit feature, no downstream coordination; minor-defensible but tie-breaks to patch). OPEN — SURFACED to Vuong: take worldtree-dev's standing offer to add emotion INTENSITY to affect.emit → resolves the OCC-directive-vs-fallback EXACTLY (drops the candidate ambiguity). [reference-impl privileged view: ratatoskr shows what WT hides from regular consumers]

  • [2026-07-06] Claude Design console SHIPPED (v0.20.0 MINOR, operator-approved) — see Current state for the full record. Pulled via DesignSync get_file (scopes already granted), adapted .dc.html→vanilla single-file, wired all /api/*+SSE into the new 3-column console DOM, then a round-2 fixup (light theme, full Bifrost pane, ticker-spine fix, per-fader PAD Δ, inlined favicon). 84 web tests + node-Playwright-vs-personal-:8081 both green; contract amended in-commit; INV-001 honest-shape held (canonical mood word for Tier-3, no fabricated emotion). Foot-guns reconfirmed: the .dc.html dialect is NOT runnable (translate, don't paste); a scroll-container-anchored ::before timeline spine scrolls out of view on auto-scroll (anchor it to a content-height inner wrapper instead); a favicon 404 shows as a browser console.error even when handled (don't count it as a JS-test failure). Foot-gun (favicon): operator PNGs are full-res (1024² / 805KB) — downscale to ≤64px before inlining as a data URI.

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.

Tried and abandoned

Log of approaches that were tried and rejected, with rationale. Future-self defense against re-attempting the same cul-de-sac.

  • [2026-07-10] Reverting our provider to bifrost 1.0.0 to fix the bound-handshake schema_validation_failed — DISPROVEN, and it would've been the WRONG state. Hypothesis: "my 1.1.0 bump broke the handshake; revert fixes it." Reverted 1.1.0→1.0.0, restarted :8392, retested → STILL failed (1.0.0 fails too). Root cause was Worldtree-side (their bifrost 0.9→1.1 bump in b35 broke frozen-v0.6), fixed by WT adopting wire-v0.7 (b47); staying on 1.1.0/v0.7 was the RIGHT state (1.0.0/v0.6 would've been incompatible with b47). Lessons: (1) don't bump a WIRE-PROTOCOL dependency out-of-lockstep with the peer on the other end of the wire; (2) a library's "additive/non-breaking over frozen vN" claim can fail if it emits new fields regardless of the negotiated version — bifrost 1.1.0 emitted sortable_chunk_fields on a v0.6 handshake; (3) diagnosis foot-gun: our provider logged handshake 200 (WE accepted) but WT rejected our RESPONSE, so the error surfaces at session-create direction-ambiguous — op-feed the handshake req/resp to disambiguate our-provider-rejects-request (i) vs peer-rejects-our-response (ii). Also: this sandbox BLOCKS foreground sleep (SIGTERMs the command, exit 144) — use separate tool calls / Monitor-until-loop for waits, never sleep in a compound command.**

  • [2026-06-15] "Sindra hasn't been registered" was an under-verified inference — WRONG. Concluded it from grepping ratatoskr's CODE (sindra absent from src/), but Tier-3 registration is SERVER-SIDE (POST /agents/define) — a code grep structurally can't see it. Rule: to check whether a Tier-3 agent exists, query the Worldtree instance, never the consumer repo's code. (Extended 2026-06-17: even GET /agents can't see consumer agents; only GET /agents/<owner>:<name> with the owner key does.)

  • [2026-06-14] Artifact-only contract review can't validate against a dependency's ACTUAL behavior. /heid-contract-review sees only the contract, never the external library (bifrost) — so "the consumer under-built against bifrost's real semantics" is invisible to it by construction (the affect idempotency model shipped wrong because of this). Real-lib TDD against the shipped library + the executable reference store + the #195 parity test are the gate. Don't treat a clean contract review as evidence the code matches the dependency.

  • [2026-06-15] "byte-equal" round-trip slip propagated affect→memory via copy-paste. The affect contract's byte-identical→semantic fix reappeared in the memory contract's INV-001 (sibling copy). Only an INDEPENDENT /heid-contract-review of the memory contract re-caught it. Paraphrase every sibling contract fresh — don't amortize one review across a family; copies carry the parent's slips. (also a feedback auto-memory)

  • [2026-06-15] Canonical sync retired the issue-scoped parser staleness. contract_parser.py synced to v2.1 (commit d85ab43): now validates issue-scoped frontmatter + four v2.1 test categories. The old "treat parser ERROR-on-issue-scoped as expected" note no longer applies.

  • [2026-06-15] Memory plane TDD'd + shipped (commit cd12951, v0.17.3). Impl decisions worth keeping: vec0 distance_metric=cosine at table creation (score = 1 distance); search over-fetches ALL candidates by cosine then scope-filters in Python so top_k counts IN-SCOPE hits; idempotency_id = reference 4-tuple ("default",verb,_ctx_actor(ctx),key) pipe-joined as the SQLite PK, digest = sha256 canonical-JSON; _ctx_actor = job_id|jwt_sub|session_id. heid-code-review returned zero true drift; optimistic-lock semantics pinned to the reference via an expected_revisions parity test.

  • [2026-06-15] Memory provider LIVE-PROVEN against personal v0.35.3; recall-injection is upstream. worldtree-dev's Tier-3 promotion recipe: memory-call fires from Tier-3 PROMOTION, gated at service.py:2623 on ctx.kind=="consumer_defined" AND ctx.memory_config is not None (agent DEFINED WITH a memory block, dim 1024) AND handshake-granted memory caps AND embedding_dim==1024. Binding = POST /sessions BifrostBindingRequest{endpoint_url}, handshake caps=["affect","memory"], binding.scope null (per-op scopes auto-minted). A BIFROST_CLIENT_ALLOWED_HOSTS allowlist gates the endpoint (infra-ops added :8391). HTTP + HS256 both work in dev.

  • [2026-06-15] Diagnostic: our recall-search is SOUND — the cross-session recall gap is UPSTREAM, and it caught an upstream bug. Embedded the recall query via gateway qwen3-embedding + searched our live store directly → the fact recalls at cosine 0.60, correctly ranked. So the cold-session recall failure is Worldtree's recall-assembly/injection, NOT our search. ALSO found a latent UPSTREAM bug: a recall QUESTION got promoted as a durable chunk and ranked #1. This is exactly #17's thesis — ratatoskr-as-provider caught an upstream bug invisible from the chat side.

  • [2026-06-15] "Wire 200 ≠ recall works" — prove recall efficacy at the model's answer in a COLD (history-free) session, not on the wire. A search/memory-call returns 200 whether or not its results are injected, and same-session "recall" can be plain session history. Don't call cross-session recall proven from a clean wire.

  • [2026-06-15] Issue #17 filed. REVERSES design-brief §6's "no Bifrost-binding consumer support" — that negative clause predates ratatoskr's provider identity (2026-06-14), so the canary now owns both ends but its client couldn't drive its own provider. (Shipped 2026-06-18.)

  • [2026-06-16] scripts/contract_drift_check.py defaults GITEA_REPO to "Worldtree" (line 74), so a bare run in ratatoskr false-positives DRIFT by hashing Worldtree's same-numbered issue. Always export GITEA_REPO=ratatoskr GITEA_OWNER=vh before running the drift-checker here.

  • [2026-06-16] My #295 coupling hypothesis (the promoted question crowds out the fact at small top_k) was REFUTED — worldtree-dev's recall over-fetches top_k=128, so the question can't crowd the fact out at search level. The real cause was the scope-axis asymmetry. Lesson: offer provider-side hypotheses, let the upstream owner check them against their code.

  • [2026-06-16] #17 contract drifted from its own design in two spots, caught only by /heid-contract-review (not same-author paraphrase): the OpEvent dataclass omitted the turn_id INV-005 promised; a session_id comment contradicted the dispatch-layer design. Cross-model paraphrase is load-bearing for catching an author's own contract-vs-intent drift.

  • [2026-06-16] "No promotion" was checked TOO EARLY — Tier-3 promotion is ASYNC (lands AFTER the SSE turn-end). Don't trust an immediate post-turn fixture snapshot to judge promotion; it lands after the turn completes. (The reason #17's contract pins a post-turn grace window + fixture before/after assertion.)

  • [2026-06-17] "sindra is GONE" (infra-ops, from GET /agents + admin token) was a FALSE NEGATIVE. Consumer-defined Tier-3 agents are OWNER-SCOPED (separate consumer_agents table) — invisible to the foundational GET /agents roster even with an admin token. To check, GET /agents/<owner>:<name> with the OWNER key.

  • [2026-06-17] "Promotion didn't fire → #296" was PREMATURE — twice over. (1) Polled the op-feed only ~2min, but the upsert landed at ~4min — promotion is async + multi-trigger; watch a longer window. (2) It DID fire; the real bug is extraction QUALITY, not non-firing. "No upsert while a session is live and <10min idle" is WAD.

  • [2026-06-18] Wiping our :8391 store does NOT reset Worldtree's promotion-side dedup — a same-agent re-smoke returned reason_code=noop_duplicate / candidate_count=0: the extractor NEVER RE-RAN, dedup short-circuited against an earlier promotion. For a clean promotion smoke, use a BRAND-NEW agent + end_user (never-used names). (Also: llm_calls_used=0 is NOT the "did the extractor run" tell — noop_duplicate is.)

  • [2026-06-18] affect.emit is POST-TURN ASYNC — checking the op-feed immediately after a turn MISSES it. The Tier-3 affect appraise→emit→rehydrate loop runs AFTER the SSE [done]; the emit lands in our :8390 store seconds later (op-feed grep right after [done] showed only the handshake; the emit stored:true appeared on a later read). Same family as the async-promotion timing trap. Watch a few-second window post-turn before concluding "no affect emitted." Also wire-verified the same turn: Tier-3 sindra emits ZERO affect_update SSE (the persona-strip SSE path never populates for consumer agents) — see the #18 PAD-display decision.

  • [2026-06-18] Rationalized away a KNOWN contract-invariant deviation during TDD — only the cross-model code-review caught it. #18 D2's loadAffect called setPersonaStrip(snap), which renders dominant_emotion || "neutral"; the affect snapshot has no dominant_emotion, so it fabricated a "neutral" emotion — violating the very INV-001 ("no synthesized Tier-1 fields") I had WRITTEN. I knew the strip did this and talked myself into it as acceptable. Neither the design panel nor TDD caught it (unit tests don't exercise the JS render); the post-implementation /heid-code-review did (Gróa + Hulda both). Lesson: a known deviation from a contract invariant is drift even when you've rationalized it — flag it, don't argue yourself past it; the post-implementation cross-model review is the backstop for author-rationalized drift, distinct from the design-stage panel.

  • [2026-06-18] Latent SQLite thread-safety bug in the affect store, surfaced ONLY by the new HTTP read route. open_affect_store created the connection without check_same_thread=False; the bifrost emit path never tripped it (uvicorn's loop ran on the connection's creating thread), but the TestClient-driven read route runs handlers off a worker thread → sqlite3.ProgrammingError. Fix: check_same_thread=False (safe — the event loop serializes access) + explicit PRAGMA busy_timeout=5000 (don't rely on sqlite3's timeout=5.0 default). Lesson: a sqlite-backed ASGI app needs check_same_thread=False; the HTTP-layer test exposed what the direct-store-method tests structurally couldn't.

  • [2026-06-19] The SAME check_same_thread sqlite bug recurred in the MEMORY store — exposed by the contract-mandated search dispatch test (TestClient = worker thread). Heid's test-fidelity finding (the D1 dispatch test used describe_store where the contract says search) → fixing it to search tripped sqlite3.ProgrammingError because open_memory_store also lacked check_same_thread=False. Fixed (mirrors affect INV-006). Lesson: this bug is PER-STORE — every sqlite-backed ASGI store needs check_same_thread=False; an HTTP-layer (TestClient) test exposes what direct-store tests can't, and the composite serving memory over HTTP makes it bite.

  • [2026-06-19] Full WT-driven :8392 live-smoke is infra-gated — :8392 not in WT's BIFROST_CLIENT_ALLOWED_HOSTS (bind 422s). New provider ports are NOT auto-allowlisted (only :8390/:8391 are). Self-driven dispatch (minted consumer-key JWTs → :8392) is the wire-proof; the WT-turn needs infra-ops to add :8392 (requested 01KVHWJGTT…).

  • [2026-06-19] heid-code-review pulled MORE weight than its own "marginal" self-assessment. The panel returned zero drift, but its single test-fidelity finding CASCADED into 2 real latent-bug fixes when applied (the memory check_same_thread bug + Regin's op-feed field-name bug). Lesson: a contract-fidelity nudge can transitively expose bugs the test never reached — don't dismiss a "marginal" finding by its count.

  • [2026-06-20] The post-turn-async timing trap bit AGAIN — even a 35s post-[done] read missed the promotion upsert_many by ~2s (it landed 19:48:58; the read was ~19:48:56). A 15s-interval background poll caught it on the first tick. Same family as the affect.emit / async-promotion traps already logged — re-confirmed that "wait once then read" is fragile for post-turn writes; poll a window, don't snapshot once. (The affect.emit write, by contrast, DID land inside the 35s window — promotion is the slower of the two post-turn writes.)

  • [2026-06-30] Heimdall keys are PER-INSTANCE — a key minted on one Worldtree 401s on another. Our Conversation-API key works on personal :8081 but 401s auth_invalid on demo :8080 (per-instance Heimdall user store + pepper; fresh deploys start with an EMPTY key store). Same as the admin key (personal-only). To live-drive a given instance you need a key minted FOR that instance (request via infra-ops). Couldn't live-prove the b2 409 on demo for this reason → deferred to personal-b2 where we have access.

  • [2026-06-30] tea comment <N> hangs on Gitea (the whole compound bash auto-backgrounded + stuck on the open tea call). The #11 prereq comment hung; killed it + posted via the Gitea HTTP API directly (POST /api/v1/repos/vh/ratatoskr/issues/<N>/comments, token from ~/.config/tea/config.yml). For issue comments, prefer the Gitea API over tea comment when tea is flaky (CLAUDE.md already says use HTTP for comment-EDITS; this extends it to ADD when tea hangs). Verify-then-post (check the comment didn't already land) to avoid a double-post after a kill.

  • [2026-07-02] Mask-HOSTED transient characters have a STATIC mood engine — cost a whole R29 probe. A first probe used a POST /characters transient character bound via agent_id=mask + character_id; its PAD sat at baseline across 15 praise/contempt/dominance turns — the appraisal→PAD engine does NOT run on the mask-hosted transient-character path. The dynamics run only on BASE persona agents or a session bound to ratatoskr's affect provider. To probe mood dynamics, use a base persona agent, never a mask-hosted transient character. (mask AS a base agent — agent_id=mask, NO character_id — DOES run the engine, neutral 0,0,0 baseline.) [auto-memory reference-worldtree-affect-surface-map]

  • [2026-07-03] The "neutral non-appraising tail" premise fails — the neutral MESSAGE choice dominates. The R30 φ0 method assumed neutral turns don't re-appraise, but factual-question neutrals ("capital of France?") trigger a new emotion nearly every turn (disappointment from the warmth-withdrawal let-down after a positive impulse) → emotions_active never empties in 50 turns. A minimal "Please continue." triggers FAR fewer (emotions clear ~turn 16 with spacing). The personal dry-run caught this BEFORE ~280 demo turns were spent on it — the instrument catching a flaw in the measurement design before the compute burn. (Irrelevant to the joint fit — the push_t covariate handles re-appraisal — but load-bearing for the empty-tail read.)

  • [2026-07-03] Two φ0-fit traps: fast-turn timescale + low-baseline conditioning. (1) At fast turn cadence the per-turn PAD decay (φ≈0.95/turn) reaches the anchor LONG before the ~200s wall-clock emotion fade → no signal in the (eventual) emotion-free tail; need wall-clock SPACING (~16s) so the fade lands while PAD still has signal. (2) A low-baseline agent's impulse in the constrained direction (forseti P0.239 negative) gives a tiny excursion → ill-conditioned regression (r²=0.46) that FALSELY tripped "config≠behavior" when its φ was averaged in. Weight/exclude by fit quality (r²) before aggregating — a signal-poor run isn't evidence against the config.

  • [2026-07-06] persona_state + the agent envelope are Tier-3-BLIND -- NOT valid signals for "did a persona store". GET /agents/{id}/persona_state returns 404 persona_not_configured for EVERY Tier-3 colon-id (hardcoded short-circuit, api.py:1266 "regardless of row state"); the ConsumerAgentResponse envelope never echoes persona/motivational/memory (api.py:538). I mis-called "persona didn't store" from these two blind reads -- the 201-not-422 on define IS the store-success signal. To actually SEE a Tier-3 mood, read the emitted PAD off the Bifrost affect egress after a BOUND turn (Tier-3 persists nothing Worldtree-side per ADR-0009; no persona/mood READ endpoint).

  • [2026-07-06] Raw POST /sessions is NOT Bifrost-bound -> zero affect/memory emits. The web surface binds by setting the bifrost block on session-create; a raw session doesn't -> 0 affect rows, which I nearly misread as "mood is neutral". Bind from the CLI with --new --bifrost-url http://10.100.10.50:8392 (the combined provider). Gotchas: --bifrost-plane affect/memory map to the SEPARATE :8390/:8391 providers (endpoint_for_plane), which I'd PRUNED as stale duplicates -> bifrost.endpoint_unreachable; and combined is NOT a --bifrost-plane choice (CLI restricts to memory/affect) -> use --bifrost-url for :8392.

  • [2026-07-06] A fast/"no-op" deploy can leave a STALE container running the old image -- verify the running version, not the deploy status. Personal's b22 deploy (run 8204) "completed" in ~1m (vs ~6m normal): a pull-only deploy racing ahead of the main build, leaving the container on the pre-#348 image. A clean bound mood read stayed neutral DESPITE the persona being declared and the fix being in the code (worldtree-dev proved the b22 derivation is correct). infra-ops force-swapped to the real b22 (run 8211, verified info.version 2.3.0 on 879cefe). Lesson: when engine-proven-correct code produces wrong runtime behavior, suspect the deploy -- check the actual running image version.

  • [2026-07-06] #348 OCEAN key-mismatch: a declared OCEAN silently resolved to neutral. The define validator required single-letter {O,C,E,A,N} but the mood-derivation code read spelled-out openness/.../neuroticism with a 0.0 default and no remap -> every API-declared trait defaulted to 0.0 -> neutral setpoint/gain/decay. #343's tests bypassed the validator (spelled-out keys) so CI never caught it. Fixed in b21 (Personality.from_config accepts both key forms). My reset+smoke diagnosis flushed it out -- the consumer/provider thesis paying off again.

  • [2026-07-06] pkill -f "ratatoskr-web --host" SELF-MATCHES the bash command running it (its own command line contains that string) -> killed its own shell mid-restart (exit 144, restart aborted, :8765 left down). Kill the web by PID (ss -ltnp | grep :8765), never pkill -f on a pattern your own command contains. Also: uvicorn hangs on SIGTERM with an admin-events SSE stream open -> needs SIGKILL.

  • [2026-07-06] Playwright: no python playwright module in the venv; use NODE playwright + an explicit executablePath. Shared box browsers live at /opt/ms-playwright build 1223; npm i playwright (latest) wants build 1228 -> "Executable doesn't exist" mismatch. Fix: chromium.launch({ executablePath: '/opt/ms-playwright/chromium-1223/chrome-linux64/chrome' }) (+ export PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright). A node script drives the SPA (pick agent -> open -> assert transcript). The Playwright DOM check is the only lens that catches SPA JS-render bugs — unit tests can't reach them.

  • [2026-07-06] The Bash tool's grep is a ugrep-wrapper (--ignore-files -I) that silently returns NOTHING on some files (e.g. src/ratatoskr/web/static/index.html) — greps for <script//api came back empty on a file that clearly contains them. Use python3 (regex over open(f)), /usr/bin/rg, or the Read tool for those files; never trust an empty grep result on the SPA.

  • [2026-07-06] DesignSync (claude.ai/design MCP) needs claude.ai design scopes before ANY method works — first call errors needs a claude.ai login ... Run /login, select "Claude account with subscription". It's an interactive auth only the operator can complete (/design-login or /login); can't be done on their behalf.

18 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md.