Local embeddings
2.0 item 5. AI_MEMORY_EMBEDDING_PROVIDER=local runs sentence
embeddings in-process — no API key, no external server, no GPU
required. Pure-Rust BERT inference (candle) with
all-MiniLM-L6-v2 (384-dim), the sentence-transformers workhorse the
comparable memory servers ship by default.
As of 2.0 this is the default: an install with no
embedding_provider configured gets local embeddings automatically —
best-effort. The model downloads in the background on the first start
(hybrid search enables on the next restart), existing pages are
backfilled automatically, and a host that cannot fetch the model (or a
slim build without the feature) simply keeps the FTS-only behaviour
with a warning. Opt out with embedding_provider = "none"; an
explicitly configured provider is never overridden.
# config.toml — all optional as of 2.0
embedding_provider = "local" # explicit: hard-fails if unavailable
# embedding_provider = "none" # opt out of vectors entirely
Why: semantic recall without data egress
Search is hybrid: FTS5 (lexical), entities, graph, and — when an embedder is configured — vectors. The vector stream is what lets "how do we deploy" find the page that says "release procedure": paraphrase recall, no shared keywords required. Before this provider, enabling it cost one of two things:
- an API key (OpenAI / Voyage / Google): per-call spend, and every page body and every search query leaves your machine. For a system whose job is recording everything you do, that is not a small ask;
- a self-hosted engine (Ollama / LM Studio via
openai-compat): keyless, but another server to run, warm, and keep on the same network as ai-memory.
local removes both. Use it when any of these describe you:
- you run the zero-LLM path and want better recall without handing a provider your memory;
- the install is offline or air-gapped (drop the model files in manually — see below);
- a homelab/team server where "one binary, one volume" is the whole operational story and adding an Ollama sidecar just for embeddings is not worth it;
- you want reproducible retrieval: same model files (checksum-pinned), same vectors, forever — no provider-side model deprecations.
Stick with a hosted or self-hosted provider when you already run one happily, want a larger/multilingual model, or want embedding compute off the memory server's CPU.
Why not ONNX?
ONNX is a model format plus a native runtime (onnxruntime) — one
mechanism for local inference, and the one the comparable servers
use. We ship the same model through candle (pure Rust) instead:
identical capability, but no native C++ library to build, license, and
debug across every release target (Windows, macOS, the sandboxed nix
build). If a future model genuinely requires an ONNX-only runtime, the
Embedder trait is where it would slot in.
The model files
The binary does not bundle the model (~87 MB). On the first start with
local configured, the server fetches three files into
<data_dir>/models/all-MiniLM-L6-v2/ — each verified against a sha256
pinned in the source, so a drifted or tampered upstream file fails
loudly instead of silently changing every vector:
| file | sha256 (pinned 2026-09-01) |
|---|---|
model.safetensors |
53aa5117… |
tokenizer.json |
be50c362… |
config.json |
953f9c0d… |
Offline installs: download the three files from
https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/
on any machine and drop them into the directory above; the loader
verifies the same checksums and never touches the network. The model
is Apache-2.0.
Coexistence and migration
Nothing is forced. (provider, model, dim) is stored on every
embedding row, and hybrid search ignores vectors whose triple does not
match the configured embedder — so local vectors sit beside any
provider vectors you already have, and switching back is a config
change. ai-memory embed --force re-embeds a project under the
current provider when you want one consistent set.
Existing installs keep their configured provider; local is opt-in.
Fresh-install defaults are decided by benchmark numbers, not vibes —
see docs/benchmarks/ for the zero-LLM vs local-embeddings
LongMemEval rows, reproducible via:
cargo run --release -p ai-memory-eval -- retrieval --embeddings local
Operational notes
- Inference is CPU, off the async runtime (
spawn_blocking); first-load reads ~87 MB into memory once per server process. - The
local-embeddingscargo feature (default on) carries the ML dependency tree; a slim build can disable it and keep every other provider. - Tokenization truncates at 512 tokens — the model's positional limit; page bodies beyond that contribute their head, same contract as the hosted embedders.