Memory-plane Bifrost consumer (v1 basic plane): a SQLite+sqlite-vec-backed durable memory store Worldtree persists Tier-3 agent memory chunks into and recalls via vector search.
v1 = worldtree-dev's BASIC PLANE only (search / get / upsert / delete + describe_store + health), which is the ONLY surface Tier-3's live path touches (#294). Worldtree v0.35.3 already requests + maps it — no Worldtree-side blocker.
Exact chunk-record field names (embedding vector key, scope keys, id key) are pinned in TDD against InMemoryMemoryStore — the executable spec — as they were for affect.
Embedding dimension matches Worldtree's PINNED_EMBEDDER_DIM, supplied as config (env RATATOSKR_MEMORY_EMBEDDING_DIM); the sqlite-vec virtual table is created at that fixed dim.
Whether the memory DB shares one SQLite file with affect or uses its own — default SEPARATE per plane (cleaner); revisit at the combined two-plane server (guide §7).
metadata_filter richness: v1 advertises filterable_metadata_fields=[] (scope_filter only); add fields when a concrete Worldtree filter need lands.
The second plane of ratatoskr's Tier-3 Bifrost consumer (after the shipped affect
plane). A SQLite + sqlite-vec durable store Worldtree writes agent memory
chunks into (upsert_many) and recalls from by vector similarity
(search), plus point reads (get/get_many) and deletes (delete_many).
v1 is worldtree-dev's basic plane — the only surface Tier-3's live path uses;
the gated verbs (edges, scan, atomic_supersede, mark_*, patch, maintenance) are
deferred. We implement bifrost's ownMemoryDataStore Protocol and hand it to
build_memory_app. Conformance is #195 parity vs InMemoryMemoryStore.
The boundary (ADR-0001/0002/0009): Worldtree owns intelligence — appraisal,
consolidation, trust; we own permanence. But unlike affect (blind conduit),
memory is a structural index: we read the chunk's embedding vector + scope
keys + id/revision + origin/injection_source to serve search and enforce the
wire's rules. The semantic content/distillate + the inert fields (trust_tier,
provenance, source_role) are persisted verbatim and never interpreted.
Data flow
In: Worldtree → POST /bifrost/memory-call → library validates envelope +
per-dispatch JWT → the verb on our store.
At rest: SQLite —
memory_chunks(chunk_id PK, record_json, revision, agent_id, end_user_id, scope_json, origin, invalid, superseded, ...) — the verbatim chunk + the
extracted structural columns for scope-filtering + lifecycle.
a sqlite-vec virtual table memory_vec(chunk_id, embedding[<dim>]) — the
embedding index for cosine search.
memory_idempotency(idempotency_id PK, digest, expires_at) — replay/conflict
cache (same shape as the affect plane).
Out: verb-specific dicts mirroring the reference: upsert_many →
{"upserted": N, "replayed": bool}; search → list of
{chunk, chunk_id, score, recalled_view, revision}; delete_many →
{"deleted": N}; get → record + revision, or None.
Invariants
INV-001 [hard]: Persist verbatim; read only the structural surface. The
whole chunk record is stored + round-tripped byte-equal (semantic round-trip).
The store reads ONLY: the embedding vector (search index), scope keys
(scope_filter), chunk id + revision (optimistic locking), and origin +
injection_source (the consistency rule). Content/distillate + inert fields
(trust_tier/provenance/source_role) are NOT interpreted.
INV-002 [hard]: Idempotency = replay-or-conflict, actor-scoped (same as
affect). idempotency_id = (verb, _ctx_actor(ctx), idempotency_key). Same
digest → replay (replayed: True, no re-write); different digest → raise
IdempotencyConflict. Actor from ctx, never from the record.
INV-003 [hard]: Optimistic locking. When upsert_many carries
expected_revisions, each record's stored revision must equal the expected;
any mismatch → raise RevisionMismatch and the whole batch rolls back. Each
successful upsert increments the chunk's revision.
INV-004 [hard]: Atomic batch.upsert_many applies all records + the
idempotency record in one transaction; on any error nothing is persisted
(no partial batch, no orphaned vec rows).
INV-005 [hard]: Scope isolation.search/scan results are filtered to
records matching scope_filter; a search never returns another scope's chunk.
INV-006 [hard]: Capabilities match implementation (advertise-⇒-implement,
INV-007 upstream). describe_store advertises ONLY what v1 implements:
relational_edges_supported=False, atomic_supersede_supported=False,
transaction_supported=False, optimistic_locking_supported=True,
filterable_metadata_fields=[]. The client gates the gated verbs off these.
INV-007 [hard]: origin == "injected_context" requires injection_source;
a non-injected record carrying injection_source is rejected — both raise
InvalidArguments (mirrors the reference).
INV-008 [hard]: The store is REQUIRED (build_memory_app(store=None)
raises); identity/scope/actor come from ctx, never call args (INV-006 affect-parallel).
Concurrency
SQLite WAL (concurrent readers, single writer). upsert_many/delete_many
serialize on the writer; search/get are concurrent reads. sqlite-vec index
writes ride inside the upsert transaction.
Division of labor (library vs store)
The bifrost library owns the wire (envelope validation, per-dispatch JWT,
scope authorization, error mapping of our typed exceptions, capability
negotiation, routes). This contract owns the store (the basic verbs + SQLite
sqlite-vec persistence/index) + the thin build_memory_provider_app wiring.
sqlite-vec (the vector index extension) loaded into the SQLite connection.
Constraints
[security] Never log chunk content/distillate. Index the vector + scope; don't interpret semantics.
[compatibility] Implement bifrost's MemoryDataStore shape exactly; raise its typed exceptions; never fork the wire. Gated verbs are simply absent + advertised unsupported.
[correctness]search ranking is cosine over the embedding; scope isolation (INV-005) is non-negotiable.
Out of scope (deferred — do NOT flag as drift)
Gated/maintenance verbs:upsert_edges/get_edges_for (relational edges), scan, mark_invalid/mark_superseded, patch_many, atomic_supersede, the lease/checkpoint maintenance plane. All advertised-unsupported or absent in v1.