29 KiB
Persistent memory — ratatoskr
Last updated: 2026-06-18
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-19:
#18 DELIVERABLE 2 SHIPPED + PUSHED — the persona-telemetry gap is CLOSED. The web
pane now renders live PAD/valence for Tier-3 agents from OUR :8390 affect store
(v0.17.14, 39eebd1, suite 482 green, pushed to origin). Three pieces:
provider read route GET /affect/state/{agent_id} (non-bifrost, added to the affect app
via app.add_route — keeps /bifrost/* top-level + op-feed-skipped); web proxy
GET /api/affect/{agent_id} (server-supplied end_user_id, colon-id quote()'d,
RATATOSKR_AFFECT_READ_URL config, default 127.0.0.1:8390); pane affect-render
(renderAffectPane/loadAffect, honest pad+valence+emitted_at, labelled "affect", NO
fabricated Tier-1 fields, explicit empty-state, 2s post-turn poll). Live-smoke + a
Playwright DOM check PROVEN against real sindra/vuong PAD. The push also published the
previously-held #17 arc (v0.17.8→v0.17.13) — origin/main is now fully caught up.
#18 DELIVERABLE 1 (composite :8392 endpoint) — PARKED on bifrost (tracked Gitea #18).
Routed to bifrost-dev for a public build_combined_app rather than hand-rolled from
bifrost privates (debug-surface-uses-canonical principle). bifrost-dev confirmed it: clean
additive minor (~v0.9.0), design locked (advertise-by-presence handshake, per-route
call-time isolation), slotted AFTER WT #289. FR-1 RESOLVED — composite is bifrost-only,
ZERO Worldtree change (single-endpoint caps-routed, worldtree-dev code-verified). NEXT:
when bifrost ships build_combined_app, repin + reimplement D1 against it (per-plane
failure status + op-feed plane-per-request derivation already specced in the issue).
Nothing blocks on our side.
OPERATOR SESSION STATE — running shells are PRE-#18 code (foot-gun). web :8765 +
affect :8390 + memory :8391 are the prior session's background shells running OLD code
(no read route; web has no RATATOSKR_AFFECT_READ_URL). To see D2 live in the operator's
own session, RESTART :8390 (affect provider, new code → gains the read route) + :8765
(web, new code + RATATOSKR_AFFECT_READ_URL=http://127.0.0.1:8390 + RATATOSKR_END_USER_ID).
This session's live-smoke used THROWAWAY :8393/:8766 instances vs the same affect.db to
avoid disrupting them. Consumer/owner key = wt_live_d81b…; providers SQLite + sqlite-vec,
memory.db/affect.db at repo root (affect.db has live sindra PAD: vuong pleasure 0.146,
familiarity 0.589, interaction_count 8).
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.8.0 / wire v0.6
(scope_all+scope_any); WT handshake now advertises bifrost_version 0.6.0 (worldtree-dev
honesty-fix FYI 858ba58 — we don't pin/assert it, no-op our side). 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: #10 (subject migration), #11 (AdminEvents
pane) — deferred; #18 (D2 PAD-read SHIPPED v0.17.14; D1 composite PARKED on bifrost
build_combined_app). Codex-first pilot dormant.
Branch: main (== origin/main @ 39eebd1). 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.
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.
18 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md.