Embeddings & Vector Stores
How Sophon turns memories, daily logs and documents into vectors for semantic search, with a local-first embedder, an embedded SQLite vector store, admin consent for cloud embedders, and a background reindex.
Semantic recall needs two things: an embedder (one model provider that turns text into vectors, chosen for the whole host) and a vector store (where the vectors live). Memory search, document search and document Q&A use them to find text by meaning, not only by keyword.
Neither is required. Without them, memory and document search run on keywords alone. Since v2.1.0, a failing or slow embedder or vector store no longer fails a chat turn, document search, document Q&A or memory.search: each semantic lookup has an 8-second deadline and then falls back to keyword results. The database is the source of truth, and vectors are a cache that Sophon can always rebuild.
What changed in v2.1.0
- A vector store out of the box. Personal, native and Desktop installs now get an embedded SQLite vector store. Before, they had none unless you ran Qdrant.
- A local embedder first. Sophon looks for a local embedding model (for example
nomic-embed-texton Ollama) and prefers it over a cloud provider. It can installnomic-embed-textfor you with one click. - Consent before a cloud embedder. A cloud embedder that has not produced vectors yet sends nothing until an admin consents.
- No more vector wipes. Each embedder writes to its own collections, so switching embedders never mixes or deletes vectors.
- A self-healing index. A background reindex fills in what is missing, for example after an outage, a new embedder, or a restore.
You still need an embedder for semantic recall. Until one is ready, the status shows why (no local model found, waiting for consent, or off) and search uses keywords.
What gets embedded
| Content | Notes |
|---|---|
| Memory entries | Every user's memories, in every tenant |
| Daily logs | The last 30 days by default for a reindex or a consent preview (--log-days, 1 to 365) |
| Documents | Processed document chunks |
Embedding calls count against the provider's budget and appear in Usage under the background source embeddings.
The vector store
The store is chosen once at startup by Sophon:Vectors:Provider (restart required):
| Value | Store |
|---|---|
auto (default) | Qdrant when Qdrant:Endpoint is set, else the embedded SQLite store |
qdrant | Qdrant at Qdrant:Endpoint (an empty endpoint stops the boot) |
sqlite | The embedded store at <dataDir>/vectors/vectors.db, created on the first write |
none | No store: keyword-only search |
Embedded SQLite
The SQLite store is one file in WAL mode. Vectors are stored normalized, and each search scans the matching points, up to the newest Sophon:Vectors:Sqlite:MaxScanPoints (default 200,000). Sophon:Vectors:Sqlite:MmapMegabytes (default 256) sizes its memory-mapped I/O. Both need a restart.
SQLite is single-node and brute-force. Several Gateway replicas cannot share the file, so a multi-replica deployment needs Qdrant (the Gateway logs a warning when sqlite is combined with Sophon:Database=postgresql). Searches that hit the scan cap only see the newest points and are counted as truncatedScans in the status. Move to Qdrant above about 100,000 points.
Qdrant
Pro and Enterprise compose files use Qdrant. Qdrant:Endpoint (Qdrant__Endpoint, outside the Sophon section) is the gRPC URL:
- A URL without a port now means
6334. An explicit:6333(the REST port) logs a warning. - A value with no scheme is read as
http://. Any scheme other thanhttporhttpsstops the boot, and so does a URL with a user name and password in it. Qdrant:ApiKey(Qdrant__ApiKey) is new. It is sent with every call to a secured Qdrant or Qdrant Cloud and is never logged. Using it over plainhttp://to a non-local host logs a warning; usehttps://off a private network.
Either store has deadlines: 5 seconds for a search, 10 seconds for a write. After a failed search the store is skipped for a cool-down (30 seconds, doubling up to 10 minutes; 5 minutes after an authentication failure). Writes skipped during that window come back with the next reindex.
The embedder
Choosing the embedder
Sophon:Embeddings:Mode picks how the host embedder is chosen. It applies without a restart.
| Mode | Dashboard | CLI | Behavior |
|---|---|---|---|
auto (default) | Automatic (local first) | sophon models embeddings auto | Keep the current choice. With none, a verified local embedder comes first, then a cloud provider with a key, which waits for consent. |
local | Local only | sophon models embeddings local | Local embedders only. A cloud embedder is never used. |
provider | Specific provider | sophon models embeddings use <providerId> | Exactly Sophon:Embeddings:ProviderId (with Model and Dimensions), never another. |
off | Off | sophon models embeddings off | No embedding: keyword-only search. |
PreferLocal (default true) ranks local candidates first under auto.
The choice is sticky. Sophon never switches embedders by itself. If a better local embedder appears later, it is offered as a suggestion (Use it in the Dashboard, or sophon models embeddings use <providerId>). The choice changes when you change the mode or the selection settings, when its provider is removed or disabled, or, under local, when it is not local. It is stored in <dataDir>/config/embedder-state.json, which is backed up.
Sophon probes local servers in the background for installed embedding models: every 5 minutes while a local candidate is pending, otherwise every 30 minutes, and at once after a provider is added, changed, enabled, disabled or removed, after a change to an embeddings setting, or after sophon ops action reload-runtime. Embedding models do not need to be listed on the provider or have a context window declared.
Supported embedders
| Provider type | Default model | Counts as |
|---|---|---|
ollama | nomic-embed-text | Local, unless its URL is ollama.com |
lmstudio | none (name one) | Local |
custom-openai | none (name one) | Local when its URL is local |
azure-openai | none (name one) | Cloud |
sophon-forge | andora-embed | Cloud |
openai | text-embedding-3-small | Cloud |
gemini | gemini-embedding-001 | Cloud |
mistral | mistral-embed | Cloud |
together | BAAI/bge-large-en-v1.5 | Cloud |
nvidia-nim | nvidia/nv-embedqa-e5-v5 | Cloud |
Any other provider type cannot embed. nomic-embed-text has 768 dimensions and a 2,048-token input window. Inputs are cut to the model's input window (about 3 characters per token), and large batches are split per provider.
Set up local-first embeddings
1. Get a local embedding model
On the Ollama host, or let Sophon pull it through a configured Ollama provider (about 274 MB, stays on that machine):
ollama pull nomic-embed-text
# or
sophon models embeddings install-local --waitIn the Dashboard, Settings → Models → Embeddings → Install nomic-embed-text does the same. Only one install runs at a time.
2. Keep a local provider configured
An Ollama, LM Studio or local OpenAI-compatible provider under Settings → Models. Add one with Add provider or sophon models add if you have none.
3. Pick one mode
sophon models embeddings auto # local first (the default)
sophon models embeddings local # never use a cloud embedderWith no embedder chosen yet, auto picks the verified local one. If a cloud embedder is already chosen, the local one is offered as a suggestion; switch with Use it or sophon models embeddings use <providerId>.
4. Check
sophon models embeddings statusThe Dashboard reads Active via <provider> · <model>. The background reindex then builds the index by itself.
Consent for cloud embedders
A cloud embedder (Sophon Forge, OpenAI, Gemini, Mistral, Together, NVIDIA NIM, Azure OpenAI, or a custom OpenAI-compatible server on a public address) that has not produced vectors yet embeds nothing until an admin of the default tenant consents. Until then the status reads awaitingConsent, and the admin can see how many memories, daily logs and documents it would receive.
Consent is recorded by:
- the Confirm… banner in Settings → Models → Embeddings ("Save and allow", "Allow and build the index");
- saving that provider in the Embeddings section, or
PUT /api/models/embeddings/settingsnaming it; sophon models embeddings use <providerId> --yesorsophon models embeddings consent --yes;POST /api/models/embeddings/consent.
A cloud embedder's first full index also needs a confirmed start (--yes, --reindex, startReindex=true, or Reindex…). Consent and warm-up send only fixed probe texts, never your data.
Choose a cloud embedder and build its index in one step (a test embed runs first):
sophon models embeddings use openai-1a2b3c4d5e6f7a8b9 --model text-embedding-3-small --yesOr, when a cloud embedder is already waiting (status awaitingConsent):
sophon models embeddings consent # shows the counts, then asks
sophon models embeddings consent --reindex --yesUpgrades from v2.0 on Qdrant keep their embedder. If your v2.0 vectors were written by a cloud embedder (for example Sophon Forge or OpenAI), that embedder is adopted on upgrade and counts as consented, so embedding to it continues without a new consent step. An embedder that already wrote vectors into its own collections is always treated as consented.
Local-only chats
A local-only chat never calls a cloud embedder. While the host embedder is a cloud one, its recall is keyword-only, and documents it stores are flagged so a cloud embedder's background reindex skips them (an admin rebuild still re-embeds them). Its memories are not flagged: they are embedded later by the host embedder. To keep them on the machine, choose a local embedder (sophon models embeddings local).
Collections and switching embedders
Each embedder writes to its own collections, named after a fingerprint of providerType:model:dimensions:recipe, for example memory__1a2b3c4d and documents__1a2b3c4d. The provider id is not part of it, so re-adding the same provider keeps its vectors. Switching embedders never mixes or wipes vectors, and switching back reuses the earlier collections and only catches up what they missed.
Upgrading from v2.0 on Qdrant. v2.0 wrote the bare memory and documents collections. On first start, Sophon finds the v2.0 embedder whose vector size matches and aliases its fingerprinted names to the bare collections. Nothing is deleted or recreated, and the bare collections stay for a downgrade. Tools that read Qdrant directly must use the fingerprinted names.
Earlier embedders' collections stay until an admin deletes them (Delete on an inactive collection, or DELETE /api/models/embeddings/collections/{name}). The current, pending and recorded embedders' collections cannot be deleted.
Reindex and rebuild
Background reindex. About once a minute (the first check 30 seconds after start), Sophon checks for missing vectors. With a local embedder it builds a new embedder's index, refills a deleted or restored vectors.db, embeds documents processed while embeddings were down, and catches up on memory written during an outage. A cloud embedder's first index never starts by itself. Failures back off from 5 minutes up to 60.
sophon models embeddings reindex --wait --yes # start (and follow) the backlog
sophon models embeddings reindex --cancel # stop itSophon:Embeddings:AutoReindex=false makes every reindex manual. The existing Sophon:Documents:AutoEmbed=false now also keeps the background reindex away from documents.
Admin rebuild. Drops and recreates the current embedder's two collections and re-embeds every user of every tenant in the background. Only one runs at a time, and its progress is kept in memory, so a restart during a rebuild needs another run.
sophon models embeddings rebuild --yes --log-days 30Without --yes, rebuild asks in an interactive terminal and fails in a script.
Your own vectors. Re-embed your own memories, daily logs and documents:
sophon memory reindex --vectors --log-days 30The bare sophon memory reindex still rebuilds the memory graph, as before.
Backup and restore
vectors/vectors.db and its -wal, -shm and -journal files are a cache. Dashboard backups, sophon backup and sophon snapshot leave them out. sophon restore and sophon snapshot-restore delete them (on Windows, stop the Gateway first), and the background reindex rebuilds the store from the restored data. For an embedder that had built its index before, that rebuild runs by itself at that provider's price. Back up Qdrant separately with Qdrant snapshots.
A damaged vectors.db is fixed by stopping the Gateway, deleting vectors/vectors.db* and starting again.
Status
GET /api/models/embeddings/status (and sophon models embeddings status, the Dashboard card and sophon health) report what actually works:
| Field | Values |
|---|---|
vectorStore | sqlite, qdrant, none |
state | ready, resolving, awaitingConsent, off, degraded (the reason field says why) |
vectorStoreState | ready, breakerOpen (skipped during a cool-down), none |
semanticSearchAvailable | Whether semantic search runs right now |
suggestion | A better local embedder you can switch to |
reindex, rebuild | Background reindex and admin rebuild progress |
Any signed-in user can read the status. Error text, counts and candidates are shown only to an admin of the default tenant. Dashboard terminal members may run sophon models embeddings status, which is on the default allowlist.
CLI reference
| Command | What it does |
|---|---|
sophon models embeddings status | Embedder, vector store and whether semantic search runs |
sophon models embeddings auto [--prefer-local|--no-prefer-local] | Automatic selection, local first |
sophon models embeddings local | Local embedders only |
sophon models embeddings off | Keyword-only search |
sophon models embeddings use <providerId> [--model m] [--dimensions n] [--no-test] [--reindex] [--yes] | Choose a provider; a test embed runs first unless --no-test |
sophon models embeddings consent [--reindex] [--log-days n] [--yes] | Review and consent to a waiting cloud embedder |
sophon models embeddings install-local [model] [--provider id] [--wait] | Pull nomic-embed-text (or another model) on a configured Ollama |
sophon models embeddings test [providerId] [--model m] [--dimensions n] | Embed one fixed text and report the result |
sophon models embeddings reindex [--wait] [--yes] [--log-days n], reindex --cancel | Start, follow or cancel the background reindex |
sophon models embeddings rebuild [--yes] [--log-days n] | Admin rebuild of the whole index |
sophon memory reindex --vectors [--log-days n] | Re-embed your own memories, logs and documents |
All except status and test need an admin of the default tenant. With --json, stdout carries only the Gateway's JSON; notes and errors go to stderr.
REST API
| Endpoint | What it does | Access |
|---|---|---|
GET /api/models/embeddings/status | Embedder and store status | Any signed-in user |
PUT /api/models/embeddings/settings | Save mode, providerId, model, dimensions, preferLocal, test, startReindex | Admin |
POST /api/models/embeddings/test | Embed one fixed text (a failure is 200 with ok: false) | ManageModels |
GET/POST /api/models/embeddings/consent | Preview (?logDays=) and record consent (?providerId=&startReindex=true) | Admin |
POST/DELETE /api/models/embeddings/reindex | Start (?confirm=true for a cloud embedder) or cancel the backlog | Admin |
POST /api/models/embeddings/rebuild | Admin rebuild (?logDays=30); 409 rebuild_running while one runs | Admin |
POST /api/models/embeddings/install-local | Pull a local embedding model on Ollama; progress in the status's install field | Admin |
GET /api/models/embeddings/collections, DELETE …/collections/{name} | List and prune collections | Admin |
"Admin" means ManageModels and an admin of the default tenant. Gateways that support these routes advertise embeddings.v1 in GET /api/system/info. GET /api/memory/search now also returns semanticAvailable and semanticError.
AUTH='Authorization: Bearer <admin token>'
# choose a provider; the test embed runs first, and 422 embedding_test_failed saves nothing
curl -X PUT "$GW/api/models/embeddings/settings" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"mode":"provider","providerId":"ollama-7f3c2a9b1d4e5f6a7","model":"nomic-embed-text"}'
# what a waiting cloud embedder would receive, then consent and start its first index
curl -H "$AUTH" "$GW/api/models/embeddings/consent?logDays=30"
curl -X POST -H "$AUTH" "$GW/api/models/embeddings/consent?startReindex=true"
curl -H "$AUTH" "$GW/api/models/embeddings/status"Saving settings from the Dashboard, the CLI or PUT /api/models/embeddings/settings writes Sophon:Embeddings to <dataDir>/config/appsettings.user.json, which overrides SOPHON__Embeddings__* environment variables from then on.
Configuration
All Sophon:Embeddings keys apply without a restart. Sophon:Vectors and the root Qdrant keys need a restart.
| Key | Default |
|---|---|
Sophon:Embeddings:Mode | unset (auto) |
Sophon:Embeddings:ProviderId, Model, Dimensions | unset (the provider type's default model and size) |
Sophon:Embeddings:PreferLocal | true |
Sophon:Embeddings:AutoReindex | true |
Sophon:Embeddings:QueryTimeoutSeconds, BatchTimeoutSeconds, VectorReadTimeoutSeconds | 8, 60, 8 |
Sophon:Embeddings:QueryRetries, BatchRetries, RetryBaseDelayMilliseconds | 1, 2, 250 |
Sophon:Embeddings:BreakerFailureThreshold, BreakerBaseCooldownSeconds, BreakerMaxCooldownSeconds, BreakerPermanentCooldownSeconds | 3, 30, 600, 300 |
Sophon:Vectors:Provider | auto |
Sophon:Vectors:Sqlite:MaxScanPoints, MmapMegabytes | 200000, 256 |
Qdrant:Endpoint, Qdrant:ApiKey | empty |
After BreakerFailureThreshold failures in a row, the embedder is skipped for the base cool-down, doubling up to the maximum; an invalid key or missing model skips it for the permanent cool-down. Embedding calls are retried with backoff on 429, 408, 5xx and network errors. A retry count of 0 turns retries off; for the timeout and breaker keys, 0 or less means the default.
{
"Sophon": {
"Vectors": { "Provider": "auto" },
"Embeddings": { "Mode": "local", "PreferLocal": true, "AutoReindex": true }
},
"Qdrant": { "Endpoint": "http://qdrant:6334", "ApiKey": "" }
}As environment variables: SOPHON__Vectors__Provider=sqlite, SOPHON__Embeddings__Mode=local, Qdrant__Endpoint=http://qdrant:6334.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
State awaitingConsent ("Waiting for consent to use …") | A cloud embedder was chosen and not allowed yet. An admin confirms (Confirm…, sophon models embeddings consent --yes), or switch to a local embedder. |
| "No local embedder found" / "Not configured" | No verified local embedding model. Run sophon models embeddings install-local or ollama pull nomic-embed-text. |
State degraded with an open breaker or lastError | The embedder keeps failing. Check the provider, key and model, then run sophon models embeddings test or wait for the cool-down. |
State degraded with provider_not_found or provider_inactive | provider mode names a provider that is gone or disabled. Enable it, or choose another (use <id>, auto). |
restartRequired with no vector store | Sophon:Vectors:Provider is none. Set it to auto and restart. |
| "Embedding model changed: the vector index needs a rebuild" | A collection holds vectors of a different size. An admin runs sophon models embeddings rebuild --yes. |
truncatedScans keeps growing | SQLite searches hit MaxScanPoints. Move to Qdrant or raise the cap. |
Known limits
- Documents are still chunked at about 2,000 characters. With an embedder whose input window is smaller (512-token models such as
mxbai-embed-largeorandora-embed), the tail of a full chunk is not embedded. - Reindex progress counts owners and documents, not individual items.
- The admin rebuild's state lives in memory; a restart during a rebuild leaves half-filled collections and the status reads idle.
- The v2.0 Qdrant adoption is covered by automated tests but has not been run against a live Qdrant.
- On several replicas, a delete reaches only the collections that replica wrote or listed in the last 60 seconds.
- The 274 MB figure for
nomic-embed-textis approximate.
Related
- Routing, Failover & Budgets — chat models, roles and local-only chats.
- Memory — how memories and daily logs are written and searched.
- Backup & Upgrade — what changes in the data directory on upgrade.
Routing, Failover & Budgets
How Sophon picks a model for each job (Default, Auto or a pinned model, per role), when it fails over and when it refuses to, local-only chats, provider health, and per-provider spend limits.
Architecture Overview
How Sophon's Gateway, Core, Dashboard, Mobile, Node, and sandbox fit together.