SurfSense/docs/adr/README.md
Anish Sarkar 84c7ffad18 feat(local): choose the embedding model in onboarding
The embedding model is now chosen in the last onboarding step and fixed
for the whole library. Skipping keeps bge-small, which ships with the
app and can't be deleted. Changing the model later is not built.

- An embedding_indexes row records the model's spec and its vector
  table, and each document records which index embedded it. Migration
  0021 stamps existing libraries with bge-small. Routes that queue
  embedding work return 409 embedding_not_chosen until a model is
  chosen.
- Ingest, Studio and search embed through the active index; the old
  worker embedding module is removed. Workers wait for the schema, and
  documents interrupted when the app closed are marked failed.
- Curated embedders (bge-small, granite-97m, granite-311m) live in the
  local manifest, run by a new onnxruntime engine. EMBEDDING is a
  catalog type, never a selection slot.
- Hugging Face search finds ONNX embedders. Each download is checked: a
  probe sets its width, and a ten-question search check must pass.
  Small tokenizer.json files that Hugging Face lists without a checksum
  are hashed at the pinned commit. Results use the GGUF search's shapes.
- Onboarding renders the embedding step with the same component and
  shared list as the other steps; only its choice hook differs. Use
  marks the choice and Finish sends it. Bundled models have no Delete.
- Settings has a read-only Embedding section.
- ADRs 0036 and 0037, a new embedding architecture doc, and updates to
  the docs it touches. New strings are translated into every language.
2026-10-02 01:45:10 +05:30

7.2 KiB
Raw Blame History

Architecture decision records

Each file here records one decision and the reasons for it: the why behind what architecture/ describes. An accepted ADR is not rewritten. When a decision changes, a new ADR supersedes the old one, and the only edits an accepted ADR takes are its Status line, which becomes "Superseded by" with a link to the new record (or, when only part is superseded, names that part and the record that supersedes it), and fixes to its links.

Adding one

Take the next free number. Add the record in the same pull request as the change it decides, or with the proposal that makes it. Use this shape and keep it short:

# ADR NNNN: <the decision, stated as a sentence>

- **Status:** Accepted
- **Date:** YYYY-MM-DD
- **Supersedes:** <only if it does>
- **Source:** [<short name> L<a>–<b>](<permalink>)

## Context
<the problem and the constraints that forced a choice>

## Decision
<the choice, precisely; bullets are fine>

## Consequences
<what it makes true, what it costs, what it rules out>

## Where the code stands
<only when the code does not fully hold the decision; state the gap plainly, citing file paths>

The Date is the day the decision was first written down. Source links are permalinks to a fixed commit, so they keep working after the file they point at moves or is deleted.

Numbering

0001 and 0002 were hosted-era records about the git-native knowledge base. They were deleted on 17 Sep 2026 in commit 35c83caa8, and their numbers are retired. 0003 was written for the hosted stack and restored for the desktop app.

Index

Number Title Status
0003 Generated deliverables are a document type, not a second corpus Accepted
0004 The desktop app is its own tree on FastAPI, SQLite and Huey, with no accounts Accepted, in part superseded by 0035
0005 Schema changes are hand-written Alembic revisions that the API applies at startup Accepted
0006 Retrieval widens recall with FTS5 and sqlite-vec, then orders the union by cosine similarity Accepted, the ordering decision superseded by 0031
0007 Embeddings come from a bundled bge-small model run in process on the CPU Accepted, amended by 0036
0008 Ingest and Studio jobs run on separate Huey queues, each drained by its own worker Accepted
0009 The UI stays fresh by invalidating queries on server-sent events, with no sync engine Accepted
0010 Studio models emit structured content and trusted builders render it, so no model-written code runs Accepted, in part superseded by 0028
0011 llama-server in router mode is the one local model runtime Accepted, in part superseded by 0026 and the model catalog proposal
0012 The GPU backend is Vulkan everywhere off Apple Silicon, with no CUDA payload Accepted, for Docker images superseded by 0035
0013 Model fit comes from the allocator's view of one device, and only physics refuses an install Accepted
0014 The model catalog is a curated offline manifest plus Hugging Face search, and SurfSense downloads the files itself Accepted, in part superseded by 0026, 0027 and the model catalog proposal
0015 Remote models come through named OpenAI-compatible connections, discovered live Accepted, in part revised by the model catalog proposal
0016 The app sends no telemetry or crash reports Accepted
0017 Every outbound destination is off until the user allows it Accepted, in part superseded by 0027
0018 Provider keys are encrypted with a per-install secret kept in the OS keychain Accepted
0019 Licenses are signed files verified offline, and they gate only plugins and priority support Accepted
0020 The new app and the legacy app share one release feed and are kept apart by update channel Accepted
0021 There is no Intel Mac build Accepted
0022 Hosted accounts move to the app as a markdown-only export that is re-embedded locally Accepted
0023 Every hosted sunset behaviour sits behind a runtime flag, and nothing is deleted or redirected unconditionally Accepted
0024 The license portal has no accounts and no license tables; Stripe and Keygen are the system of record Accepted
0025 The hosted scraper API client ships as a paid plugin whose source lives in this repo Accepted
0026 Curated models are ordered by their position in the manifest, with no score Accepted, in part revised by the model catalog proposal
0027 Egress consent is per host, so model search and downloads share one Accepted
0028 Model-written code may run on the user's machine without a sandbox, and the agent asks before each shell command Accepted
0029 Interface text lives in ICU MessageFormat catalogs, one flat JSON file per language Accepted
0030 FormatJS renders interface text from precompiled, bundled catalogs, with no network at build or run time Accepted
0031 Ranking blends the two legs on absolute scales, keyword strength being term coverage rather than BM25 Accepted, amended by 0032 and 0033
0032 The index and the question are split by one tokenizer, FTS5's own, and it keeps a word's combining marks Accepted
0033 Every candidate is scored on its own cosine, because a chunk the vector leg did not reach is unmeasured rather than unrelated Accepted
0034 Whether a chat model reads images is llama.cpp's own answer for a local model and the catalog's for a remote one, and it is stored nowhere Accepted
0035 The desktop app's stack also ships as one Docker container, with Caddy as its only listener Accepted
0036 The index records which embedder built it, and the embedder is fixed once per install Accepted
0037 Curated embedders are a fourth engine in the local catalog, and embedding is a type, never a slot Accepted