RAG without
the sync stack.
The row, its dense embedding and its sparse postings commit in one atomic write. Retrieval runs dense and sparse in parallel and fuses by rank, on one snapshot — so a returned vector can never point at a row that no longer matches.
> POST /v1/tenants/:t/vector/search
{
"k": 5,
"dense": { "query": [0.12, …], "metric": "cosine" },
"sparse": { "tokens": { "warranty": 0.81, "claim": 0.63 } },
"where": "tier IN ('premium','enterprise')",
"min_score": 0.7
}
rrf fusion dense ∥ sparse → one ranked list
1 doc_912 dense #1 · sparse #3
2 doc_204 dense #4 · sparse #1
3 doc_338 sparse #2
one snapshot · every hit's row is current Two stores. One source of truth, at most.
A customer updates a row in the relational store. A sync job re-embeds it and writes to the vector store. Between those two writes there is a window where retrieval surfaces the old vector pointing at the new row — and the model composes a confidently-wrong answer from context that was never true.
This is the most common RAG failure mode in production, and it produces the "sometimes the LLM hallucinates" reports postmortems struggle to explain. It's not your model. It's your stack.
One upsert. Three artifacts. Zero pipelines.
Every shape lives on the same substrate — the database indexes the dense vector and the BM25-style sparse postings in the same append that writes the row. The sync job you didn't build can't fall behind.
The row, its dense embedding and its sparse postings arrive in a single request. There is no separate embed-and-forward pipeline to build, monitor, or replay.
row + dense + sparse, one request All three artifacts land on the same write-ahead log in a single append. Either every index sees the new version, or none of them do — by construction, not by best-effort.
one WAL append, all or nothing A concurrent retrieval cannot observe the row without its vector, or the old vector next to the new row. The half-written state is structurally impossible to read.
no observable half-state > POST /v1/tenants/:t/rows/docs
> { "title": "warranty policy v2",
> "body": "…",
> "embedding": […] }
committed atomically
row docs/d_912 ✓
dense hnsw entry ✓
sparse term postings ✓
a concurrent retrieval sees all three — or none Dense and sparse in parallel. Then fuse by rank.
The two retrievers make different mistakes: dense search catches semantic matches the keyword side misses; BM25-style sparse catches exact terms — SKUs, acronyms, product codes — the embedding model never saw. OriginChain runs both in one query and fuses the ranked lists with Reciprocal Rank Fusion, by rank rather than raw score, because the two scores live on incomparable scales.
A SQL where filter rides
the same plan and the same snapshot, and min_score
keeps the floor honest.
How the fusion works →
RRF(d) = Σ 1 / (k + rank_i(d))
i ∈ retrievers
with k = 60 (the Cormack default)
in both lists → rises to the top
in one list → still contributes
k damps low-ranked hits Three guarantees your reranker gets for free.
Row + dense + sparse commit together in one atomic write. Retrieval can't return a vector pointing at a row that no longer matches.
Semantic + BM25-style sparse + a SQL filter in one query. One plan, one consistent snapshot — the filter and both retrievers read the same instant.
Empty result is a real result. Below the threshold, you get nothing back instead of a confidently-wrong nearest neighbour for the model to cite.
/ask orchestrates RAG by default.
If you'd rather not compose the retrieval yourself, /ask runs the whole loop — translate, hybrid retrieve, re-rank, generate — against the same consistent snapshot.
POST the user's question to /ask. A foundation model compiles it to a retrieval plan — the LLM is the compiler, not the runtime, so the plan passes the same security boundary as any other query.
question → compiled plan The plan runs hybrid retrieval — dense and sparse fused by rank — then re-ranks. Every step reads the same consistent snapshot, so the context handed to the model is internally coherent.
hybrid retrieve · re-rank The answer is grounded in rows the engine actually returned. Add ?explain=true to see the exact plan, and override the retrieval plan yourself if the LLM picks wrong.
?explain=true → the plan Retrieval numbers from the published architecture guide.