Search by meaning
Search by meaning is off by default. With it on, search also finds notes that share no word with the query: inloggning finds the note about the OAuth flow, and an English query reaches a Swedish note. How search works describes the results.
It needs the semantic extra (the fastembed and numpy packages) and VAULT_SEMANTIC=on. With VAULT_SEMANTIC=on and the extra missing, the server logs one line saying so and searches by words.
Turn it on
Docker Compose
The -semantic image tag carries the extra; the plain tag leaves it out. Create compose.override.yml beside compose.yml:
services:
vault:
image: ghcr.io/gronare/mindin:v0.14.0-semantic
environment:
VAULT_SEMANTIC: "on"
mem_limit: 3g
Then docker compose up -d. Compose reads the override on top of compose.yml, so fetching a new compose.yml keeps it; change the tag here when you upgrade.
Kubernetes
Set the image in deploy/k8s/deployment.yaml to the -semantic tag, add VAULT_SEMANTIC: "on" to the Secret, and raise the container's memory limit to at least 3Gi.
One machine, without a server
claude mcp add vault -s user \
-e VAULT_PATH=$HOME/vault \
-e VAULT_SEMANTIC=on \
-- uvx --from 'mindin[semantic]' mindin
Any client works the same way: run mindin[semantic] and set VAULT_SEMANTIC=on.
The model and Hugging Face
The model turns each section of a note into a vector. It comes from Hugging Face, a public hub where open machine-learning models are published, and runs on your machine through fastembed and ONNX Runtime.
- Downloaded once. The first time the model is needed, the server downloads it into
VAULT_CACHE_DIR/models(/data/cache/modelsin the image). fastembed puts the size of the default model at 1.0 GB and of the smaller one at 0.22 GB. Later starts load it from that folder. - No account, no token, no note text sent. The download is a plain fetch of public files. Your notes are never sent anywhere: the vectors are computed on the machine and stored in its index.
- Without internet on the first start, loading the model fails. The server logs
embedding notes failed, searching by wordsonce, keeps answering by words, and tries the model again on later refreshes; once it loads, the notes are embedded. - A machine that is always offline can be given the model by copying the
modelsfolder from a machine that has downloaded the same model into its ownVAULT_CACHE_DIR/models. fastembed looks there before it reaches the internet.
Pick a model
VAULT_SEMANTIC_MODEL takes a fastembed model name.
| Model | Download | Notes |
|---|---|---|
sentence-transformers/paraphrase-multilingual-mpnet-base-v2 |
1.0 GB | The default. Multilingual. |
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 |
0.22 GB | About half the memory; finds fewer notes by meaning. Multilingual. |
These two are tested, and each has its own similarity cutoff below which a section does not count as a match. Any other fastembed text model loads with a generic cutoff of 0.3 and is untested here. Pick a multilingual model when your notes are not all in English.
Changing the model drops every vector and embeds every note again with the new one. Every session on a machine should name the same model, since a session asking for another one restarts the shared model process with its own.
Memory and cost
Measured on an M2 Pro with a vault of 500 notes in 3,300 sections:
| mpnet (default) | MiniLM | |
|---|---|---|
| Disk | 1 GB for the model, about 4 KB per section in the index (14 MB here) | 250 MB for the model, about 2 KB per section (7 MB here) |
| Shared process | 1.1 to 1.7 GB just after it starts, about 500 MB once the system has paged out what searches do not touch | About 900 MB just after it starts, about 160 MB later |
| Each session's MCP server | About 35 MB more than searching by words | About 25 MB more |
| First sync | About four minutes (14 sections a second) | About a minute (60 sections a second) |
| Query | About 23 ms, against 11 ms by words alone; about 50 ms each with six sessions searching at once | About 20 ms; about 33 ms with six at once |
On a server the model process runs in the server's container, and the first sync is when it uses the most memory: loading mpnet peaks near 2 GB by itself, and embedding stays at about that peak with the defaults of VAULT_EMBED_BATCH and VAULT_EMBED_MAX_TOKENS. Give the container a memory limit of at least 3 GiB with mpnet; below that the kernel kills the process, and the server with it, while it embeds. A restarted server goes on from the notes that already carry vectors. Lowering VAULT_EMBED_BATCH lowers the peak; see Configuration.
mindin sync embeds whatever is pending and says how many notes carry vectors; sync --rebuild computes them all again.
The model in its own process
The model can run as a process of its own, outside the server. The server then restarts in seconds and stays near the memory of searching by words, because it never loads the model; the model stays loaded across server restarts. Run the model:
VAULT_EMBED_TOKEN=<a long random secret> mindin embed-worker --listen 10.0.0.5:7400
and point the server at it:
VAULT_SEMANTIC=on
VAULT_EMBED_URL=tcp://10.0.0.5:7400
VAULT_EMBED_TOKEN=<the same secret>
- Note text crosses the network unencrypted. Every section the server embeds and every query reaches the worker in plaintext, also from a sealed vault. Run the worker only on a private network: one host, a WireGuard link, or a cluster network that a NetworkPolicy closes to everything but the server. Nothing in the worker encrypts the connection.
- The token is required beyond loopback.
--listendefaults to127.0.0.1:7400, whereVAULT_EMBED_TOKENis optional. On any other address the worker refuses to start without one, and answers no request that lacks it. - The worker runs until stopped. It loads the model before it accepts connections, stays up while idle and stops on
SIGTERM; a server cannot stop or restart it. It takesVAULT_SEMANTIC_MODEL, or--model, and keeps the model in its ownVAULT_CACHE_DIR/models. - The server names the worker's model. A server whose
VAULT_SEMANTIC_MODELdiffers from the worker's gets no vectors, logs the mismatch once and searches by words. A server of another mindin version uses the worker as long as the model and the worker's protocol match. - Without
VAULT_EMBED_URLthe server starts its own model process, as above.
The server then needs numpy but not fastembed or the model files, and its memory stays near searching by words; the worker's machine needs the memory listed above.
Where the vectors live
Each vault has its own index, VAULT_CACHE_DIR/<id>.sqlite, holding every note's text and its vectors in plaintext. The model process is shared by every vault and session on the machine and stores nothing. On a server, whose vaults are sealed in storage, put VAULT_CACHE_DIR on an encrypted volume.