Markdown search forces a bad choice: stand up a server, or grep and miss.

Full-text engines like Tantivy and Meilisearch are excellent, and they bring a deployment: an index server, a process, sometimes a port. That is overkill for a few thousand files on the user's own disk. A plain grep needs no deployment and fails the moment the query and the document's wording diverge, which is most of the time once notes are written in human language across years. ae-vx is the in-between: small enough to embed without changing how a tool ships, capable enough that a paraphrase still lands on the right document.

One crate, two builds, no bundled weights.

The same crate compiles full-hybrid or lexical-only; feature flags decide whether any machine-learning machinery is linked at all.

Anatomy ae-vx
01 Runtime
Rust 2024; single crate, library plus one CLI binary
02 Default model
the embedding model fetched once, SHA-pinned: ~86 MB, then every call a cache hit
03 Lean build
feature flags drop the embedding runtime, tokenizer and download for ML-free consumers
04 Chunking
split at headings; each hit carries its breadcrumb chain and byte range
05 Tagged scope
narrow a query to one project; a file's frontmatter wins on conflict
06 On-disk index
a versioned binary index; refuses to load stale rather than rebuild silently

Index once, rank two ways, all in-process.

One orchestrator. Indexing chunks each file at heading boundaries and feeds both rankers; a query dispatches by mode. No server answers it. The call is the search.

Query → ranked hits ae-vx / search
Query string one corpus, indexed once at heading boundaries
Lexical · word-match
01 Tokenize
split on word boundaries · lowercase · stem to root forms
02 Score
term frequency over the index, length-normalised
Semantic · vectors
01 Embed
the query becomes a vector through the embedding model
02 Cosine
flat brute-force similarity over the chunk vectors
Rank fusion both lists merged by rank; each hit keeps its lexical and semantic position

Three search modes over one index, embedded in-process.

The same index answers lexical, semantic, or hybrid on a per-query basis, with no rebuild. What a consumer gets: capable search added as a dependency line, no server and no port.

What it gives you ae-vx
Three modes
Word-match, meaning-match, or both, per query.
Embed once
The model fetched once, SHA-pinned.
Lean build
Flags drop the ML runtime when unused.
Heading chunks
A hit is the section, with its breadcrumb.
Versioned index
Refuses to load stale, never rebuilds silently.
In-process
No server, no port. The call is the search.
Tagged scope
Narrow any query to one project.

A paraphrase still lands on the right file.

Search encrypt user credentials at rest over notes worded another way. A literal match returns an empty page: not one of those words is in the file. The note is there, about sealing secrets before they hit disk, and ae-vx surfaces it on meaning rather than characters.

What it changes ae-vx
Without it
Stand up and manage a search server, or grep and miss the moment the wording diverges from the note.
With ae-vx
A paraphrase lands on the right document in-process, and a literal-match query for 'encrypt user credentials at rest' that returns nothing is found on meaning.
work