Worldtree on v1.0.0b2 (both demo + personal); ratatoskr v0.18.4 all pushed. Session arc: web combined-bind default (v0.18.1), bifrost 1.0.0 repin (v0.18.2), b1/b2 eager 409/503 status mapping (v0.18.3/.4), readonly-admin key collected (#11 prereq cleared), and the regard-dead-axis finding (provider-side catch -> worldtree-dev escalating to Vuong). Next substantive effort = the v1 coverage-audit (folds in the deferred live-409 + b2 spec re-vendor).
40 KiB
Persistent memory — ratatoskr
Last updated: 2026-06-30
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 insrc/ratatoskr/provider/, depends onbifrost(provideroptional-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-06-20:
#17 and #18 BOTH CLOSED — the composite both-plane binding is fully proven. #18 shipped
v0.18.0 (359dbb1): D2 (PAD read-endpoint, v0.17.14) renders live PAD in the web pane from our
:8390 store; D1 (composite endpoint, v0.17.16 7f4ceaa) — build_combined_provider_app
(provider/combined.py) on :8392 wraps bifrost's public build_combined_app over BOTH stores +
the shared affect read route; one bound session drives memory.* AND affect.* through ONE endpoint,
op-feed deriving plane per path. Suite 503 green. #17 closed in the tracker 2026-06-20
(shipped v0.17.8–.13 + the v0.17.17 op-feed field fix).
#18's final leg — the Worldtree-DRIVEN composite turn — RAN and is PROVEN end-to-end + persisted
(2026-06-20). infra-ops added 10.100.10.50:8392 to the personal WT's (:8081)
BIFROST_CLIENT_ALLOWED_HOSTS (thread 01KVHWJGTT…), unblocking the smoke. A real WT turn through
:8392 (session b83a66b6, agent ratatoskr:sindra, fresh end_user resmoke-choco-1) drove the
FULL both-plane lifecycle on ONE endpoint, caps-routed by path: handshake
(caps_granted=[memory, affect]) → affect.fetch + memory.search (reads) → affect.emit
(stored:true, PAD row in affect_snapshots) → memory.upsert_many (upserted:1, chunk
2df1b79de761b948 in memory_chunks). Both writes verified directly in our SQLite. The
model-backend outage that blocked the first attempt (both agents' models model_unavailable) was
operator-fixed mid-session, then the resmoke completed clean. No open legs remain on the composite.
bifrost repinned 0.8.0 → 0.10.0 (floor, provider extra). 0.10.0 made affect.fetch
MANDATORY (strong-or-absent: _supports_affect_plane requires affect_supported+emit+fetch,
gating EVERY affect op incl. emit) — so the repin FORCED affect.fetch (v0.17.15, conformed
to bifrost's reference InMemoryAffectStore.fetch → {found, snapshot?}) or our shipped affect
plane would 400. The composite's affect cap depends on it.
OPERATOR SESSION STATE — :8390/:8391/:8765 shells are PRE-#18 code (foot-gun). web :8765
- affect
:8390+ memory:8391are prior-session background shells on OLD code. The:8392composite provider is RUNNING on NEW code (ratatoskr-combined-provider, pid started Jun19,RATATOSKR_OPFEED_PATH=/tmp/ratatoskr-combined-opfeed.jsonl, sharedaffect.db/memory.db) — now:8392-allowlisted and WT-turn-proven. To see the full web stack on new code, RESTART:8390/:8765from current code (D2 web needsRATATOSKR_AFFECT_READ_URL). Consumer/owner key =wt_live_d81b…(~/.config/ratatoskr/provider.env, mode 600, rotate via infra-ops); providers SQLite + sqlite-vec,memory.db/affect.dbat repo root (live sindra PAD: vuong + theresmoke-choco-1smoke fixture).
Tier-3 memory PROVEN end-to-end (earlier this session): ratatoskr:terse-probe
cold-recalled a seeded user fact (scope_any → 1 hit @ cosine 0.6994), and the verbose
sindra-probe too under #296 Stage 2 (v0.36.0). The #296 extraction-quality arc closed
(Stage 1 v0.35.19 gate + Stage 2 v0.36.0 user-only extraction at worldtree-codex; hard-
linguistic layer → Worldtree #305). :8081 runs v0.36.0.
Sindra: ratatoskr:sindra, thoughtful-character role → mistral-small-4-reasoning
(DELETE+redefined on v0.35.16; memory:{} block trips the promotion gate). Owner-scoped
(separate consumer_agents table) — invisible to GET /agents; check via
GET /agents/<owner>:<name> with the owner key.
Standing: Worldtree spec pin v0.35.16 (f1b59f8); bifrost 0.10.0 / wire v0.6
(scope_all+scope_any). Heimdall key env-only at ~/.config/ratatoskr/provider.env (mode
600); rotate via infra-ops. graphify-out/ runs dirty (auto-regen, not chased). Open issues:
#11 (AdminEvents pane — the next-reachable Worldtree-I/O coverage gap, blocked on an
admin.events.read scope request) and #10 (subject-migration watch on Worldtree #196) — both
deferred. #17 + #18 CLOSED. Codex-first pilot dormant. No in-flight implementation work — repo
is at a converged checkpoint; v1 advances when Worldtree does (v1 = full Worldtree I/O coverage).
Branch: main (tag v0.18.0, 359dbb1) — in sync with origin/main (the full #17+#18 arc is
pushed). This /snapshot commit will sit one ahead of origin until pushed (push is the operator's
call). Remote: origin → git@gitea.phasefinal.com:vh/ratatoskr.git.
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. Pinbifrost>=0.6.1in aprovideroptional-extra (gitea PyPI index, auth via~/.netrc; 0.6.0 was yanked for a circular import). Implement bifrost's OWNMemoryDataStore/affect Protocols (NOT worldtree-memory's);describe_storeis SYNC; affect is conduit-opaque. New modulesrc/ratatoskr/provider/. Authoritative how-to:~/development/bifrost/docs/implementing-a-consumer.md. (commits1a73d77pin,d90a58daffect store v0.17.1,bcdcd71serve 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 onlyagent_id+end_user_id); memory = structural index (reads vector/scope/id/origin to serve search). Conformance for both = #195 parity vs bifrost'sInMemory*Storethrough the realdispatch_*_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-reviewSTRUCTURALLY 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 (bifrostc0d0a11). -
[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-provideras 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.emit200 from10.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_HOSTSallowlist 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 onagent_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_filter400); 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, NOTWORLDTREE_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 indocs/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.BifrostBindingRequestis oneendpoint_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. Contractdocs/contracts/issues/17.contract.mdwritten +/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 memorysearchto the v0.6 scope split (operator-directed).scope_filter→scope_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 viascope_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_matchesto OR" note.) -
[2026-06-17]Worldtree spec pin bumped v0.29.0→v0.35.16 (562001a→f1b59f8); cold recall closed on the WIRE. Worldtree shipped #297 (client-side per-scope-value union recall) + #298/#299 (adopt the bifrost v0.6scope_any/scope_allwire) — emitsscope_anyon 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 restoredratatoskr: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+redefineratatoskr: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 takesrole(capability), NOTmodel:role:"character"→ first-healthy bindmistral-small-4;memory:{}trips the promotion gate (GET does NOT echomemory_config). Ourtier3.pydefine 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-turnplan_promotion_runfor consumer_defined. DELETE does NOT drain/promote (delete-is-delete, #276) — idle≥10minis 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 live —ratatoskr:terse-proberecalled 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: verbosesindra-probepromoted 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 bumpsv0.17.8→v0.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 readssession_idoff the dispatch JWTsubclaim (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}; scopesmemory: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_importdid a liveimportlib.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: neverimportlib.reloada 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+:8391advertising 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 fixedplaneparam). (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:8390store. 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-404persona_stateAND Tier-3 emits ZEROaffect_updateSSE (wire-verified) — both WT sources dead, so #18's PAD-display half is the only path.affect.fetchover 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:8392endpoint) routed to bifrost-dev to add a PUBLICbuild_combined_apprather 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-memoryfeedback-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: oneBifrostClientper session (single_endpoint_url), handshakecapabilities_grantedparsed 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; teststest_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 routeGET /affect/state/{agent_id}added viaapp.add_route(NOT an outerMount— keeps/bifrost/*top-level so the existing route test + the op-feed path-check stay valid); webGET /api/affect/{agent_id}proxy (server-suppliedend_user_id, colon-idquote()'d,RATATOSKR_AFFECT_READ_URL); pane renders the affect-emit shape honestly. Contractdocs/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/:8766vs the realaffect.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.8→v0.17.13); origin/main now ==39eebd1, tagv0.17.14. -
[2026-06-19]bifrost repinned 0.8.0→0.10.0;affect.fetchbecame MANDATORY (strong-or-absent). 0.10.0's_supports_affect_planerequiresaffect_supported+emit+fetchand gates EVERY affect op — an emit-only store 400s. Implementedaffect.fetch(v0.17.15,ca6af6b) conformed to bifrost's referenceInMemoryAffectStore.fetch({found, snapshot?}): the forced D1 prerequisite + a new Worldtree I/O point consumed. Flagged the now-stale consumer-guide line to bifrost-dev (fixeda2e6d62). -
[2026-06-19]#18 D1 SHIPPED — compositebuild_combined_appon:8392(v0.17.16,7f4ceaa); #18 CLOSED; publishedv0.18.0(359dbb1).build_combined_provider_appwraps bifrost's public builder over both stores + the shared read route; op-feedplane='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:8392allowlist. -
[2026-06-19]op-feed handshake field-name fix (#17,v0.17.17d60b77d):capabilities_requested→capabilities. The summary read a field that never exists on the wire (bifrost readscapabilities,_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-memoryfeedback-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 3–7 capability list. [auto-memoryproject-ratatoskr-v1-derived-from-worldtree-io-coverage] -
[2026-06-20]#18's final leg PROVEN — composite:8392WT-driven smoke ran end-to-end + persisted. infra-ops allowlisted10.100.10.50:8392on the personal WT (01KVHWJGTT…); a real WT turn (sessionb83a66b6,ratatoskr:sindra, fresh end_userresmoke-choco-1) dispatched the full both-plane lifecycle through ONE endpoint — handshake (both caps) →affect.fetch+memory.search→affect.emit(stored:true) →memory.upsert_many(upserted:1) — both writes verified in our SQLite (affect_snapshotsPAD row +memory_chunkschunk2df1b79…). First attempt blocked by amodel_unavailableoutage 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 fixv0.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.dbholds 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:8392provider but never exposed it in the web bind dropdown (only memory/affect single-plane). Addedcombined (:8392)as the DEFAULT option (both planes in one session), kept single-plane for isolation diagnostics; wiredendpoint_for_planecombined→8392 + server validation + the dropdown. Direct TDD; #17 contract updated (the governing spec for the web bind). Restarted:8765on 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.0per 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.3b2e4901,v0.18.4e4317f6). Worldtree #331 decoupled turn execution from the SSE connection → turn-launch failures now arrive EAGERLY as a status before any stream: 409agent_not_available(pre-b1 a 200 + in-stream error event), 503 retryable. Mapped both instream_turnto typedSseConnectFailedsubclasses 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) fullconversation-api-spec.mdmarkdown 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_availablewas in the ErrorCode enum, but NO 503/turn-launch code). worldtree-dev confirmed it was THEIR gap (#331 added the statuses without extending the #328openapi()override), shipped the fix in v1.0.0b2 / OpenAPI 2.2.0 (409/503 now enumerated, 503 code finalized asnot_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[].regardis 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-writerupdate_regardearly-returns unless an emotion isabout="other", but the Vili appraiser'sViliResponseschema has NO directedness axis (everything hardcodedabout="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]
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-06-15]"Sindra hasn't been registered" was an under-verified inference — WRONG. Concluded it from grepping ratatoskr's CODE (sindraabsent fromsrc/), 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: evenGET /agentscan't see consumer agents; onlyGET /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-reviewsees 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-reviewof 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.pysynced to v2.1 (commitd85ab43): 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 (commitcd12951, v0.17.3). Impl decisions worth keeping: vec0distance_metric=cosineat table creation (score = 1 − distance);searchover-fetches ALL candidates by cosine then scope-filters in Python sotop_kcounts 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 anexpected_revisionsparity 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 atservice.py:2623onctx.kind=="consumer_defined"ANDctx.memory_config is not None(agent DEFINED WITH amemoryblock, dim 1024) AND handshake-granted memory caps ANDembedding_dim==1024. Binding =POST /sessions BifrostBindingRequest{endpoint_url}, handshakecaps=["affect","memory"],binding.scopenull (per-op scopes auto-minted). ABIFROST_CLIENT_ALLOWED_HOSTSallowlist 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 gatewayqwen3-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. Asearch/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.pydefaultsGITEA_REPOto "Worldtree" (line 74), so a bare run in ratatoskr false-positives DRIFT by hashing Worldtree's same-numbered issue. Alwaysexport GITEA_REPO=ratatoskr GITEA_OWNER=vhbefore 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-fetchestop_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): theOpEventdataclass omitted theturn_idINV-005 promised; asession_idcomment 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, fromGET /agents+ admin token) was a FALSE NEGATIVE. Consumer-defined Tier-3 agents are OWNER-SCOPED (separateconsumer_agentstable) — invisible to the foundationalGET /agentsroster 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<10minidle" is WAD. -
[2026-06-18]Wiping our:8391store does NOT reset Worldtree's promotion-side dedup — a same-agent re-smoke returnedreason_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=0is NOT the "did the extractor run" tell —noop_duplicateis.) -
[2026-06-18]affect.emitis 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:8390store seconds later (op-feed grep right after[done]showed only the handshake; theemit stored:trueappeared 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 ZEROaffect_updateSSE (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'sloadAffectcalledsetPersonaStrip(snap), which rendersdominant_emotion || "neutral"; the affect snapshot has nodominant_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-reviewdid (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_storecreated the connection withoutcheck_same_thread=False; the bifrost emit path never tripped it (uvicorn's loop ran on the connection's creating thread), but theTestClient-driven read route runs handlers off a worker thread →sqlite3.ProgrammingError. Fix:check_same_thread=False(safe — the event loop serializes access) + explicitPRAGMA busy_timeout=5000(don't rely on sqlite3'stimeout=5.0default). Lesson: a sqlite-backed ASGI app needscheck_same_thread=False; the HTTP-layer test exposed what the direct-store-method tests structurally couldn't. -
[2026-06-19]The SAMEcheck_same_threadsqlite bug recurred in the MEMORY store — exposed by the contract-mandatedsearchdispatch test (TestClient = worker thread). Heid's test-fidelity finding (the D1 dispatch test useddescribe_storewhere the contract sayssearch) → fixing it tosearchtrippedsqlite3.ProgrammingErrorbecauseopen_memory_storealso lackedcheck_same_thread=False. Fixed (mirrors affect INV-006). Lesson: this bug is PER-STORE — every sqlite-backed ASGI store needscheck_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:8392live-smoke is infra-gated —:8392not in WT'sBIFROST_CLIENT_ALLOWED_HOSTS(bind 422s). New provider ports are NOT auto-allowlisted (only:8390/:8391are). Self-driven dispatch (minted consumer-key JWTs →:8392) is the wire-proof; the WT-turn needs infra-ops to add:8392(requested01KVHWJGTT…). -
[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 memorycheck_same_threadbug + 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 promotionupsert_manyby ~2s (it landed19: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:8081but 401sauth_invalidon 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 openteacall). 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 overtea commentwhenteais 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.
18 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md.