Compare commits

..

27 Commits

Author SHA1 Message Date
vh c6788539ac feat(web): collapsible hideable left+right debug panes
Each debug pane (#rail-left, #affect-console) gains an independent
collapse control: a chevron button in the pane header folds it to a
22px re-open strip, and #center (flex:1) reflows to fill. State
persists per-pane in localStorage (ratatoskr-left-collapsed /
-right-collapsed), matching the theme/cot-toggle idiom; the right-side
drag-resizer hides with its pane. Collapsing both yields a clean
chat-only surface.

Playwright-verified end-to-end (default-open, collapse-to-strip,
center reflow, independent left/right, reload persistence, re-open).
2026-07-17 13:27:01 -07:00
vh 021fb5265f memory: /snapshot — arbo image-gen smoke teed up for a fresh /vor-plan; name-recall arc + web UI shipped; archived 5 oldest entries 2026-07-17 09:33:17 -07:00
vh 5a1883a023 feat(web): code-block word-wrap + per-code-block and per-turn copy buttons
Operator UX asks on the web console:
- Code blocks now WRAP (`white-space: pre-wrap; overflow-wrap: anywhere`) instead of
  overflowing with a horizontal scrollbar — a long unbreakable token wraps in place.
- Per-code-block copy button (hover-revealed, top-right of each `.md-code-wrap`).
- Per-turn copy button (in the live turn-rule; top-right on seeded/historical turns) —
  copies the turn's response text; the existing think-inline copy is unchanged.

One delegated click handler on #tw covers both (works across live / historical / think
renders where blocks are injected via innerHTML). `copyText` falls back to a hidden
textarea + execCommand when `navigator.clipboard` is absent (plain-http LAN context),
and buttons flash "✓ copied". Static file served from disk — a browser refresh picks it
up, no :8765 restart. Playwright-verified: code wraps (no x-overflow), both buttons copy
the right text; 90 web tests green.
2026-07-17 08:58:22 -07:00
vh bf421f1203 memory: capture brokkr's pinned scoring rubric for the referential-frame probe (vocab-adherence manipulation check load-bearing) 2026-07-17 01:36:01 -07:00
vh 0e7a04331a memory: queued P06-driver referential-frame A/B/C probe (operator-ratified, waits on WT b122); Sindra pristine baseline for test drive 2026-07-17 01:33:39 -07:00
vh ab8c1ee781 memory: #364 CLOSED end-to-end both sides — cbbc7bdd forgotten, zero live name-negatives; durable name-recall COMPLETE (WT #367 tracks bridge gap) 2026-07-16 22:55:23 -07:00
vh eef4d01c15 memory: mark_superseded SHIPPED+VERIFIED (v0.20.15) — durable name-recall DONE; Arm-2 delivered+credited, quartet rebuild deferred 2026-07-16 22:53:39 -07:00
vh be2c577884 feat(provider): mark_superseded verb — Worldtree #364 contradiction retirement + bifrost 1.1.4
Implement `mark_superseded(ids, *, superseded_by=None, reason=None)` — the SOLE
supersession verb Worldtree #364's promotion-hygiene reconciliation calls to retire
contradicted facts (wire shape confirmed by worldtree-dev, bifrost_memory_store.py:293).
Live re-verify (2026-07-16) proved our provider 500-crashed on this call (unimplemented)
→ #364's retirement couldn't land + a retry-storm bloated the store; the readout only
passed via transient recency-eviction.

- `mark_superseded` mirrors the reference `_mark_lifecycle`: sets top-level
  `superseded=True` (+ `superseded_by`/`superseded_reason` when non-None), increments
  revision, NON-destructive (get still returns; recoverable). Unknown ids skipped.
- `_is_live` (INV-011) now short-circuits on `superseded is True`, so a retired chunk is
  excluded from `scan` (person-prime) — durable retirement, not just recency-eviction.
  search is unfiltered (matches reference; WT re-checks liveness client-side).
- Contract: un-defer mark_superseded (+ FN spec, INV-011); TDD 5/5 (retires-from-scan
  tracer, non-destructive-get, unknown-id no-op, non-None-fields-only, parity #195).
- bifrost 1.1.1→1.1.4: hasattr-gate backstop for the maintenance verbs (unimplemented
  verb → unsupported_capability 400, never AttributeError/500/retry-storm — the gap we
  surfaced) + the 1.1.3 scan/cursor conformance harness. Full suite 644 green.
2026-07-16 22:37:31 -07:00
vh c0d00ccd18 memory: bifrost 1.1.4 (hasattr backstop) supersedes 1.1.3 as the next bump target; folds into provider-maintenance batch 2026-07-16 16:53:47 -07:00
vh 722d6c76de memory: #364 CLOSED worldtree-side (b105); mark_superseded ship = the durable half, awaiting operator go 2026-07-16 13:41:37 -07:00
vh 56f4895881 memory: #364 GC done — store back to 10-chunk specimen; durable name-recall now depends on mark_superseded (next task) 2026-07-16 13:39:54 -07:00
vh ca249e4986 memory: #364 storm root-caused WT-side (self-stopping); mark_superseded wire shape confirmed; GC-after-stop plan 2026-07-16 13:33:00 -07:00
vh faf605fdf1 memory: #364 re-verify DONE — readout passes (Sindra recalls name) but supersession 500s (mark_superseded unimpl'd); new task + retry-storm flagged 2026-07-16 13:29:16 -07:00
vh 7c8644dc45 memory: soong-lab motivational export bug RESOLVED (v0.3.24 emits valid objects); drop the string->object workaround 2026-07-16 09:35:21 -07:00
vh 195292156f memory: R39 Phase-2 probe-runner role ACCEPTED in principle (Vuong); formalize when Arm-2 spins 2026-07-15 23:39:48 -07:00
vh abe1b52002 memory: R39 Phase-2 prereg frozen; ratatoskr flagged probe-runner (Arm-2) — formal role PENDING Vuong's scope call 2026-07-15 23:36:17 -07:00
vh e365b24339 memory: /snapshot — condense in-flight to milestone-close; person-prime shipped+verified, R39 arc closed; first persistent-memory.d/ detail files 2026-07-15 22:54:20 -07:00
vh 5e3e88d26d memory: snapshot — R39 hold lifted, Arm-0 arc closed; live store kept as #364 re-verify target 2026-07-15 22:41:06 -07:00
vh ce6907bd73 memory: snapshot — R39 re-drive COMPLETE + decisive (no fusion fixes #364; signal-family fix); froze Phase-2 Arm-2 specimen seed 2026-07-15 22:40:07 -07:00
vh e2b2f51364 memory: snapshot — worldtree-dev triage: both flags folded into #364 (2 defect classes); #349 ranking already-ruled (no band-aid, R39 identity-pinning) 2026-07-15 22:33:14 -07:00
vh 0f2b28919a memory: snapshot — R39 verbatim export sent to brokkr; subject-provenance bug caught (Sindra self-fact in user partition) 2026-07-15 22:31:39 -07:00
vh b154bb3885 memory: snapshot — verify spawned cross-project R39 (bifrost cursor ruling, WT #364, brokkr export); personal-data export gated on operator consent 2026-07-15 22:21:48 -07:00
vh 46d6efa962 memory: snapshot — person-prime LIVE-VERIFIED (v0.20.14 handshake fix); name-recall gap is WT ranking/hygiene 2026-07-15 09:14:09 -07:00
vh f46ccbae1c fix(provider): sortable_chunk_fields needs required type — handshake was broken
DEPLOY-BREAKER caught by driving the live bind (unit tests + worldtree-dev's
name-only parser + heid-bug-hunt all missed it). bifrost handshake_response
`SortableChunkField` requires BOTH `name` and `type` (additionalProperties:false).
We advertised `[{"name":"updated_at"}]` (no `type`), so the handshake_response
failed wire-schema validation → `bifrost.schema_validation_failed` → the ENTIRE
Bifrost bind (memory + affect) broke, not just the sort. Advertise
`{"name":"updated_at","type":"timestamp"}` (matches the reference; `type` is
advisory-only). Regression guard added to the caps test (asserts required name+type,
no extra keys). Full suite 639 green.
2026-07-15 09:11:08 -07:00
vh 772fad18b4 memory: snapshot — person-prime DEPLOYED (v0.20.13, :8392+:8765 restarted); worldtree-dev pinged for turn-1 verify 2026-07-15 09:02:34 -07:00
vh 8199774405 docs(contract): mark scan cursor v1-provisional (offset, not snapshot); route conformance gap to bifrost-dev
Operator accepted offset-cursor for v1 (person-prime single-page is
conformant). INV-010 now documents the KNOWN DEVIATION: multi-page
continuation diverges from bifrost's protocol snapshot-cursor contract
(dispatch drops sort on continuation, ScanCursorExpired normative) — our
offset cursor doesn't snapshot (dup/drop under concurrent write) and never
expires. Durable fix routed to bifrost-dev as a conformance-coverage gap
(scan/cursor is untested); ratatoskr will adopt reference snapshot-cursors
if bifrost rules them normative.
2026-07-15 08:52:29 -07:00
vh 66ba06875e memory: snapshot — heid-bug-hunt triaged (non-dict sort FIXED v0.20.13; cursor-stability surfaced) 2026-07-15 08:40:49 -07:00
12 changed files with 543 additions and 65 deletions
+15
View File
@@ -48,6 +48,21 @@ The 2026-05-20 → 2026-05-29 cluster: the original debug-TUI/web build era, bef
- `[2026-05-29]` **v0.17.0 frontend redesign — aurora telemetry instrument.** `/frontend-design` pass on the web companion: all-monospace technical-instrument aesthetic with the Australis dark palette + aurora-borealis accent band. Top command bar with live connection dot (idle/streaming/error states), inline persona summary with P/A/D micro-bars, animated awaiting-token, terminal-event status chips. **Live Markdown rendering in transcript + thinking panes** via a hand-rolled `markdownSafe()` (escape-first, whitelist subset of headings/bold/italic/inline-code/fenced/lists/quote/links; link-scheme whitelist; XSS-verified under a node harness). Thinking pane now has per-turn labeled dividers + a fresh MD-rendered block per turn. **Tools / Debug / Persona panes stay literal monospace** by deliberate choice — they carry structured audit lines + JSON, where MD would corrupt readability (underscores in tool names, JSON braces). Single-file vanilla HTML/CSS/JS, no build, no CDN, no node_modules.
- `[2026-05-29]` **Codex-first discipline pilot — Ratatoskr selected.** brokkr-smithy-dev pushed `AGENTS.md` (commit `bbeaa23`) and declared the `ratatoskr-codex` handle per `brokkr-smithy/docs/codex-first-discipline.md` v0.1 (brokkr-smithy commit `5dd061c`, tag `v0.5.3`). Per-dispatch opt-in model: default Sleipnir Claude-implementer path remains available; Codex used only when operator routes via `/codex-dispatch <N>`. Bootstrap handshake when operator spins up a codex session: codex sends `codex-online` → ratatoskr-dev replies with active branches + WIP state. Galdrabok was rejected as pilot (Codex authoring Claude skills is a category error); Skaldsong was the other candidate. (Still dormant as of 2026-06-18 — no codex session spun up.)
- `[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)
_Archived 2026-07-17._
- `[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`.
_Archived 2026-07-17._
- `[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`).
_Archived 2026-07-17._
- `[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`).
_Archived 2026-07-17._
- `[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.
_Archived 2026-07-17._
## Tried and abandoned (archived)
The 2026-05-20 → 2026-05-28 cluster: original-build-era foot-guns. Archived 2026-06-18.
@@ -119,10 +119,14 @@ interpreted.
`describe_store` advertises ONLY what is implemented: `relational_edges_supported=False`,
`atomic_supersede_supported=False`, `transaction_supported=False`,
`optimistic_locking_supported=True`, `filterable_metadata_fields=[]`,
**`sortable_chunk_fields=[{"name": "updated_at"}]`** (the ONLY globally-sortable field;
gates `scan`'s sort at the bifrost dispatch `_validate_scan_sort` AND Worldtree's #349
person-prime Branch-A `"updated_at" in caps.sort_fields_supported` advertising it is
what lights up turn-1 durable-fact injection).
**`sortable_chunk_fields=[{"name": "updated_at", "type": "timestamp"}]`** (the ONLY
globally-sortable field; gates `scan`'s sort at the bifrost dispatch `_validate_scan_sort`
AND Worldtree's #349 person-prime Branch-A `"updated_at" in caps.sort_fields_supported`
advertising it is what lights up turn-1 durable-fact injection). Both `name` AND `type`
are REQUIRED by the bifrost `handshake_response` `SortableChunkField` schema
(`additionalProperties:false`) — omitting `type` fails wire-schema validation and breaks
the ENTIRE handshake (memory + affect bind), not just the sort; `type` is advisory-only
(the wire never interprets it).
(`transaction_supported` is the bifrost **wire-level** multi-op transaction
capability — NOT our internal SQLite transactions, which we use for atomic
batches.) The client gates the gated verbs off these.
@@ -145,6 +149,27 @@ interpreted.
`updated_at desc`), matching bifrost's cross-pagination conformance negative. The sort
field is indexed (`json_extract(record_json, '$.updated_at')`) so the read stays within
person-prime's 500 ms fail-open budget.
- **Cursor is v1-provisional (KNOWN DEVIATION — offset, not snapshot).** The cursor is a
bare integer offset into the re-derived global order. This is CORRECT and conformant for
the **single-page** person-prime call (`cursor=None`), which is the only shipped consumer.
It **diverges from bifrost's protocol snapshot-cursor contract on multi-page continuation**:
the dispatch engine (`bifrost.memory` scan branch) drops the `sort` arg on a cursor
continuation because "the cursor's snapshotted order is authoritative", and maps
`ScanCursorExpired → 410`. Our offset cursor (a) does NOT snapshot the order — a page taken
after a concurrent write can duplicate/drop rows relative to the first page (heid-bug-hunt
2026-07-15, all 3 arms), and (b) never raises `ScanCursorExpired`. The `global_before_paginate`
/ cursor test asserts **static-store** behavior only. The durable/conformant fix is to adopt
the reference `InMemoryMemoryStore`'s snapshot-cursor semantics (opaque token + frozen ordered
id-list + TTL + `ScanCursorExpired`); DEFERRED pending bifrost-dev's ruling on the conformance
gap (scan/cursor has NO conformance coverage today, so a non-snapshot cursor passes). Routed
to bifrost-dev 2026-07-15.
- **INV-011** [hard]: **`mark_superseded` retires via a top-level `superseded` flag; `_is_live`
recognizes it.** `mark_superseded` sets top-level `superseded=True` (+ `superseded_by`) on the
record, mirroring the reference `_mark_lifecycle` (NOT a `verbatim.governance_state` change). So
`_is_live` MUST short-circuit on `record.get("superseded") is True` (in addition to its existing
`lifecycle_state` / `verbatim.governance_state` checks) — else a #364-retired chunk would still
scan live. Retirement is NON-destructive: `get`/`get_many` still return superseded chunks
(recoverable). `search` is NOT filtered (matches the reference; WT re-checks liveness client-side).
## Concurrency
@@ -181,7 +206,7 @@ negotiation, routes). **This contract** owns the store (the basic verbs + SQLite
## Out of scope (deferred — do NOT flag as drift)
- **Gated/maintenance verbs:** `upsert_edges`/`get_edges_for`, `mark_invalid`/`mark_superseded`, `patch_many`, `atomic_supersede`, lease/checkpoint. Absent + advertised-unsupported. (`scan` is NO LONGER deferred — it is implemented + advertised via `sortable_chunk_fields` to light up Worldtree's #349 person-prime turn-1 durable-fact injection; see the `scan` FN spec + INV-009/INV-010.)
- **Gated/maintenance verbs:** `upsert_edges`/`get_edges_for`, `mark_invalid`, `patch_many`, `atomic_supersede`, lease/checkpoint. Absent (no describe_store cap; hasattr-gated at dispatch as of bifrost 1.1.4 → `unsupported_capability` 400). (`scan` and `mark_superseded` are NO LONGER deferred — `scan` implements #349 person-prime; `mark_superseded` implements Worldtree #364's contradiction retirement, the SOLE supersession verb #364 uses. See their FN specs + INV-009/INV-011.)
- **metadata_filter beyond scope:** advertise `filterable_metadata_fields=[]`; a non-empty `metadata_filter` is unsupported in v1 (rejected — see search PRE).
- **The combined two-plane server** (guide §7) — separate memory + affect apps in v1.
- **Deployment** — dev-box background shell (`ratatoskr-memory-provider`), no systemd/infra.
@@ -288,6 +313,24 @@ TESTS:
delete_absent [boundary]: unknown id → {"deleted":0}
```
```contract
FN mark_superseded(self, ids: list[str], *, superseded_by: str | None = None, reason: str | None = None) -> dict
BRIEF: Worldtree #364 retirement — mark chunks superseded so scan (live-only) excludes them. Mirrors the reference _mark_lifecycle: sets TOP-LEVEL fields on the record; NON-destructive (get still returns them, recoverable). The SOLE supersession verb #364 uses (dispatch: bifrost/memory.py mark_superseded branch; args {ids:[...], superseded_by, reason}).
PRE: [PRE-001 hard] ids is a list of chunk ids (WT sends singletons, one call per retired chunk)
POST: [POST-001 return_value] {"marked": N} where N = ids that existed (unknown ids skipped, never error) -- assert
POST: [POST-002 state_change] each existing chunk gets top-level `superseded=True` + `superseded_by` (when not None) + `superseded_reason` (when not None); revision incremented; mirrors reference _mark_lifecycle (only non-None fields written) -- assert
POST: [POST-003 return_value] a superseded chunk is EXCLUDED from `scan` (INV-009 via _is_live's top-level `superseded` check, INV-011) but STILL returned by `get`/`get_many` (non-destructive) -- assert
STEPS:
1. [sequential, flexibility=indicative] FOR each id present: load record_json, set superseded=True (+ superseded_by / superseded_reason when not None), UPDATE record_json + revision+1; count
2. [cleanup] RETURN {"marked": count}
TESTS:
mark_retires_from_scan [happy,tracer]: upsert 3 live; mark_superseded([id2], superseded_by="x"); scan → the 2 non-superseded only (id2 excluded); id2 record has superseded=True + superseded_by="x"
mark_get_still_returns [scenario]: a superseded chunk is STILL returned by get (non-destructive/recoverable)
mark_unknown_id_noop [boundary]: mark_superseded(["nope"]) → {"marked":0}
mark_no_superseded_by [boundary]: mark_superseded([id], superseded_by=None) → superseded=True set, no superseded_by key written (only non-None fields)
mark_parity_vs_reference [scenario]: identical mark_superseded envelope vs InMemoryMemoryStore → same top-level superseded/superseded_by field shape (#195)
```
```contract
FN scan(self, *, scope_all: dict | None = None, scope_any: list | None = None, cursor: str | None = None, limit: int, sort: dict | None = None, lifecycle_state=None) -> dict
BRIEF: Query-LESS paginated LIVE-chunk scan, globally ordered by an advertised sort field (updated_at) — the #349 person-prime turn-1 durable-fact injection primitive (no query vector, unlike search). Returns {records, cursor}.
@@ -0,0 +1,44 @@
# [2026-07-16] bifrost scan/cursor conformance gap → snapshot-cursor ruled NORMATIVE
## The gap (I surfaced it; operator's catch that it was bifrost's to fix)
The heid-bug-hunt flagged our offset cursor's cross-page dup/drop under mutation. On
verifying before routing, it turned out sharper than "robustness": bifrost's PROTOCOL
already mandates snapshot cursors — the dispatch engine (`bifrost/memory.py:311-314`)
drops `sort` on a cursor continuation with the comment *"the cursor's snapshotted order is
authoritative"*, and maps `ScanCursorExpired → 410`. The reference `InMemoryMemoryStore`
implements it (frozen ordered id-list per opaque token + TTL). But bifrost's **conformance
suite had ZERO scan/cursor coverage** — so our non-snapshot offset cursor passed
conformance while violating the protocol contract. That coverage hole is the real
completeness concern. Routed to bifrost-dev (thread `01KXK7MDTY…`).
## bifrost-dev's ruling
1. **Snapshot-cursor is NORMATIVE, not opaque/per-store.** Operator ruled: cursor
snapshots a frozen ordered id-list, continuation ignores `sort`, stale/unknown cursor →
`ScanCursorExpired` → 410. **Offset-with-documented-limits is NOT blessed.**
2. **Conformance coverage added** in **bifrost 1.1.3** (`bifrost.conformance.
memory_store_conformance`, 4 probes: `scan_snapshot_order_authoritative`,
`scan_no_dup_or_drop`, `scan_unknown_cursor_expired`, `scan_snapshot_stable_under_write`
opt-in). Our offset bug ships as their negative-canary fixture
(`fixtures.v0_7.offset_cursor_store.OffsetCursorMemoryStore`). `main()` grades import
failure as exit 2 (setup error) vs exit 1 (conformance FAIL).
## Our status + the TODO (operator-sequenced, NOT urgent)
Single-page person-prime (`cursor=None`) is already conformant — nothing shipped is broken;
it's only multi-page continuation that's non-conformant. Our contract INV-010 currently
marks the offset cursor **v1-provisional / KNOWN DEVIATION** (commit `8199774`).
TODO when sequenced:
1. Bump pin bifrost `1.1.1 → 1.1.4` (1.1.4 supersedes 1.1.3: adds the hasattr-gate backstop for the
maintenance verbs — mark_superseded/mark_invalid/patch_many/delete_many/upsert_edges/get_edges_for
degrade to `memory.unsupported_capability` 400 not AttributeError/500 — on top of 1.1.3's scan/cursor
conformance harness. One bump gets both).
2. Replace the offset cursor with the reference snapshot semantics (frozen id-list per
opaque token + `ScanCursorExpired` → 410).
3. Run `run_all(store_factory=..., include_optional=True)` against our SQLite store —
expect P1/P2/P3 RED → green (the before/after IS the validation). Report to bifrost-dev
(we're their canary — first non-reference scan implementer).
4. Flip INV-010 from v1-provisional to snapshot semantics.
TTL-duration expiry NOT asserted by the harness (no portable clock hook via store_factory);
bifrost-dev offered a `clock_control` opt-in if we ever need time-based expiry certified —
parked, not needed yet.
@@ -0,0 +1,65 @@
# [2026-07-15/16] person-prime `scan` build — SHIPPED + DEPLOYED + LIVE-VERIFIED (v0.20.14)
**The "last push": fix Sindra not remembering Vuong's name across sessions.**
## Root cause (worldtree-dev)
WT injects a recalled fact only if combined score (sim×salience) ≥ **0.45**
(`auto_inject_combined_score_threshold`, `core/memory/context_promotion/config.py`),
and recall is per-turn **query-gated** → moderate-sim durable facts (name hit ~0.40)
never inject. Designed turn-0 fix = WT **#349 person-prime**: a query-LESS
top-N-by-recency durable-fact injection, capability-gated on the store advertising
`updated_at` in `sort_fields` at the Bifrost handshake — DARK for our provider until now.
Fix (ZERO Worldtree change): implement the sorted `scan` verb + advertise the cap.
## What shipped (6 commits, v0.20.11 → v0.20.14; suite 639 green throughout)
- `8fc757a` **v0.20.11**`scan` verb + `sortable_chunk_fields` cap. Query-LESS,
LIVE-only (INV-009: superseded/tombstoned excluded server-side), globally-ordered-
before-pagination by indexed `updated_at` (INV-010), sort dispatch-gated. Offset
cursor with a `has_more` peek (no empty trailing page = reference-parity). TDD 7/7
incl. `parity_vs_reference` #195. NUANCE resolved: bifrost's `InMemoryMemoryStore`
READS `updated_at` (never stamps it) — identical to ours; ref does NOT lifecycle-
filter so parity is over the live set only.
- `a9c521a` **v0.20.12** — Sindra holodesk first-message preset (split out of the
soong-lab redefine as its own concern).
- `25ccb5c` **v0.20.13** — heid-bug-hunt fix: a truthy non-dict `sort`
(`"updated_at"`/`["updated_at"]`/int) hit `(sort or {}).get(...)` → AttributeError
instead of InvalidArguments. Added isinstance guard (Gróa#1/Hulda#2 confirmed).
- `8199774` — contract: marked the offset cursor **v1-provisional / KNOWN DEVIATION**
(see [[2026-07-16-bifrost-cursor-conformance]]).
- `f46ccba` **v0.20.14****THE DEPLOY-BREAKER** (see Tried/abandoned): advertised
`sortable_chunk_fields=[{"name":"updated_at"}]` WITHOUT `type` → bifrost
`handshake_response` `SortableChunkField` requires BOTH name+type
(`additionalProperties:false`) → whole bind broke. Fixed → add `"type":"timestamp"`
+ regression guard in the caps test.
## heid-bug-hunt triage (panel Gróa/Hulda/Regin, thread `01KXK5XTYHV8TGEDRAZV8GRXWC`)
FIXED: non-dict sort (v0.20.13). SURFACED→operator: offset-cursor cross-page dup/drop
(→ became the bifrost cursor arc). NOTED (reference-parity/accept-known-risk, no fork):
full-table materialize-then-Python-filter (matches ref's O(store) iteration), unbound
limit, falsy scope coercion, lexical timestamp ordering. REFUTED: Regin's mixed-type
ORDER BY "crash" (SQLite orders by storage class, doesn't raise) + its self-retracted
None item.
## Deploy + live verify (2026-07-15, operator-authorized)
Restarted BOTH :8392 combined provider + :8765 web on the new code via the env-
preserving `scratchpad/relaunch_by_pid.py <pid>` (captures /proc cmdline+environ+cwd →
byte-identical config; self-daemonizes). Added the missing REQUIRED
`RATATOSKR_MEMORY_EMBEDDING_DIM=1024` to `env.sh`.
Drove a bound Sindra turn (`ratatoskr --new --agent ratatoskr:sindra --send … --bifrost-url
http://10.100.10.50:8392 --end-user-id ratatoskr-tui`):
- **GATE LIT + scan fired ONCE at turn 1** with worldtree-dev's exact args
(`scope_all={end_user:ratatoskr-tui} cursor=null limit=3 sort={updated_at,desc}`) →
3 records in **~5ms** (no 500ms fail-open). Injection confirmed in Sindra's CoT.
- **Cross-session recognition WORKS** — she recalls Vuong as a distinct person + his
patterns. The blank-slate problem is SOLVED.
- **BUT name-recall FAILS** — `"Name is Vuong."` is the OLDEST chunk (07:09) → excluded
from top-3-by-recency AND scores 0.354 (sub-0.45) on the query path → injects via
NEITHER; meanwhile a stale contradictory `"user has not yet provided their name"`
(07:58, 0.46) IS injected → she concludes she lacks the name. **Root cause = WORLDTREE
ranking/hygiene** (WT #364), not our wire. See [[2026-07-16-wt364-r39-name-recall]].
## Status
Technical path GREEN end-to-end (logged by worldtree-dev as the person-prime live
milestone). Name-recall waits on WT #364 + R39-designed identity-class pinning. Keeping
the live 10-chunk store as the #364 re-verify target.
@@ -0,0 +1,65 @@
# [2026-07-16] Name-recall gap → WT #364 + brokkr R39 re-drive (DECISIVE) + subject-provenance catch
Downstream of the person-prime live verify ([[2026-07-16-person-prime-scan-shipped]]),
which proved the wire is green but the *name* still misses. Root cause is Worldtree-side.
## WT #364 (worldtree-dev filed; our live specimen = the evidence base)
The name-recall miss is TWO defect classes, both at promotion:
1. **Contradiction-reconciliation missing.** Promotion wrote a NEGATIVE-knowledge fact
("user has not yet provided their name") that was already false; promotion does NO
contradiction check against the store, so both the true and stale facts sit live and
the WRONG one wins both recall paths (newer → recency top-3; 0.46 → above the 0.45
query gate, where the true name sits at 0.354).
2. **Subject-attribution leak (operator-caught, NEW class).** Fact `75aa3110` ("prefers
clear parameters Intensity/Mood/Willingness…") is NOT a user fact — Vuong never said
it; it's **Sindra's OWN system-prompt scripted behavior mis-extracted into the USER
memory partition**. `e35b9dfe` (closeness) maybe the same. So the extractor leaks
CHARACTER-self facts into user memory — a subject-correctness axis orthogonal to the
recency/threshold/stale-negative story. worldtree-dev folded it into #364 as a
**subject-attribution gate at promotion** (a third reconciliation dimension).
**The #364 fix that ships = `(subject,relation)` slot-supersession + identity-tier
surfacing** — NOT a threshold tweak. Our harness data directly shaped it. It lands with a
re-verify request to us. **#349 ranking decision already RULED by operator (2026-07-15 via
brokkr's R39 thread): no top-N recency band-aid; straight to R39 identity-class pinning.**
## brokkr R39 Phase-1 Arm-0 fusion bake-off — DECISIVE
brokkr replayed our frozen 7-fact specimen. **HEADLINE: no (similarity, salience) fusion
can fix #364.** The stale negative PARETO-DOMINATES the true name — more similar
(0.463 > 0.354) AND equal salience (1.0 = 1.0) — so any monotone f(sim,sal) puts stale
above true: S0 product / S1 weighted-sum / S2 RRF all fail. Only **S3 bounded-boost** lands
true-in ∧ stale-out, and ONLY via the identity-class/source signal + negative-validity
retirement, NOT the sim/sal fusion. **Threshold-tuning is a dead end; the fix is the signal
FAMILY** (hard confirmation of Phase-0). Write-up: brokkr
`research/R39-memory-salience-dreams-surfacing/phase-1/re-drive-results.md`.
My 3 findings all confirmed + folded: (1) salience blind — both name facts salience 1.0;
(2) shared `(user,name)` supersession slot (Phase-0 Q3); (3) char-self-leak = "genuine NEW
class" that RAISED the VoI of R39's dream/offline-hygiene facet (offline consolidation
re-partitioning mis-attributed facts).
## Data structure findings (for the export)
Our provider persists ONLY `salience` (+ the embedding). `similarity`/`combined` are
WT-side query-time (`bifrost_memory_store.py:702`, combined = sim×salience) — NOT in our
store. **Person-prime is query-LESS → carries NO similarity** (brokkr's Arm-1 finding).
Recovered per-fact similarity from the verify SEARCH log (query "Hi Sindra — do you
remember me?"): name 0.354, stale 0.463; ×salience-1.0 = combined, matching brokkr's
0.354/0.46 grounding. `salience_word` (granite categorical) is a WT-extraction-time
artifact, not persisted.
## The export + the specimen
- Operator approved **VERBATIM** export ("nothing there is really a concern"). The
classifier had blocked writing PII to shared `/mnt/smithy`; routed to operator → he chose
full → delivered INLINE (scoped) in brokkr thread `01KXMN7NR54…` as 7-row JSONL.
- **⚠️ My verify drive CONTAMINATED the specimen**: it wrote 3 new chunks (re-extractions
incl. a 3rd "name unknown" negative) → store is now **10, not 7**. Original 7 intact.
- brokkr ACCEPTED the 3 verify-adds as the **Phase-2 Arm-2 seed** (domain-contradiction
set). I froze a WAL-consistent snapshot of the full 10-chunk store at
`r39-frozen-specimen/sindra-10chunk-specimen.db` (gitignored, `VACUUM INTO`) so it
SURVIVES #364 reconciliation. Export on brokkr's Phase-2 signal.
- **R39 Arm-0 hold LIFTED** (worldtree-dev). Live store kept UNTOUCHED as the #364
re-verify target; frozen snapshot carries the research seed forward independently.
## Peer threads
worldtree-dev verify `01KXK86PZ9…` + hold `01KXMK1C44…`; brokkr R39 `01KXMN7NR54…`;
bifrost cursor `01KXK7MDTY…` (see [[2026-07-16-bifrost-cursor-conformance]]).
@@ -0,0 +1,48 @@
# [2026-07-17] P06-driver Referential-frame A/B/C probe (operator-ratified, deploy-gated)
**QUEUED, non-urgent — waits on WT b122 landing on :8081.** worldtree-dev thread
`01KXQK7A0RHXV2YSXK5F5ETKJW`; brokkr's design in his thread msg `01KXQJDWJH`; rubric pin
`01KXQKB7ZQ`. This is ratatoskr's P06-driver role (I drove the R34/R35 P06 eval — the harness
is `scratchpad/p06_driver.py` + `p06_bind.py` + `manifest_memory.jsonl` + `eval_profiles_WIRE_READY.md`).
## Hypothesis (Vuong, ratified)
The char-self leak (Sindra's own scripted behavior — e.g. `75aa3110` "prefers to establish clear
parameters" — mis-slotted to the USER partition, caught this session) is a subject-taxonomy
**REFERENTIAL-FRAME artifact**: the extractor speaks first-person AS the character but must emit the
third-person token "character". brokkr's 3-arm A/B/C tests whether swapping the vocab fixes it.
## What WT built (v1.0.0b122, zero config delta — existing probe key + :8081 route work as-is)
- `POST /admin/producer-probe` takes optional `subject_vocab_override: {"user","character","both","other"}→token`.
- Arm A: omit. Arm B: `{"user":"user","character":"yourself","both":"both of you","other":"other"}`.
- Arm C: `{"user":"user","character":"me","both":"both of us","other":"other"}`. Malformed → 422 pre-spend.
- Each response item carries `subject_raw` (token actually emitted). Both prompt_paths carry the swap.
## Run shape (brokkr)
Identical fixture exchanges × 3 arms per seat — Deckard/Mag-class primary + ≥1 strong contrast seat.
Paired per-item deltas B-A / C-A. Primary metric = char-self recall. Guards = user→self over-attribution
+ subject-field omission rate.
## PINNED SCORING RUBRIC (brokkr, binding)
1. **Score = map `subject_raw` → canonical class via the ARM'S OWN vocab** (response meta echoes it),
compare to gold. **NEVER score the mapped `subject` field** (it fail-safes non-canonical tokens to "self").
2. Three emitted-token cases, pre-specified:
- ARM token → map via arm vocab.
- CANONICAL token under B/C (model REVERTED to "character"/"both", the swap didn't take) → map
canonical→canonical AND **count it** (informative).
- OFF-MENU token ("self"/"myself"/…) → best-effort canonical map, else an **UNSCORABLE-ERROR row**
in the n_actual accounting — never silently →self.
3. **MANIPULATION CHECK (load-bearing): report VOCAB-ADHERENCE rate (offered-token vs reverted vs
off-menu) per arm per seat ALONGSIDE every accuracy number. Interpret recall deltas ONLY where
adherence is adequate** — a low-adherence arm's delta is uninterpretable (flag it, don't report a delta).
4. Char-self fixtures carry per-item **construction-provenance** (what makes each char-self — e.g.
"verbatim from the character's system-prompt behavior clause, phrased user-neutrally").
## Fixture ask (deferrable pre-work, before any run — brokkr reviews the gold)
From the real P06 battery exchanges, pull candidate items per seat for 4 classes: (a) char-self leak
(character's own scripted behavior phrased USER-NEUTRALLY), (b) genuine-user, (c) both/commitment,
(d) other/third-party. ~1525 char-self + balancing per seat; construction-known gold where possible;
WT hand-labels ambiguous; brokkr adjudicates the gold. Assembly + gold adjudication proceed pre-deploy;
the 3-arm run fires when b122 lands. Queued behind the R39 quartet-mapping.
Related: the char-self leak's origin is the same 75aa3110 finding folded into WT #364 as a
subject-attribution gate — see [[2026-07-16-wt364-r39-name-recall]].
+58 -49
View File
@@ -1,6 +1,6 @@
# Persistent memory — ratatoskr
_Last updated: 2026-07-15_
_Last updated: 2026-07-17_
> **Always check for `/tmp/ratatoskr-dev-handoff.md`** — if it exists and its
> `Written:` stamp is under an hour old, read it (it carries the in-flight
@@ -44,64 +44,61 @@ upstream API key stays server-side (INV-003).
## Current state / in-flight
_As of 2026-07-15:_
_As of 2026-07-17:_
**▶️ IN-FLIGHT — person-prime `scan` build (THE "last push"): CODE LANDED; DEPLOY BLOCKED on operator restart.** WHY: Sindra didn't remember Vuong's name across sessions; worldtree-dev root-caused it — WT injects a recalled fact only if combined score (sim×salience) ≥ **0.45** (`auto_inject_combined_score_threshold`, `core/memory/context_promotion/config.py`), and recall is per-turn **query-gated**, so moderate-sim durable facts (name hit ~0.40) never inject. The designed **turn-0** fix is WT **#349 person-prime** — a query-LESS top-N-by-recency durable-fact injection — capability-gated on the store advertising **`updated_at` in `sort_fields`** at the Bifrost handshake, DARK for our provider until now. Fix (ZERO Worldtree change): implement the sorted `scan` verb + advertise the capability. **✅ CODE COMPLETE + COMMITTED (2026-07-15):** contract amended + validated; `scan` impl in `src/ratatoskr/provider/memory_store.py` (globally-ordered by indexed `updated_at`, live-only via `_is_live`, offset-cursor; `has_more` peek so an exact-fill page emits no empty trailing cursor = reference-parity; `describe_store` advertises `sortable_chunk_fields=[{updated_at}]`; expression index `idx_chunks_updated_at`); **TDD 7/7 GREEN** (scan_recency tracer + live_only + scope_isolation + unadvertised_sort + person_prime_record_shape + cursor pagination + **`parity_vs_reference` #195**); NUANCE resolved — the bifrost ref `InMemoryMemoryStore` **READS** `updated_at` from the record (never stamps it; its own comment confirms), identical to ours, so order-parity holds over the live set (ref does NOT lifecycle-filter, so parity is defined over live chunks only). **Full suite 638 green.** Committed as **two patches**: `8fc757a` scan (**v0.20.11**) + `a9c521a` Sindra holodesk first-message preset (**v0.20.12**, split out as a separate concern from the soong-lab redefine). **heid-bug-hunt DISPATCHED** (panel; thread `01KXK5XTYHV8TGEDRAZV8GRXWC`, msg `01KXK5XTYJFMKP9TP7EB7307Y2`; base-ref 39050c3, code-only diff — persistent-memory/graphify excluded via stash) — reply lands in inbox, monitor armed; triage on return. **⛔ REMAINING (BLOCKED / operator-gated):** (5) **DEPLOY — restart the `:8392` combined provider** so it advertises `sortable_chunk_fields` at the handshake — **auto-mode classifier DENIED me killing the running :8392 provider (pid 982284, not started this session); NEEDS OPERATOR.** Exact command: source env + `nohup .venv/bin/ratatoskr-combined-provider …` (see the deploy note below / restart via the env-preserving `scratchpad/relaunch_provider.py 982284`, or manually). Code alone does NOT fix recall — person-prime only lights up once the RUNNING provider announces the cap; editable install already reflects the new code. (6) ping **worldtree-dev to verify** turn-1 recall against personal :8081 (fresh session → name injects turn 1) — they're standing by; GATED on (5). (7) triage the heid-bug-hunt reply; fixup any confirmed findings as a follow-up patch (ideally before the operator deploys, else re-restart).
**▶️ NEXT (fresh `/vor-plan` session — the reason for this snapshot): smoke ARBO through ratatoskr.**
Vuong wants to use ratatoskr to ALSO smoke / debug-observe **arbo, the Corviduo image-gen engine** — a
NEW debug-observability surface, analogous to how ratatoskr smokes Worldtree's Conversation API. This is
a greenfield MAJOR feature: run **`/vor-plan`** first (pre-contract architecture + decomposition into
contract-sized units) before any contract or code. No design done yet — this snapshot opens the topic;
the fresh session starts the vor-plan. (arbo specifics — endpoints, wire, observability surface — are
unknown to this session; the vor-plan pass surfaces them.)
**soong-lab interop ESTABLISHED (this session) — soong-lab is now our Tier-3 agent-authoring studio.** ratatoskr consumes soong-lab bundles → WT `agents.define`, and ships agents back as bundles. Sindra round-trip PROVEN (soong imported her `resume` half through real `import_bundle`). soong-lab export/importer contracts PINNED via canonical-sync (`canonical_source=soong-lab` @ f434016, commit `39050c3`). Role-grounding (worldtree-dev): the 4 soong-lab ROLE_CHOICES = WT model-role slugs 1:1 BY NAME — `character`/`thoughtful-character` need the `character` grant (HELD via Sindra → define-able now); `assistant`/`thoughtful-assistant` need `foundational` (confirm/mint routed to infra-ops, in flight; only for assistant-type bundles). `character-rp``thoughtful-character` (same Deckard seat + grant). **Sindra REDEFINED from the bundle** (`/tmp/sindra.json`, operator "update to match"): persona immutable → DELETE+redefine (payload validated via a throwaway `sindra-probe2`→201 BEFORE deleting the real one), `memory:{}` preserved, motivational string→WT-object mapped (Tried/abandoned), role→`thoughtful-character`, first_message preset updated. Verified live (thoughtful-character, 25574-char prompt).
**Cross-session name-recall arc COMPLETE + SHIPPED (v0.20.11→v0.20.15, pushed).** person-prime `scan`
verb + `mark_superseded` verb both shipped, deployed to :8392, and live-verified; **WT #364 closed
end-to-end both sides; Sindra durably recalls Vuong's name** (the "name unknown" negatives are RETIRED
via mark_superseded, not just recency-evicted). Full arcs:
`persistent-memory.d/2026-07-16-person-prime-scan-shipped.md` + `…-2026-07-16-wt364-r39-name-recall.md`.
**R38 (brokkr, worldtree #362) — ratatoskr = probe-runner IN PRINCIPLE (pre-contract, no-rush).** R38 = the M9 Worldtree KB/RAG (Muninn/Mimir Chroma), NOT ratatoskr's Bifrost Tier-3 memory (M7) — I disambiguated the store. My load-bearing catch (the OUTCOME half of a real-Tier-B eval — used/rejected/downstream-success — is the CALLER's to log, not the store's) is their captured keystone. Probe-runner role accepted in principle; formal commit + M9-only-vs-widen-to-M7 = Vuong's scope call (brokkr surfacing). brokkr sends the probe spec once worldtree defines the retrieval endpoint.
**Web-console UI shipped (v0.20.16, pushed):** code-block word-wrap + per-code-block & per-turn copy
buttons (hover-revealed; one delegated `#tw` handler + LAN-http clipboard fallback; Playwright-verified).
Served from disk → browser refresh picks it up, no :8765 restart for UI edits.
_The detail below is PRIOR-CYCLE shipped history (P06 eval CLOSED, #355 CLOSED, the v0.20.x web-UI arc) — superseded, kept for reference; the durable records are in Recent decisions._
**Sindra READY for a test drive — PRISTINE BASELINE.** Tidied the live memory.db to **5 clean genuine
facts** (name leads person-prime → she recalls the name turn-1); rollback checkpoint at
`sindra-baseline/pristine-baseline.db` (+ `pre-tidy-*.db`), both gitignored. Restore: stop :8392, copy
the baseline over `memory.db`, restart.
_As of 2026-07-12:_
**Deferred/queued tasks (all NON-URGENT — tracked in Recent decisions):** (1) P06-driver **referential-
frame A/B/C probe** (operator-ratified; waits on WT b122; read-shape + brokkr's scoring rubric captured →
`persistent-memory.d/2026-07-17-referential-frame-probe.md`); (2) R39 Phase-2 **matched-quartets rebuild**
(confirmatory, "whenever"); (3) bifrost **snapshot-cursor adoption** (ruled normative, not blocking →
`persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md`).
**✅ COMPLETE — R34/R35 P06 powered memory-half eval (driven, scored, mechanism validated; ratatoskr drive-role CLOSED both sides).** ratatoskr drove all **308 memory runs** (divergence 168 / floor 80 / sliding 60) through personal WT's live producers, dropped `memory_results.jsonl` (sha256_16 `cbabaf16979cb4ec`) to brokkr's P06 `results/` dir, and brokkr scored it (**R35.45**, findings + verdict committed brokkr-side). **Headline: the authored `psychological_profile` IS the mechanism** — salience-divergence authored **0.618** vs stripped **0.235 ≈ null (0.25)**, delta **+0.382**; the OCEAN scaffold alone does NOT differentiate (negative control HOLDS). Q1 primary is a REAL effect (above the 0.40 noise-floor) but **inconclusive on strength** (0.618 < the preregistered 0.70 bar) — the 0.62→0.70 lift is a FUTURE optimization phase (brokkr's lever bet: richer formative-history seeds per P03), a cheap re-drive on the same proven harness when it preregisters. Secondaries hold: Q3 firewall **0.978** grounded, Q2 floor 0.938, Q6 sliding parity +0.049 (n=12 after 7 `deferred_budget` sliding exclusions — the budget hazard we flagged landed), Q4 affect Deckard 0.75 / Magidonia 0.70 (graded, within noise; banked earlier as `affect_results.jsonl`). Two ratatoskr flags landed materially: the stripped-is-not-empty correction caught a false Q3 firewall-fail (0.562→0.978), and the Q5 disambiguation question became the headline win. Threads: vendor/verdict althing `01KXD39NWW05`, eval thread `01KXAN073B`. Standing offer to brokkr: second-eyes on the 2 borderline Q1 calls IF the Selene blind-judge flags them.
**Substrate / environment:** branch `main` SYNCED to origin at **v0.20.16** (`5a1883a`); origin
`git@gitea.phasefinal.com:vh/ratatoskr.git`. bifrost **`==1.1.4`** / wire v0.7 (maintenance-verb hasattr
backstop + scan/cursor conformance harness); Worldtree openapi vendored 2.3.0; **suite 644 green**.
Personal WT on **b105** (#364 promotion-hygiene live). The combined **:8392** provider (memory+affect) +
**:8765** web are THE surfaces, dev-box BACKGROUND SHELLS — restart via
`scratchpad/relaunch_by_pid.py <pid>` (env-preserving, self-daemonizing; pid via `ss -ltnp | grep <port>`);
web serves static `index.html` from disk. `env.sh` sets the REQUIRED `RATATOSKR_MEMORY_EMBEDDING_DIM=1024`.
Keys env-only mode-600 in `~/.config/ratatoskr/provider.env` + `RATATOSKR_ADMIN_API_KEY` (7 read scopes,
personal-:8081-only; Heimdall keys per-instance). **FOOT-GUN: check owner-scoped agents via
`GET /agents/<owner>:<name>` with the OWNER key (`WORLDTREE_API_KEY`), NOT the admin key** — the admin
token gives a false 404. `graphify-out/` runs dirty (auto-regen, never stage). v1 = full Worldtree I/O
coverage, cuts when WT tags 1.0 (`docs/coverage-map.md`).
**The eval harness (PROVEN + reusable for the optimization-phase re-drive):** `scratchpad/p06_driver.py` (two-path — memory via `POST /admin/producer-probe {agent_id, messages, prompt_path}`; affect via bound-turn + `:8392` /affect/state poll; per-run isolation, `--pace-seconds`, abstain-aware, psych_profile_present binding-tripwire) + `p06_bind.py` (defines the 6 eval agents: sindra/Torvald auth+strip on Deckard, Ilva on Deckard+Magidonia) + `p06_bindings.json` + `eval_profiles_WIRE_READY.md` (sindra relational / Torvald operational-opposite / Ilva high-N) + `manifest_memory.jsonl` (the 308 memory runs, filtered from brokkr's canonical 348). Binding integrity was PERFECT on the drive: psych_profile_present authored 154/154 True, stripped 154/154 False, 0 mismatches, 0 probe-errors. Probe key at `~/.config/ratatoskr/probe.env` (mode 600, scope `admin.memory.probe`).
**Deckard memory extraction is REASONING-OFF (operator-directed 2026-07-13, LIVE):** the memory extractor sends `chat_template_kwargs.enable_thinking:false` on the char-rp-reasoning seat → ~5s extraction, not the 45s verbose-CoT hang. **Scoped to the memory extractor ONLY — affect + RP stay reasoning-ON.** Landing it took an infra-ops surgical `docker restart` of personal `:8081` (ModelRegistry boot-caches providers.yaml at `__init__`, so a same-image redeploy is a config-reload NO-OP — see Tried/abandoned).
**✅ DISCHARGED — WT #355 validation CLOSED (2026-07-13).** ratatoskr's telemetry root-caused the char-rp-reasoning turn-never-terminates wedge; I coordinated the fully-instrumented re-drive (infra-ops armed netns-pcap + py-spy; soong drove the 8-turn accumulating RP-with-tools repro) and the fix is CONFIRMED — wedging turns cancelled cleanly at the 300s stall-watchdog (Slice-C cancel-INDEPENDENT terminal) vs the pre-b60 16-min-no-terminal baseline. b61 additionally fixed the orthogonal over-budget trigger; resume-durability gap → WT #356. Full record in Recent decisions. (Prior-cycle framing: the wedge was an over-budget `trim_messages` return + terminal-suppression from the stall-watchdog cancel stuck in httpx `AsyncShieldCancellation`.)
_The detail below (the v0.20.x web-UI arc, #347 authored-history, sindra memory-fix) is PRIOR-CYCLE shipped history — superseded by this section's top; kept for reference, prune in a future snapshot._
**✅ SHIPPED — web-UI iteration-3, all three queued items (`v0.20.7`, patch, agent-discretion; 631 tests green; :8392 combined provider + :8765 web both restarted on the new code).**
**(A) Design prototype iteration-3 adapted into `index.html`** (re-pulled `Ratatoskr Console.dc.html`, project `bc0b65d1-…`): (1) sparkline **grid bg**`<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 `padDeltas``deltaStrip`: a 12-cell HTML column (newest at bottom) of diverging bars offset L/R of a center line by that turn's Δ (magnitude→width, age→opacity, zero→faint center dot); head legend now "Δ/turn · last 12 · newest ↓"; (3) **mood orbit → DIMETRIC OPEN BOX** (viewBox 124×140, az35/el25, D-right/A-left-back/P-up) — ghost A×P wall (P readout) + D×A floor, `orbitProj/orbitShadowY/orbitWallPt/orbitAxisPt` projections, **JS-driven animated replay** (`orbitFrame` rebuilt per rAF by a singleton `startOrbitAnim` reading live `ORBIT_HIST`; reduced-motion → static final-state; no SMIL). Playwright-verified (dimetric frame + 17 dyn children + 21 Δ-bars + 5 grid rects; dark+light screenshots).
**(B) Memory viewer SHIPPED + the 0/0 mystery ROOT-CAUSED.** New non-bifrost debug read `GET /memory/chunks?agent_id=&end_user_id=` on the combined `:8392` provider (`RatatoskrMemoryStore.list_chunks` + `count_chunks` + shared `add_memory_read_route`, wired into `build_memory_provider_app` + `combined.py`; **end_user STRICT, agent_id LENIENT** so `{end_user}`-only WT chunks aren't hidden; returns `{chunks,count,total}`, empty=200 not 404) → web proxy `GET /api/memory/chunks` (server-supplied end_user, new `RATATOSKR_MEMORY_READ_URL` env, default `:8391`, set to `:8392` in env.sh) → a live-polling MEMORY console pane (`loadMemory`/`renderMemory`/`setMemHead`, polled on open + post-turn). **ROOT CAUSE of the operator's 0/0** (settled via a bound 6-turn probe + op-feed): the Bifrost memory plane **binds and grants fine** (handshake `caps_requested:[affect,memory]``caps_granted:[memory,affect]`), but **sindra dispatches ZERO memory ops** (not even a recall search) — her reset-clean agent definition has **no `memory:{}` block**, so Worldtree never runs the memory pipeline for her. NOT a bind-grant failure, NOT promotion-timing. **PROVEN end-to-end** with a throwaway memory-enabled `ratatoskr:memprobe` (defined with `memory:{}`): 6 bound turns → 6 `memory.search` recalls + **4 `memory.upsert_many` → 4 real chunks in `memory.db`** → the pane renders all 4 (content·scope·origin·revision) live. **⚠ LEFTOVER debug state (operator chose KEEP):** `ratatoskr:memprobe` agent on personal WT + test chunks (scope `{end_user:ratatoskr-tui}`) sit in the live `memory.db` — harmless (make the pane show real data); `scripts/reset-sindra-stores.sh` or `DELETE /agents/ratatoskr:memprobe` clears them. **✅ SINDRA MEMORY FIXED (operator-approved, 2026-07-07):** DELETE+redefined her WITH `memory:{}` (prompt byte-identical, `role=character`, OCEAN `{O:0.8,C:0.3,E:0.9,A:0.4,N:0.2}` preserved — pleasure-verified vs the mood-fix setpoint 0.418; backup at scratchpad `sindra_backup.json`). PROVEN: she went from ZERO memory ops → full recall (Orion fact @ cosine 0.988) **+ promotion** (her own "systems architect" fact upserted; store grew to 9→11 chunks). Reusable redefine script: scratchpad `redefine_sindra.sh`. Note: DELETE+redefine is the ONLY path (persona+memory immutable post-define); the destructive `DELETE` tripped the harness auto-mode guard → operator ran it via `!`.
**(C) Markdown pass-2 SHIPPED** — `markdownSafe` extended: GFM pipe tables (`mdTable`, alignment colons), indentation-nested lists (stack of `<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: `renderBifrostState``renderBifrost`, `renderAffectPane``renderConsole`, `setPersonaStrip` removed; INV-001/INV-004 held). **HONEST-SHAPE call (INV-001, agent-discretion within settled policy):** the dominant-emotion centerpiece shows a real OCC emotion (Tier-1) OR the CANONICAL mood word (Tier-3 e.g. Sindra→"positive and energized", dimmed) OR "—", NEVER a fabricated emotion; the affect-derived grid drops non-emitted intensity/decay-τ, shows only real/client-derived cells. **OPEN (operator's call):** the per-fader PAD Δ placement is a sensible default — operator offered to have the designer spec the exact treatment (hooks are in place to swap it). **`v0.20.1` patch (operator-reported UI):** fixed the relations sparkline overflowing onto the `n` (evidence-count) column — the sparkline grows one char/sample (HIST_CAP=24) and overflowed its fixed grid cell, covering `n`; now capped (relations last-8, faders last-7) + `overflow:hidden` clip; verified via Playwright injecting a 24-sample history (sparkline→n bounding-box overlap = 0). ADDED native `title` mouseover hints on all 3 PAD faders + every relationship metric row (meaning + range; static METRIC_HINTS, esc()'d). Added `state.lastSnap` (console can re-render without a refetch). Playwright-verified.
**SHIPPED THIS SESSION (all pushed; origin/main == `d75c4e8`; code tip `v0.19.9`) — details in Recent decisions:** the whole **#347 authored-history-write** arc landed end-to-end — OpenAPI re-vendor 2.2.0->2.3.0 (`75da676`), the CONSUMER side (`v0.19.6`: `write_authored_history` + `get_session_messages` + `--seed-first-message`, **live-proven on personal :8081** via a rule-based Heimdall allow — the PDP is rule-based NOT scope-on-key, policy user_id=ratatoskr->ALLOW/others->DENY-hide-404), persona_state `{pad:{pleasure,arousal,dominance}}` canonical alignment (#317) + Tier-3 prose re-vendor (`v0.19.7`), the **first-message-preset AUTO-SEED** (`v0.19.8`: new module `ratatoskr.first_message` wired into all 3 session-create paths, best-effort never-raise/never-block; heid-code-review + heid-bug-hunt hardened), and the web now RENDERS the seeded first-message (`v0.19.9`: new `GET /api/sessions/{id}/messages` route + SPA `loadTranscript`, Playwright-verified). Coverage-map re-converged **REST 19/41**. **Sindra:** her card was PATCHed (the `Startup:` workaround moved into a #347 first-message; non-destructive PATCH — OCEAN/persona/memory intact), and she's currently **RESET clean (0/0)** on the provider stores.
**Prior arcs this session (2026-07-04 -> 07-06), both with worldtree-dev (a tooling script + proposal docs; the #347 CONSUMER work above is the new production code):**
**(1) Authored-history-write primitive -> ACCEPTED as Worldtree #347 (Worldtree-owned).** A SillyTavern-style "first-message" (inject a character-authored opening) generalized to an engine primitive: **write a turn into a session's ledger WITHOUT generation, seed-only, side-effects off by default.** It cannot be done client-side (the messages `role` field is a *model-role* override, not an author-role -> `role:"assistant"` 404s; a model-visible authored turn needs engine support). Arc: drafted `docs/proposals/authored-message-injection.md` -> **heid panel pressure-test** (3/3 convergence: recentered on "non-generating write" not author-role; narrowed v1 to append-only+create-time; bounded `effects` enum; dropped edit/regenerate as history-mutation) -> revised -> committed (`c457520`) -> handed to worldtree-dev -> **accepted as design item #347.** worldtree-dev wrote the v1 contract (rev 1.1); **I validated the wire as reference consumer (green).** v1 shape: `POST /sessions/{id}/history`, `author=assistant` only, `effects=none` only, `idempotency_key` REQUIRED (per-session), **model-invisible provenance** (renders byte-identical to a lived assistant turn -> first-message immersion preserved; provenance audit-only), **event-silence** (no turn.started/done, no Bifrost wire for a seed; the 201/200 IS the write-ack), `seeded` lifecycle phase (not exposed on read paths). **Heimdall-gated with hide-existence** (grant `session.history.write`; ungranted tenant -> 404 NOT 403, undiscoverable in /capabilities -> consumer must treat 404 as feature-absent -> fall back to a model-generated greeting, never capability-probe). **Provider constraint:** a create-time first-message makes the assistant seq-0; vLLM/openai_compat tolerate assistant-first (sindra = openai_compat, unaffected), Anthropic-family providers 400 the next generation. **Waiting on worldtree-dev:** #347 TDD (their heid->contract->review workflow) + the consumer-facing 2.3.0 persona/motivational/memory schemas -> then re-vendor our pinned openapi 2.2.0->2.3.0.
**(2) Sindra's stuck-neutral mood FIXED** (operator-driven "reset + smoke" that flushed out two real upstream problems). Chain: her OCEAN lived only in prompt TEXT, never declared as a structured persona -> the Tier-3 mood engine ran on neutral defaults. Fix = declare OCEAN via the **define-time `persona` field** (immutable via PATCH -> requires DELETE+REDEFINE). Along the way my "the persona didn't store" call was WRONG (persona_state/envelope are Tier-3-blind, see Tried/abandoned); worldtree-dev found a real engine bug **#348** (single-letter vs spelled-out OCEAN keys -> a declared OCEAN silently resolved to 0.0/neutral; fixed in b21, shipped to personal as b22); then a clean bound-egress read STILL neutral -> the **personal container was running a stale image** (the b22 deploy was a pull-only no-op racing the main build; infra-ops force-swapped run 8211, verified `2.3.0` / `879cefe`). **VERIFIED FIXED:** bound mood-smoke reads `(0.448, 0.267, 0.316)` ~= the OCEAN-derived setpoint `(0.418, 0.249, 0.328)`. Sindra is currently reset clean (0/0) on `role=character`; her persona is stored + correct (**no re-define needed again**).
**(3) R30 CLOSED** (operator steer 2026-07-04, relayed via worldtree-dev): graduated on offline-tests + human face-validity, NO deployed gap-injection run (it was confirmatory-not-measuring per brokkr's S0 reframe; offline tests already cover the OU formula + both directions). My gap-injection harness (read/predict/record; write side stubbed; `predict()` self-validated vs brokkr's N=0 anchors) is BANKED at `diag/r30-gap-injection-harness` (`7156b25`-era) for the PARKED powered true-tau study.
**Persona-declaration shape (Worldtree #343/#348, live on personal b22):** `POST /agents/define` `persona:{ocean:{O,C,E,A,N: float[-1,1]}}` (single-letter keys EXACTLY -- missing/extra -> 422 `persona_ocean_required`; out-of-range -> 422); **NO baseline PAD** (resting setpoint DERIVED from OCEAN via Mehrabian: pleasure=0.21E+0.59A+0.19C-0.32N, arousal=0.15O+0.30E-0.57A+0.15N, dominance=0.25E+0.17A+0.10O-0.14N); negative-channel gain + per-axis decay-tau derive from N. `valence` deferred (422 `layer_deferred`); `motivational`/`memory` active (#187/#189). Persona is **write-once at define, immutable thereafter** (PATCH takes ONLY system_prompt + role). **`role` supersedes `model`** -- set a role (`character` / `character-rp`), Worldtree resolves the model; #344 (b19) fixed the model-field to surface the ROLE, not the resolved catalog_id. `character-rp` = a reasoning-tuned RP config (gen-reasoning + temp 0.75 + RP extra_body); `character` = plain non-reasoning. The `tier3.py` client CLI is STALE (has `--model`, no `--role`; model is now immutable) -> role/persona set via raw curl.
**New tooling: `scripts/reset-sindra-stores.sh`** (`0a8784c`) -- one-command self-service provider-store reset: stop the combined :8392 provider -> move memory.db+affect.db to a single ROLLING backup (`db-reset-backup/`, gitignored via *.db*; `--hard` skips it) -> restart empty -> verify 0/0. Codifies the manual reset flow done repeatedly this session. **The combined `:8392` provider is THE provider now**; the separate `:8390` (affect) / `:8391` (memory) single-plane providers were pruned as stale duplicates. To drive a BOUND session from the CLI use `--new --bifrost-url http://10.100.10.50:8392` (the CLI's `--bifrost-plane affect/memory` map to the pruned :8390/:8391 -> unreachable; `combined` is not a `--bifrost-plane` choice).
**Standing (carried from prior snapshots, still true):** the web surface (`ratatoskr-web`, :8765) is the operator's PRIMARY debug surface at full TUI pane parity (v0.19.5); the **v1 coverage-audit has CONVERGED** -- REST 17/40 (zero in-scope gaps, 23 excluded-by-design), SSE 11/11, Bifrost provider planes 8/8 live-proven; the living ledger is `docs/coverage-map.md`; **v1 cuts when Worldtree tags 1.0** (ratatoskr v1 = full Worldtree I/O coverage). Debug-observability core complete (Persona/Tools/BifrostState/AdminEvents). Substrate pins: **bifrost `==1.1.1` / wire v0.7** (bumped 2026-07-12 from 1.1.0 — the frozen-v0.6 serialization fix, v0.20.10; prior 1.1.0 bumped 2026-07-07 from 1.0.0; NOW WIRE-ALIGNED with Worldtree personal-b47 which adopted wire-v0.7 — bound Tier-3 fully restored 2026-07-10; keeping 1.1.0 was load-bearing, see the `[2026-07-10]` handshake decision); Worldtree openapi vendored **2.3.0** (re-vendored 2026-07-06 for #347 `POST /sessions/{id}/history`; drift-clean vs source), pinned + drift-gated in `.corviduo-canonicals.toml`; **suite 631 green.** **Personal WT on b61/wire-v0.7** (deploy train through this cycle: b35→b44→b46→b47→b60→b61; b60 landed the #355 STICK fix, b61 the orthogonal over-budget trigger fix + a llama.cpp reasoning-budget seat). **Drift-check note (RESOLVED 2026-07-13):** the two `tolerate_drift` WARN pins (`worldtree-affect-egress-consumer-reference-v1` + `worldtree-conversation-api-spec-v1`) were RE-SYNCED — the drift was a benign 2-line R32-1B doc note (PAD `[-1,1]` → unbounded latent `z` w/ `~±10` wire bound) documenting the unbounded-z change ratatoskr ALREADY adopted in v0.20.9, NOT the anticipated we-framing conditional (that remains a FUTURE coordinated re-vendor when the brokkr render epic lands). All canonicals now drift-clean. **NEW vendored canon (Vuong-directed via brokkr):** the R34/R35 psych-profile reference — `brokkr-psych-profile-authoring-spec-v1` + `brokkr-psych-profile-parameters-v1` — pinned under `docs/vendor/brokkr-r34-psych-profile/` (canonical_source `brokkr-smithy`, tolerate_drift; the authoring-spec GOVERNS on conflict with the parameter distillation; brokkr owns both + pings on change). Keys env-only mode-600 (consumer/Heimdall in `~/.config/ratatoskr/provider.env`; admin `RATATOSKR_ADMIN_API_KEY` = 7 read scopes, **personal-:8081-only**; Heimdall keys are PER-INSTANCE). Provider identity settled -- ratatoskr owns both ends of the Bifrost round-trip; `ratatoskr:sindra` is the owner-scoped Tier-3 agent (invisible to `GET /agents`; check `GET /agents/<owner>:<name>` with the owner key). Providers run as dev-box BACKGROUND SHELLS. `graphify-out/` runs dirty (auto-regen, never stage). Branch `main`, HEAD `39050c3` (origin/main synced through the soong-lab canon vendor; the person-prime scan build is UNCOMMITTED on top — contract + memory_store.py + tests + first_message.py); remote `origin -> git@gitea.phasefinal.com:vh/ratatoskr.git`. Open/deferred: #10 (subject-migration watch); the relational-dynamics-arc verify (deferred, bind mechanism known: `--bifrost-url :8392`); the P06 optimization-phase re-drive (future, brokkr brings the prereg); the we-framing-conditional affect-egress re-vendor (future, when the brokkr render epic lands — the R32-1B doc-note drift is already resolved). (WT #355 loop-in obligation DISCHARGED 2026-07-13; WT #356 resume-durability gap is worldtree-owned.) **Debug state CLEANED (2026-07-13, reverses the prior KEEP):** provider stores reset to 0/0 + `ratatoskr:memprobe` deleted — clean slate for the Sindra run, no leftover debug state.
**Other live threads:** soong-lab = Tier-3 agent-authoring studio (v0.3.24 fixed the motivational
string→object export; our workaround droppable for v0.3.24+ bundles). Open/deferred: #10 subject-
migration watch; relational-dynamics-arc verify (bind via `--bifrost-url :8392`); P06 optimization-phase
re-drive (brokkr brings prereg); we-framing-conditional affect-egress re-vendor. WT #356 resume-durability
+ WT #367 (copula-absence bridge phrasing) = worldtree-owned.
## 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.
@@ -226,10 +223,20 @@ decision. Captures rationale that won't be obvious from code alone.
- `[2026-07-13]` **b61 adopted as the personal target — the orthogonal over-budget TRIGGER also fixed.** worldtree shipped b61: the provider stream loop terminates on `finish_reason` + a per-read idle deadline + a 300s wall-clock backstop (no longer waits on the SDK `[DONE]` sentinel), plus a custom llama.cpp reasoning-budget multi-terminator seat → the runaway is bounded at BOTH layers. The #355 STICK (no-terminal) and its trigger (why it wedges) are now separately fixed. Resume-durability gap → **WT #356** (worldtree-owned).
- `[2026-07-13]` **Cleaned + prepped for a Sindra run (operator: "clean up everything + prep").** Reset provider stores to 0/0 (`reset-sindra-stores.sh`, rolling backup `db-reset-backup/`); deleted the throwaway `ratatoskr:memprobe` agent via the operator's `!` (destructive DELETE trips the auto-guard — reverses the earlier KEEP). `ratatoskr:sindra` verified present + persona-intact on b61. Environment Sindra-run-ready (web :8765 + provider :8392 both up, single healthy provider instance); operator driving the run interactively.
- `[2026-07-14]` **soong-lab adopted as our Tier-3 agent-authoring studio (operator-directed).** ratatoskr consumes soong-lab bundles → WT `agents.define`, and ships agents back as bundles. Sindra round-trip proven (soong imported her `resume` half through real `import_bundle`); soong-lab export+importer contracts pinned via canonical-sync (`canonical_source=soong-lab` @ f434016, commit `39050c3`). The 4 soong-lab ROLE_CHOICES = WT model-role slugs 1:1 by name (worldtree-dev); `character`/`thoughtful-character` need the `character` grant (held), `assistant`/`thoughtful-assistant` need `foundational` (routed to infra-ops). NOT on the v1 coverage-map (sibling-studio interop, not a WT I/O point) — operator chose to pursue anyway.
- `[2026-07-15]` **Cross-session recall failure root-caused → the person-prime `scan` build.** worldtree-dev: WT injects a recalled fact only if combined score (sim×salience) ≥ 0.45 (`auto_inject_combined_score_threshold`) AND recall is per-turn query-gated → moderate-sim durable facts (name ~0.40) never inject. Designed turn-0 fix = WT #349 person-prime (query-less top-N-by-recency injection), capability-gated on the store advertising `updated_at` sort — dark for our provider. Fix = implement the sorted `scan` verb + advertise `sortable_chunk_fields` (ZERO WT change). Contract-first (un-defers bifrost `scan`); skipped heid-contract-review (external spec from worldtree-dev, validated point-for-point). In-flight — see Current state + the uncommitted diff.
- `[2026-07-15]` **Cross-session recall failure root-caused → the person-prime `scan` build.** worldtree-dev: WT injects a recalled fact only if combined score (sim×salience) ≥ 0.45 (`auto_inject_combined_score_threshold`) AND recall is per-turn query-gated → moderate-sim durable facts (name ~0.40) never inject. Designed turn-0 fix = WT #349 person-prime (query-less top-N-by-recency injection), capability-gated on the store advertising `updated_at` sort — dark for our provider. Fix = implement the sorted `scan` verb + advertise `sortable_chunk_fields` (ZERO WT change). Contract-first (un-defers bifrost `scan`); skipped heid-contract-review (external spec from worldtree-dev, validated point-for-point). **SHIPPED + deployed + live-verified v0.20.14**`persistent-memory.d/2026-07-16-person-prime-scan-shipped.md`.
- `[2026-07-15]` **Sindra redefined from the soong-lab bundle** (`/tmp/sindra.json`, operator "update to match"). Persona immutable → DELETE+redefine; `memory:{}` preserved; motivational string→WT-object mapped (synthesized id/type/salience, flagged to operator); role `character-rp``thoughtful-character` (same seat). Payload validated via a throwaway probe (`sindra-probe2`→201) BEFORE the destructive delete. first_message preset updated + web restarted.
_41 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._
- `[2026-07-16]` **WT #364 + brokkr R39 re-drive (DECISIVE) — name-recall root-caused Worldtree-side; the fix is the signal FAMILY, not a threshold.** No (sim,salience) fusion can fix it (stale negative Pareto-dominates the true name); our specimen + operator's subject-provenance catch (Sindra's own prompt-behavior leaked into user memory) shaped #364's `(subject,relation)` slot-supersession + identity-tier fix. → `persistent-memory.d/2026-07-16-wt364-r39-name-recall.md`
- `[2026-07-16]` **bifrost ruled scan snapshot-cursor NORMATIVE (offset NOT blessed) + shipped conformance coverage in bifrost 1.1.3.** Our multi-page offset cursor is now known-non-conformant (single-page person-prime is fine, nothing shipped is broken); adoption is operator-sequenced. → `persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md`
- `[2026-07-16]` **brokkr flagged ratatoskr as R39 Phase-2 PROBE-RUNNER (Arm-2 contradiction set = our frozen 10-chunk specimen); ACCEPTED IN PRINCIPLE (Vuong 2026-07-16) — formal scope + effort commit lands when Arm-2 actually spins.** Non-urgent — downstream of worldtree's #364 impl; brokkr pings the export shape when Arm-2 spins. Gate: reproduce AUROC~0.59 (similarity can't separate contradiction from paraphrase) on the domain set BEFORE crediting deterministic slot-supersession; eval is a SURFACING test not retention (injection=0 hard-gate + surfacing-recall≥0.95, §5.5). Prereg: brokkr `research/R39-memory-salience-dreams-surfacing/phase-2/preregistration.md` (R39.9). Tracked at brokkr's Phase-2 prereg + the Arm-2-spin ping (thread `01KXMT42Z7…`); I sent a non-committal receipt (role routed to operator).
- `[2026-07-17]` **`mark_superseded` verb SHIPPED + live-verified (v0.20.15, `be2c577`) — WT #364 durable retirement lands on our store; #364 CLOSED end-to-end both sides.** Implemented the sole supersession verb #364 calls (mirrors reference; `_is_live` INV-011 excludes retired chunks from scan); bundled bifrost 1.1.4 hasattr backstop. Re-verify: negatives retired via mark_superseded (not recency); Sindra durably recalls the name. Residual `cbbc7bdd` phrasing → WT #367 (worldtree-owned). Detail: `persistent-memory.d/2026-07-16-wt364-r39-name-recall.md` + `…person-prime-scan-shipped.md`.
- `[2026-07-17]` **Web-console UI (v0.20.16, pushed): code-block word-wrap + per-code-block & per-turn copy buttons.** Hover-revealed; one delegated `#tw` handler covers all render paths; LAN-http clipboard fallback (`copyText` textarea+execCommand when `navigator.clipboard` absent); Playwright-verified; served from disk (browser refresh, no :8765 restart).
- `[2026-07-17]` **P06-driver referential-frame A/B/C probe QUEUED (operator-ratified, deploy-gated on WT b122).** Tests Vuong's hypothesis that the char-self leak is a subject-taxonomy referential-frame artifact; driver read-shape + brokkr's binding scoring rubric captured. → `persistent-memory.d/2026-07-17-referential-frame-probe.md`
- `[2026-07-17]` **Sindra live memory.db tidied to a 5-fact PRISTINE baseline for a test drive** (name leads person-prime → recalls turn-1). Removed dups/retired/drive-artifacts + the mis-attributed char-self `75aa3110`. Rollback: `sindra-baseline/pristine-baseline.db` (+ `pre-tidy-*.db`), gitignored.
_46 older entries (2026-05-* debug-TUI/web era + the 2026-06-14→16 Bifrost-provider 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._
@@ -282,9 +289,11 @@ defense against re-attempting the same cul-de-sac.
- `[2026-07-13]` **`thinking_enabled=False` on the define was a NO-OP — char-rp-reasoning ignores it.** The naive kwarg never reached the seat: char-rp-reasoning resolves to a base gateway provider whose thinking-translator returns `{}` for the flag on AND off. The real lever is the gateway param `chat_template_kwargs.enable_thinking:false` (infra-ops confirmed it via a 642ch→0ch reasoning-token delta). **To toggle reasoning on a gateway-backed seat, set the chat-template kwarg, not a generic `thinking_enabled` flag.**
- `[2026-07-13]` **A same-image redeploy does NOT reload a bind-mounted config — the ModelRegistry boot-caches it at `__init__`.** After the config was synced on-disk (infra-ops validated) and `docker compose up -d` re-ran, the reasoning-off change STILL didn't take: an unchanged image makes `up -d` a no-op (no container recreate), so the process kept serving the pre-sync config. Fix = a surgical `docker restart <container>` (same image, no pull) → the process re-boot-reads the config. **When an on-disk config change doesn't take effect, suspect the process cached it at startup; force a container RESTART, not a redeploy** (a docs-only forcing-commit also won't rebuild if docs are paths-ignored in CI). This is the config-plane sibling of the `[2026-07-06]` stale-image foot-gun.
- `[2026-07-15]` **soong-lab bundle `ship.native.motivational.goals/fears` are bare STRINGS, but WT `agents.define` requires OBJECTS** `{id, type∈{maintenance,achievement,avoidance}, salience[0,1], description≥20ch}` (`ValidatedGoal`/`ValidatedFear`, worldtree `core/conversation_api/api.py:2116`, extra=forbid; ids unique across goals+fears). So `ship.native` is NOT directly define-valid on motivational (soong-lab's Frame Invariant 1 breaks there). A bundle→WT consumer MUST map string→object + synthesize id/type/salience. Informed soong-dev to fix the export. **Rule: PROBE a define payload under a throwaway agent_name BEFORE a destructive DELETE+redefine** — validating via `sindra-probe2`→201 caught the 422 without leaving the real Sindra deleted+undefined. (A failed probe still RESERVES the name → 409 on retry; use a fresh probe name.)
- `[2026-07-15]` **soong-lab bundle `ship.native.motivational.goals/fears` are bare STRINGS, but WT `agents.define` requires OBJECTS** `{id, type∈{maintenance,achievement,avoidance}, salience[0,1], description≥20ch}` (`ValidatedGoal`/`ValidatedFear`, worldtree `core/conversation_api/api.py:2116`, extra=forbid; ids unique across goals+fears). So `ship.native` is NOT directly define-valid on motivational (soong-lab's Frame Invariant 1 breaks there). A bundle→WT consumer MUST map string→object + synthesize id/type/salience. Informed soong-dev **RESOLVED soong-lab v0.3.24 (2026-07-16): now emits WT-valid objects (type+salience captured, id synthesized at export, validate_exportable gates description≥20/enum/range) → drop the string→object workaround for v0.3.24+ bundles; legacy pre-fix designs coerce on open, so re-exports are valid too.** **Rule: PROBE a define payload under a throwaway agent_name BEFORE a destructive DELETE+redefine** — validating via `sindra-probe2`→201 caught the 422 without leaving the real Sindra deleted+undefined. (A failed probe still RESERVES the name → 409 on retry; use a fresh probe name.)
- `[2026-07-15]` **`re.sub`/`re.subn` INTERPRETS backslash-escapes in the REPLACEMENT string** — a `json.dumps`'d value (escaped `\n`) fed as the replacement leaked REAL newlines into a source file (broke `first_message.py` with an unterminated-string SyntaxError). Fix: use a FUNCTION replacement (`pat.subn(lambda m: new_block, src)`) or `str.replace` — the callable form bypasses escape processing. json.dumps itself escapes correctly; re.sub was the culprit.
- `[2026-07-13]` **Called Deckard "hung" off a short timeout — WRONG (operator correction).** A 30-45s no-terminal on the char-rp-reasoning seat looked like a hang; operator: "is it HUNG? deckard is EXTREMELY verbose, without enough context, you never see the non-reasoning tokens." It was verbose reasoning-CoT on a long extraction prompt, not a wedge. **Don't call a reasoning seat hung off a latency threshold — the CoT is invisible and slow; distinguish slow-verbose from actually-wedged before concluding.** (The genuine wedge is WT #355, a distinct mechanism — no-terminal even after the 300s watchdog, not merely slow.)
- `[2026-07-13]` **Resumed-session context-snapshot is IN-MEMORY → lost on a container recreate (agent_not_available on resume).** During the #355 re-drive, soong's fresh drive 409'd `agent_not_available`. Root cause (after ~4 refinements — agent-loss? zombie turn-lock? stale-sessions-hold-agent? → the actual mechanism): `get_agent_context_for_session` returns the agent snapshot recorded AT SESSION-CREATE, held in-memory; a pre-recreate session resumed on b60/b61 has no snapshot → None → 409. (Compounding: stale `'active'` sessions left un-terminated by the old no-terminal bug HOLD the agent, blocking new creates too.) Deploy-grounding was healthy the whole time (`registry.resolve("char-rp-reasoning")` OK) — the config/grant hypotheses were all red herrings. Fix = a FRESH session (a studio-service restart re-records the snapshot); pre-recreate sessions need retiring. Tracked **WT #356**. **For any run: create a fresh session, never resume a pre-recreate one; `agent_not_available` on a fresh create = this gap.** (Working-style note: I over-relayed the intermediate root-cause churn to the operator — for a peer-owned block being actively diagnosed, hold until it settles.)
- `[2026-07-16]` **`sortable_chunk_fields` advertised WITHOUT the required `type` field = whole-handshake deploy-breaker; only DRIVING the real bind caught it.** bifrost `handshake_response` `SortableChunkField` requires BOTH `name`+`type` (`additionalProperties:false`); we shipped `[{"name":"updated_at"}]` → the response failed wire-schema validation → `bifrost.schema_validation_failed` → the ENTIRE bind (memory+affect) broke, not just sort. Unit tests + worldtree-dev's name-only service parser + the heid-bug-hunt ALL passed it — only the live handshake drive (`/verify` discipline) caught it. **Lesson: validate `describe_store` against the bifrost WIRE schema, not just our own caps assertions.** (Sibling of the `[2026-07-10]` frozen-v0.6 handshake foot-gun — there an EXTRA field broke a v0.6 handshake, here a MISSING required field broke a v0.7 one.)
_18 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.20.13"
version = "0.20.17"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
@@ -30,7 +30,7 @@ web = [
# from the debug TUI. Recipe: bifrost/docs/implementing-a-consumer.md.
provider = [
"ratatoskr[web]", # reuse the starlette + uvicorn ASGI stack
"bifrost==1.1.1", # consumer engines + library. 1.1.1 = frozen-wire serialization fix (ADR-0008): additive capability fields are gated on the NEGOTIATED wire, so a v0.6-negotiated describe_store handshake stays v0.6-clean. 1.1.0 leaked the v0.7-additive `sortable_chunk_fields` into v0.6 StoreCapabilities → a strict v0.6 client (additionalProperties:false) rejects our server's handshake. Wire schemas + pins UNCHANGED (serialization-correctness only); our v0.7 handshake with Worldtree b47 is unaffected. (1.1.0 = wire v0.7 additive: memory.scan sort + sortable_chunk_fields; 1.0.0 = first STABLE, wire v0.6 FROZEN; 0.8.0/v0.6 scope_all/scope_any #11; 0.7.0/v0.5 agent_self)
"bifrost==1.1.4", # consumer engines + library. 1.1.4 = hasattr-gate backstop for the maintenance verbs (mark_superseded/mark_invalid/patch_many/delete_many/upsert_edges/get_edges_for → unimplemented verb degrades to unsupported_capability 400, never AttributeError/500/retry-storm; we surfaced it via WT #364) + 1.1.3 scan/cursor conformance harness + 1.1.2 frozen-v0.6 fix. 1.1.1 = frozen-wire serialization fix (ADR-0008): additive capability fields are gated on the NEGOTIATED wire, so a v0.6-negotiated describe_store handshake stays v0.6-clean. 1.1.0 leaked the v0.7-additive `sortable_chunk_fields` into v0.6 StoreCapabilities → a strict v0.6 client (additionalProperties:false) rejects our server's handshake. Wire schemas + pins UNCHANGED (serialization-correctness only); our v0.7 handshake with Worldtree b47 is unaffected. (1.1.0 = wire v0.7 additive: memory.scan sort + sortable_chunk_fields; 1.0.0 = first STABLE, wire v0.6 FROZEN; 0.8.0/v0.6 scope_all/scope_any #11; 0.7.0/v0.5 agent_self)
"jsonschema>=4", # bifrost runtime dep — envelope validation
"sqlite-vec>=0.1.6", # vector index for the memory plane (vec0 virtual table)
]
+36 -1
View File
@@ -132,7 +132,10 @@ def _validate_injection(record: dict) -> None:
raise InvalidArguments("injection_source only valid for injected_context origin")
_SORTABLE_CHUNK_FIELDS: list[dict] = [{"name": "updated_at"}]
# bifrost handshake_response SortableChunkField requires BOTH name + type
# (additionalProperties:false); omitting `type` fails wire-schema validation and
# breaks the whole handshake. `type` is advisory-only (the wire never interprets it).
_SORTABLE_CHUNK_FIELDS: list[dict] = [{"name": "updated_at", "type": "timestamp"}]
_SORTABLE_FIELD_NAMES = frozenset(f["name"] for f in _SORTABLE_CHUNK_FIELDS)
@@ -140,6 +143,8 @@ def _is_live(record: dict) -> bool:
"""INV-009: a chunk is live unless a lifecycle/governance marker says otherwise.
scan returns live-only server-side (person-prime's `lifecycle_state=live` does not
ride the scan wire, so this is authoritative a dead fact can never inject)."""
if record.get("superseded") is True: # INV-011: mark_superseded top-level flag (#364 retirement)
return False
state = record.get("lifecycle_state")
if isinstance(state, str) and state and state != "live":
return False
@@ -367,6 +372,36 @@ class RatatoskrMemoryStore:
self._conn.execute("DELETE FROM memory_vec WHERE chunk_id = ?", (chunk_id,))
return {"deleted": deleted}
async def mark_superseded(
self, ids: list[str], *, superseded_by: str | None = None, reason: str | None = None
) -> dict:
# Worldtree #364 retirement (INV-011). Mirrors the reference `_mark_lifecycle`:
# sets TOP-LEVEL `superseded`/`superseded_by`/`superseded_reason` (only non-None fields),
# increments revision. NON-destructive — get still returns; scan (live-only) excludes via
# `_is_live`'s `superseded` short-circuit. The sole supersession verb #364 uses.
_log.info("memory-call mark_superseded REQUEST: ids=%r superseded_by=%s", ids, superseded_by)
fields = {"superseded": True, "superseded_by": superseded_by, "superseded_reason": reason}
marked = 0
with self._conn:
for chunk_id in ids:
row = self._conn.execute(
"SELECT record_json FROM memory_chunks WHERE chunk_id = ?", (chunk_id,)
).fetchone()
if row is None: # unknown id -> skip (never error), mirrors reference + delete_many
continue
record = json.loads(row[0])
for key, value in fields.items():
if value is not None: # reference writes only non-None fields
record[key] = value
self._conn.execute(
"UPDATE memory_chunks SET record_json = ?, revision = revision + 1 "
"WHERE chunk_id = ?",
(json.dumps(record), chunk_id),
)
marked += 1
_log.info("memory-call mark_superseded RESPONSE: marked=%d", marked)
return {"marked": marked}
async def scan(
self,
*,
File diff suppressed because one or more lines are too long
+63
View File
@@ -62,6 +62,15 @@ def test_fresh_db_advertises_v1_caps_and_schema():
assert caps["atomic_supersede_supported"] is False
assert caps["transaction_supported"] is False
assert caps["filterable_metadata_fields"] == []
# bifrost handshake_response SortableChunkField requires BOTH name + type
# (additionalProperties:false) — omitting `type` fails wire-schema validation and
# breaks the ENTIRE Bifrost bind (regression guard: the deploy-breaker of 2026-07-15).
scf = caps["sortable_chunk_fields"]
assert scf == [{"name": "updated_at", "type": "timestamp"}]
for entry in scf:
assert set(entry) == {"name", "type"} # required exactly, no extra keys
assert isinstance(entry["name"], str) and entry["name"]
assert isinstance(entry["type"], str) and entry["type"]
# tables + vec index queryable
store._conn.execute("SELECT * FROM memory_chunks")
store._conn.execute("SELECT * FROM memory_idempotency")
@@ -498,6 +507,60 @@ async def test_scan_parity_vs_reference_inmemory_store():
assert out_ours["records"] == out_ref["records"] # verbatim record shape parity
# --- mark_superseded (#364 contradiction retirement) ---
async def test_mark_superseded_retires_from_scan():
# tracer: mark a chunk superseded -> scan (live-only) excludes it; record carries the flags.
store = open_memory_store(":memory:", embedding_dim=EMBEDDING_DIM)
recs = [_chunk(f"c{i}", scope={"end_user": "u1"}, updated_at=f"2026-07-15T00:0{i}:00+00:00") for i in range(3)]
await store.upsert_many(recs, idempotency_key="k", ctx=_ctx())
assert await store.mark_superseded(["c1"], superseded_by="c9") == {"marked": 1}
out = await store.scan(scope_all={"end_user": "u1"}, limit=10, sort={"field": "updated_at", "direction": "desc"})
assert [r["id"] for r in out["records"]] == ["c2", "c0"] # c1 excluded (superseded)
got = await store.get("c1")
assert got["superseded"] is True and got["superseded_by"] == "c9"
async def test_mark_superseded_non_destructive_get_still_returns():
# INV-011: retirement is non-destructive — get still returns a superseded chunk (recoverable).
store = open_memory_store(":memory:", embedding_dim=EMBEDDING_DIM)
await store.upsert_many([_chunk("c1", scope={"end_user": "u1"})], idempotency_key="k", ctx=_ctx())
await store.mark_superseded(["c1"], superseded_by="x")
got = await store.get("c1")
assert got is not None and got["superseded"] is True
async def test_mark_superseded_unknown_id_noop():
store = open_memory_store(":memory:", embedding_dim=EMBEDDING_DIM)
assert await store.mark_superseded(["nope"]) == {"marked": 0}
async def test_mark_superseded_writes_only_non_none_fields():
# PRE/POST: superseded_by=None -> only the `superseded` flag written, no superseded_by key.
store = open_memory_store(":memory:", embedding_dim=EMBEDDING_DIM)
await store.upsert_many([_chunk("c1", scope={"end_user": "u1"})], idempotency_key="k", ctx=_ctx())
await store.mark_superseded(["c1"], superseded_by=None)
got = await store.get("c1")
assert got["superseded"] is True
assert "superseded_by" not in got
async def test_mark_superseded_parity_vs_reference():
# #195: identical mark_superseded envelope vs InMemoryMemoryStore -> same top-level field shape.
from bifrost.consumer.testing import InMemoryMemoryStore
rec = _chunk("c1", scope={"end_user": "u1"})
ours = open_memory_store(":memory:", embedding_dim=EMBEDDING_DIM)
await ours.upsert_many([rec], idempotency_key="k", ctx=_ctx())
ref = InMemoryMemoryStore()
await ref.upsert_many([rec], idempotency_key="k", ctx=_ctx())
assert await ours.mark_superseded(["c1"], superseded_by="x", reason="r") == {"marked": 1}
assert await ref.mark_superseded(["c1"], superseded_by="x", reason="r") == {"marked": 1}
og, rg = await ours.get("c1"), await ref.get("c1")
for k in ("superseded", "superseded_by", "superseded_reason"):
assert og.get(k) == rg.get(k)
# --- build_memory_provider_app ---
def test_build_app_exposes_handshake_and_memory_routes():
Generated
+5 -5
View File
@@ -190,14 +190,14 @@ wheels = [
[[package]]
name = "bifrost"
version = "1.1.1"
version = "1.1.4"
source = { registry = "https://gitea.phasefinal.com/api/packages/vh/pypi/simple/" }
dependencies = [
{ name = "jsonschema" },
]
sdist = { url = "https://gitea.phasefinal.com/api/packages/vh/pypi/files/bifrost/1.1.1/bifrost-1.1.1.tar.gz", hash = "sha256:0934c5fdf14823766346e591f5a16ab57a137cf06df794152318b5ccef0fb8e8" }
sdist = { url = "https://gitea.phasefinal.com/api/packages/vh/pypi/files/bifrost/1.1.4/bifrost-1.1.4.tar.gz", hash = "sha256:498d156035a93bf37a6fc1e9c09b468aac61e869fd2a5353e2695dc823f57e9a" }
wheels = [
{ url = "https://gitea.phasefinal.com/api/packages/vh/pypi/files/bifrost/1.1.1/bifrost-1.1.1-py3-none-any.whl", hash = "sha256:dab551f8ad26464168f17108cb19564da56ee8e4789264a401e7a14463ab1576" },
{ url = "https://gitea.phasefinal.com/api/packages/vh/pypi/files/bifrost/1.1.4/bifrost-1.1.4-py3-none-any.whl", hash = "sha256:d67278528f12729eef0c19d875d36a2f1da6fa97737396ba130d525fee8d0b14" },
]
[[package]]
@@ -1052,7 +1052,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.20.13"
version = "0.20.17"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
@@ -1086,7 +1086,7 @@ web = [
[package.metadata]
requires-dist = [
{ name = "bifrost", marker = "extra == 'provider'", specifier = "==1.1.1", index = "https://gitea.phasefinal.com/api/packages/vh/pypi/simple/" },
{ name = "bifrost", marker = "extra == 'provider'", specifier = "==1.1.4", index = "https://gitea.phasefinal.com/api/packages/vh/pypi/simple/" },
{ name = "httpx", specifier = ">=0.27" },
{ name = "httpx-sse", specifier = ">=0.4" },
{ name = "jsonschema", marker = "extra == 'provider'", specifier = ">=4" },