---
title: "Hybrid search"
description: "Lexical search always works; semantics and graph boost are optional; results merge through RRF, and without the optional layers, search stays full-text."
---

# Hybrid search

Search in Notarium is built from independent channels that complement one another. Full-text (lexical) search is always available and needs no setup. Semantic (meaning-based) search and awareness of graph links are optional layers on top of it. The channels' results merge into a single ranking, and if the optional layers are unavailable, search keeps working over the full text — without an error.

## Three channels

| Channel | What it does | When it works |
|---|---|---|
| Lexical (FTS/BM25) | Finds exact matches of words and phrases, ranks by BM25 | Always, no setup |
| Semantic (vectors) | Finds what's close in meaning, even without shared words | Optional (`VECTOR_SEARCH`) |
| Graph boost | Pulls in neighbors of a hit via `[[wiki-links]]` | Optional (`GRAPH_BOOST`), off by default |

The lexical channel is the foundation: it works right after startup, with nothing to install. The semantic channel adds an understanding of meaning (synonyms, paraphrases), and graph boost adds knowledge of the links between notes.

## How results merge

The channels are combined by the **RRF** algorithm (Reciprocal Rank Fusion, k=60): each channel produces its own ranked list, and RRF folds them into a shared ranking by position rather than by incomparable "raw" scores. That way a lexical match and semantic closeness are weighed together. Merging happens at the note level — by its stable identifier.

```mermaid
flowchart LR
  q([Query]) --> fts[Lexical · FTS/BM25]
  q --> vec[Semantic · vectors]
  q --> gr[Graph · 1-hop]
  fts --> rrf{RRF fusion}
  vec --> rrf
  gr --> rrf
  rrf --> res([Results])
```

## If semantics are unavailable

The semantic layer may be turned off or unavailable: `VECTOR_SEARCH` isn't enabled, the vector stack isn't installed, the model didn't load, or computing the query vector timed out. In any of these cases the query is served by the lexical channel — search always answers and doesn't throw an error just because semantics are missing.

> [!note] Semantics are opt-in
> Vector search pulls in a heavy native stack (~660 MB on disk) and loads the embedding model into memory (hundreds of MB of RAM), so it's off by default in the public image. How to turn it on and which model to choose is covered in [Search setup](/docs/self-hosting/search-setup/).

## Where you search

- **Spotlight / OmniSearch** — a fast, ranked jump to a note. The full hybrid works here: relevance matters, not just an exact word match. See [Search and Spotlight](/docs/guides/search-spotlight/).
- **Feed filter** (`?q=` in [the Feed](/docs/guides/feed/)) — a lexical filter over the corpus: it keeps notes where the terms actually appear, and combines with folder, tag, and date filters.

## The index and its rebuild

The search index is derived knowledge: it's built from files and fully recoverable by a rescan. One index per space. Switching the embedding model isn't a migration but a rebuild of the vector part from scratch. The initial build of the vector index on a large corpus takes minutes, or even hours; during that time the interface shows a "Building search index…" indicator, while search itself stays available in lexical mode.

Related topics: [Knowledge graph](/docs/concepts/knowledge-graph/) — where the third channel comes from, [Search setup](/docs/self-hosting/search-setup/) — enabling semantics and choosing a tier, [File-first](/docs/concepts/file-first/) — why the index is recoverable.
