Retrieval
read(entityId, query, options?) returns a MemoryBundle:
const { facts, tasks, events } = await wiki.read('user-123', 'weekend plans');// facts — ranked by relevance (top maxResults, default 10)// tasks — open tasks (pending / in progress)// events — the 10 most recent events, oldest firstPass an empty query ('') to skip search and get the most recently updated facts.
Three retrieval modes
Section titled “Three retrieval modes”| Mode | When it’s used | Best for |
|---|---|---|
| Semantic (vector cosine) | llmProvider.embed is set |
Natural-language questions (“what do I know about X?”) |
| Keyword (FTS5 or MiniSearch) | No embed, offline, or hybridWeight: 0 |
Exact terms, names, IDs |
| Hybrid | hybridWeight between 0 and 1 |
The best of both. 0.7 is a common default. |
| Graph | You call traverseGraph() |
Structural questions (“what’s connected to Alice?”) |
The decision path
Section titled “The decision path”read(entity, query) ├─ hybridWeight === 0 ? ───────────────────────────────► keyword only (no embed call) ├─ no embed() configured ? ────────────────────────────► keyword only └─ embed(query) ├─ throws / bad vector ─► onRetrievalFallback(err) ─► keyword only └─ ok ├─ preFilterLimit set ? ─► keyword top-K → cosine on those K └─ else ─────────────────► cosine on all facts (vectors cached) └─ hybridWeight set ? ─► blend with keyword score : pure semantic └─ fetch full rows for the top maxResults → MemoryBundleA few properties come out of this design:
- It never fails because of the network. A failed embedding falls back to keyword search,
and
onRetrievalFallbacktells you it happened so you can show an “offline” hint. - Two-phase reads. Scoring reads only minimal columns. Full rows are fetched for the winners only.
- Vector cache. Parsed vectors are cached per entity. See Vector cache.
Vector cache
Section titled “Vector cache”Full-scan semantic reads cache the parsed Float32Array vectors in memory, keyed by entity, so a
repeated query doesn’t parse them again.
- Up to 16 entities are cached. When the limit is reached, the oldest-inserted entity is evicted.
- An entity with more than 500 live facts isn’t cached at all, and any existing cache for it is dropped.
- Only single-entity full-scan reads on the built-in cosine path fill the cache. Reads with
preFilterLimit, multi-entity reads, and reads served by avectorRankerdon’t fill it. ApreFilterLimitread still reuses vectors that are already cached. - Any mutation clears it:
runLibrarian,runHeal,runPrune,runReembed,ingestDocument,importDump, andforget. - On memory-constrained runtimes, call
wiki.clearVectorCache()to free it yourself.
Keyword index backends
Section titled “Keyword index backends”The indexStrategy config option picks the keyword engine:
| Value | Behavior |
|---|---|
'auto' (default) |
Uses FTS5 if the SQLite build has it, otherwise MiniSearch. |
'fts5' |
The index is stored inside the database file, so memory use stays flat at any size. setup() throws if FTS5 or json1 is missing. |
'minisearch' |
An in-memory JS index. Memory grows with the total text stored. This was the behavior before 7.11. |
Operational details:
- The first
setup()that adds FTS5 to an existing database rebuilds the index in bounded windows and sweeps, each in its own transaction, so the write lock is not held for the whole rebuild. A rebuild continues across latersetup()andsyncSearchIndex()calls until it finishes. - Under
'auto', a failed FTS5 init is retried once. After that,setup()completes with keyword search unavailable: keyword reads throwWikiSearchUnavailableError. It never quietly switches to MiniSearch. - While search is unavailable,
runHealreturnssearchUnavailable: trueandrunPendingMaintenancelists skipped jobs inperEntity[].searchUnavailable. CallingsyncSearchIndex()with no arguments retries the init, backing off from 5 seconds up to 5 minutes after a failure. - MiniSearch keeps all entry text in the JS heap. The measured ceiling is about 1 million entries
on a default Node heap (
--max-old-space-size).
Tuning knobs at a glance
Section titled “Tuning knobs at a glance”| Option | Default | Effect |
|---|---|---|
maxResults |
10 |
How many facts are returned |
hybridWeight |
unset (pure semantic) | 0 means pure keyword, 1 means all-semantic, values in between blend |
preFilterLimit |
unset | Run cosine scoring only on the top-K keyword matches. Recommended above about 500 facts. |
tokenBudget |
unset | Pack facts to fit about N tokens |
excludeDrafts |
false |
Hide unreviewed draft facts |
asOf |
now | Answer as of a point in time (temporal facts) |
maxResults, hybridWeight, preFilterLimit and excludeDrafts can be set globally in
config or per call in read() options. tokenBudget and asOf are per call only. See
Tune retrieval.