onadit Docs
How it works / How retrieval works
Download

How retrieval works

Onadit looks for a passage 2 ways at once, by keyword and by meaning, puts both on 1 scale, and nudges recent work up. This page gives the formula.

2 lanes#

Every search runs 2 lookups at the same time and merges the results by ID.

LaneHowNeeds
KeywordA full-text index over the archive, scored with BM25Nothing. Always on
MeaningYour question is embedded, then compared with stored embeddings by cosine similaritySearch by meaning turned on

Each lane fetches 3 times the number of results you asked for, and at least 30. A row found by both is marked hybrid.

With Search by meaning off, only the keyword lane runs. The response says "mode": "lexical".

The score#

text
score = 0.55 × similarity
      + 0.10 × recency
      + 0.10 × quality
      + 0.10 × tier
      + 0.10 × 1.0          (reserved for recorded outcomes, fixed today)
      + 0.05 × found-both-ways
      + 0.05 × freshness
TermValue
SimilarityFor a meaning match, the cosine similarity. For a keyword match, its BM25 score divided by the best keyword score, then scaled to the best meaning score in the same pool. For a row found both ways, the larger of the 2
Recency1 / (1 + 0.01 × days). 1.0 today, 0.77 after 30 days, 0.5 after 100
Freshnessexp(-days / 14). 1.0 today, 0.61 after 1 week, 0.37 after 2 weeks, 0.12 after 1 month
Quality1.0, 0.7 or 0.4 for conversations rated high, medium or low. 1.0 for documents and digests
Tier1.0 for current conversations and documents, 1.1 for digests, 0.7 for archived conversations
Found both ways1.3 if both lanes found the row, otherwise 1.0

days counts from the time a row was indexed. For an imported conversation that is the day of the import.

Similarity carries 55% of the score. The 2 time terms together carry 15%, so a recent passage wins a close call and loses to a clearly better match.

After ranking#

  • Near-identical copies of the same conversation are folded into 1 result.
  • Generated reports are placed behind first-hand material.
  • Each result is cut down to a snippet (400 characters) and a summary (800), with the size of the full text and a pointer to open it.

Current and archived conversations#

New conversations land in the current table. rift compact moves those older than 30 days to an archive table and, if you have enrichment on, writes a weekly digest of them. Compaction runs when you call it. It is not scheduled.

A normal search covers documents, current conversations and digests. The archive is searched too in 3 cases:

  • Search by meaning is off.
  • The best result scores under 0.4.
  • An agent asks for it with rift_conversations_search and detail: "full".

When a search is degraded#

"degraded": true means the meaning lane was tried and did not finish, so the results are keyword only. The response gives the reason. The usual one is that embedding the question took longer than 1.5 seconds, the most Onadit will wait.

Search by meaning being off is not a degraded search. It is the keyword mode working as intended.

How a context pack is built#

rift_context_pack runs the same search, then sorts what it finds for an agent that is about to work.

First it reads the task and picks an intent:

IntentWhenEffect
current_truthThe task asks what is true nowLive files and trackers come first
reasoning_archiveThe task asks why something was decidedOlder conversations are treated as valid evidence
blendedBoth, or neitherA mix

Then it gives every source a level of trust:

LevelWhat it isExamples
live_stateThings that can be run or observedSource code, install scripts, package.json, config.json
trackerFiles kept to record statePROJECT_STATE.md, TODO.md, dated decision notes
committed_docDurable written framingREADME, CLAUDE.md, AGENTS.md, a PRD
discussionConversations and agent reasoningAny captured or imported chat
discountedMaterial that may be staleUntracked or deleted files, generated reports

A conversation is never the only authority for what is true now. When the pack has no live_state item to stand on, it says so: "current_truth_caveat": "tracker-backed, not live-verified". When it knows which files would settle the question, it lists them and the agent should read them.

A decision or an example needs a relevance of at least 0.3 to be included. Items are dropped from the end until the whole response fits in max_bytes.

The embedding model#

With a key Onadit providesvoyage-4-lite, 512 dimensions, served by MongoDB Atlas
With your own Voyage keyvoyage-3-lite by default, 512 dimensions. Change it with embedding.model
Sources set to "extraction": "local"An Ollama model on your Mac, 768 dimensions
With no keyNo embedding. Rows are stored without a vector and found by keyword

Embeddings are cached on disk, so the same text is not sent twice.

Last updated 12 October 2026. This page is also available as Markdown, and every page is listed in llms.txt.