ADR-003 — Semantic (vector) time travel
Status: Proposed 2026-06-15. Phase 0 (verification) targeted for the 8.0.0/3.0.0 lockstep release; Phases 1–2 for the immediate follow-up (8.1/3.1); Phase 3 is a research track.
Related:
ADR-002 — DiskANN vector index
docs/snapshot-safety.md— the shadow-page generation substrate this builds onGraph time-travel generation-threading (cor 3.0 / brainy 8.0
getVerbEndpointsAtGeneration)
Context
Brainy 8.0's db.asOf(g) returns the database as it existed at generation g. For a triple-intelligence find() (metadata filter ∩ graph hop ∩ vector similarity), every substrate must answer "what did you look like at g."
Two of the three are solved by the shadow-page generation substrate: the metadata LSM and the graph endpoints store record brainy's generation per write and resolve point-in-time reads from a version chain. The id-mappers are monotonic. So the existence dimension — which entities and edges were present at g — is correct.
The vector/semantic dimension is the hard one, and it decomposes into three sub-problems:
Candidate set at
g— which entities existed. Already solved (the versioned substrates above).Vector values at
g— an entity re-embedded after an edit has a different vector now than atg. The historical value must be recoverable.Index structure at
g— HNSW/DiskANN build a navigation graph. The current graph differs from the one atg.
Plus a fourth, cross-cutting limit:
Embedding-model drift — the query is embedded with the current model. If the embedding model changed between
gand now, comparing a current-model query against old-model data vectors is a comparison across different vector spaces — silently wrong unless detected.
Versioning a million-node ANN graph per generation is prohibitive, which is why no standalone vector database offers semantic time travel. This ADR records a strategy that does, without paying that cost in the common case.
The key insight
In a triple-intelligence query the vector leg rarely runs alone — it runs over the intersection of the metadata and graph filters, which are already time-travel-correct and usually small. Over a candidate set of hundreds (not millions), an exact rerank over the entities' gen-g vectors gives perfect recall in sub-millisecond time, with no historical ANN index at all. The other two intelligences pre-pay the cost of vector time travel.
The hard residue is only the unfiltered case (pure semantic search over everything, as of g), which is where the index itself must time-travel.
Decision
A tiered strategy on the existing generation substrate, selected automatically by the AdaptiveDiskAnnModeSelector on (filter selectivity × history depth):
Regime | Mechanism | Recall | Cost |
|---|---|---|---|
Filtered, any depth (common) | Exact rerank over the pre-filtered candidate set's gen- | Exact | Sub-ms (small set); no new index |
Unfiltered, recent | Differential overlay: current index + visibility bitmap + sparse per-node vector version chain (only re-embedded nodes); navigate current graph, mask future nodes, evaluate gen- | Near-exact | One index; near-zero overhead |
Unfiltered, deep | LSM-tiered shadow-page ANN: immutable index segments at generation checkpoints + deltas; search the segment(s) covering | Exact (segment) | Bounded per-segment; pay-per-use storage |
Cross-cutting | Embedding-model versioning: stamp each vector with its model; auto-project the query into the historical space, or hard-error — never silently wrong | — | Detection is O(1) |
Options considered (and why not, alone)
Exact rerank over the pre-filtered window — chosen for the filtered case; insufficient alone (unfiltered queries fall through).
LSM-tiered / shadow-page ANN — chosen for the unfiltered-deep case; reuses the proven base+delta+compaction primitive so the vector index joins one unified MVCC substrate. Cost: search amplification across segments.
Generation-tagged Vamana + delta-replay — exact historical structure, but Vamana's robust-prune rewires many neighbors per insert → fat delta logs; folded into (2) as the segment-internal representation rather than a standalone mode.
Persistent / copy-on-write immutable ANN (structural sharing, à la Datomic) — elegant and instant
asOf, but ANN's high fan-out + rewiring limits sharing and CoW pointer-chasing fights the cache locality ANN depends on. Research track (Phase 3).Differential overlay — chosen for the unfiltered-recent case; structure is current (approximate recall for deep history), which is why it is scoped to recent generations.
Time as a first-class ANN filter (
[birth_gen, death_gen]predicate) — composes with our metadata/graph filters and is how the candidate set is computed; suffers known recall cliffs at extreme selectivity, so it feeds the rerank/overlay rather than standing alone.Embedding-model re-projection — chosen as the cross-cutting correctness layer; the alignment map (Phase 3) is research-grade, but the detection of model drift (Phase 1) is cheap and ships early.
Open-core behavior (brainy alone vs brainy + cor)
The API and correctness are identical; only speed and scale differ — the open-core contract.
brainy alone (MIT):
db.asOf(g).find()is correct via brainy's canonical-record materialization — the historical candidate set is rebuilt from versioned records and reranked in JS. Slower (brute-force, no native acceleration), bounded to the JS-feasible scale (~10⁵–10⁶), but semantically correct.brainy + cor: the candidate set comes from cor's versioned native substrates, the rerank is native, the unfiltered-deep path uses the tiered shadow-page ANN, and scale extends to 10⁸–10¹⁰. Same
asOf(g).find()call; faster and larger.
This means semantic time travel is a brainy feature that cor accelerates — not a cor-only capability.
Phasing
Phase 0 — Verify + document (this release, 8.0/3.0). ✅ VERIFIED — see the guarantee + limits below. A cross-layer test (
src/native/semanticAsOf.test.ts) builds entities, re-embeds one across generations, then assertsasOf(g).find({vector, where})returns only the g-valid set, ranked by the at-g embedding, with thewherefilter seeing the at-g metadata. It passes (incl. a leak-detector that fails if the vector leg used the live index). No new critical-path code.
Phase 0 — verified guarantee + its limits (be honest in GA messaging)
Guarantee (verified, 8.0.0-rc.2 + cor 3.0): db.asOf(g).find({ vector, where }) is set-correct, rank-correct (at-g embedding), and filter-correct (at-g metadata) — for mutations committed via brain.transact([...]).
Limits — all four are real; do not claim past them:
~~
transact()-only.~~ RESOLVED before GA — brainy's Model-B landed in 8.0.0-rc.3 (5c3bb2c): single-opadd()/remove()writes ARE retained (per-writepersistSingleOp/commitSingleOpgeneration-stamping), soasOf(g)is history-correct for BOTHtransact([...])and single-op mutations. Regression-pinned insrc/native/semanticAsOf.test.ts(runs green in the release gate vs the published 8.0.11). The rc.2-era gap this bullet used to describe no longer exists in any shipped 8.x.The historical leg is brainy-JS-served this release.
asOf(g)builds an ephemeral reader brain and rebuilds a JS HNSW from the at-g vectors; cor's native vector index (NativeDiskAnnWrapper) is not on the historical path. cor accelerates only the live (now) leg in 3.0. (Open-core: brainy-alone is correct via JS rerank.)O(n-at-g) rebuild per
asOfDb — small/medium scale only. OneaddItemper at-g vector into a fresh JS HNSW; this OOMs at billion-scale. Billion-scale historical semantic find is Phase 1–2 (cor native rerank / overlay / tiered ANN; #35). Phase 0 is correctness, not scale.No embedding-model-drift guard yet. Comparing a query embedded with model vN against stored vectors from model vM is silently wrong across a model upgrade — detection lands in Phase 1, alignment in Phase 3.
Phase 1 — Native-accelerate + drift detection (8.1/3.1). Cor native exact-rerank path; embedding-model version stamping + hard-error on cross-model comparison (never silently wrong).
Phase 2 — Unfiltered paths (8.1/3.1+). Differential overlay (recent) + LSM-tiered shadow-page ANN (deep), lazily materialized + cached.
Phase 3 — Research track (lands when ready). Persistent/CoW ANN; learned cross-model re-projection (semantic time travel that survives embedding-model upgrades).
Consequences
Semantic time travel ships correct-first (Phase 0 verifies the realistic query), then fast (Phase 1+), rather than waiting on a versioned ANN index that may never be worth its cost.
The vector index becomes a first-class citizen of the same generation substrate as metadata and graph — one MVCC clock across all three intelligences.
The honest limit (embedding-model drift) is made explicit and, in Phase 3, solvable — rather than ignored.
Division of labor
brainy:
asOfrouting (when to materialize vs serve native); canonical-record retention of per-generation vectors; embedding-model version stamping in the record; the open-core JS rerank path.cor: native exact-rerank; the differential overlay; the LSM-tiered shadow-page ANN; mode selection by selectivity × depth; the model-drift detector + (Phase 3) re-projection.