SurfSense/docs/adr
2026-10-13 11:07:27 +08:00
..
0003-artifacts-as-documents.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0004-desktop-app-is-its-own-tree.md docs(proposals): run the desktop stack in Docker, with CPU and CUDA images 2026-10-01 10:58:26 +05:30
0005-hand-written-migrations.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0006-hybrid-retrieval.md feat(local): rank on both legs, keyword strength being term coverage 2026-09-24 20:26:36 -07:00
0007-bundled-embeddings.md docs(local): keep the FP16 note in ADR 0007's Status line and fix the cosine's scope 2026-10-04 19:05:48 -05:00
0008-two-job-queues.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0009-freshness-by-invalidation.md perf(local): reload the Studio list on workspace events instead of polling every 1.5s 2026-10-01 11:22:58 -05:00
0010-studio-builders-not-sandboxes.md docs: cover the work since 2.1.0 so others can pick it up 2026-10-11 17:55:51 -07:00
0011-llama-cpp-local-runtime.md docs: describe per-type model settings and the models feature 2026-09-24 04:13:52 +05:30
0012-vulkan-only-gpu-backend.md docs(proposals): run the desktop stack in Docker, with CPU and CUDA images 2026-10-01 10:58:26 +05:30
0013-fit-from-the-allocator.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0014-two-tier-model-catalog.md docs: Update documentation for clarity and accuracy regarding model evaluation and catalog structure. A 2026-09-24 01:00:35 -07:00
0015-openai-compatible-connections.md docs(adr): record the ChatGPT plan sign-in decision and drop its proposal 2026-10-02 22:19:42 +05:30
0016-no-telemetry.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0017-egress-off-by-default.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0018-keychain-envelope-encryption.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0019-offline-licenses.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0020-two-update-channels.md fix(release): refuse to publish a prerelease desktop version 2026-09-29 09:13:20 +02:00
0021-no-intel-mac-build.md docs(plugins): redesign plugins as MCP servers behind one tool gateway 2026-10-09 01:49:22 +05:30
0022-markdown-only-cloud-import.md fix(import): recognize continued legacy threads 2026-10-13 11:07:27 +08:00
0023-sunset-behind-flags.md fix(sunset): gate the web redirect on DEPLOYMENT_MODE 2026-09-29 09:08:25 +02:00
0024-portal-without-accounts.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0025-scraper-client-as-paid-plugin.md docs(plugins): redesign plugins as MCP servers behind one tool gateway 2026-10-09 01:49:22 +05:30
0026-curated-order-is-list-position.md docs: Update documentation for clarity and accuracy regarding model evaluation and catalog structure. A 2026-09-24 01:00:35 -07:00
0027-egress-consent-per-host.md docs: reorganize engineering docs under docs/ 2026-09-22 21:27:26 -07:00
0028-model-written-code-runs-with-approval.md docs: cover the work since 2.1.0 so others can pick it up 2026-10-11 17:55:51 -07:00
0029-icu-translation-catalogs.md docs(localization): move the proposal into an architecture doc and ADRs 0029 and 0030 2026-09-25 01:53:10 +05:30
0030-formatjs-renders-interface-text.md refactor(electron): use one role-based menu in dev and packaged builds and drop @formatjs/intl from main 2026-09-25 02:38:56 +05:30
0031-ranking-blends-absolute-leg-scores.md fix(search): score every candidate on its own cosine 2026-09-24 23:49:37 -07:00
0032-one-tokenizer-for-index-and-question.md fix(local): keep a word's combining marks when the keyword index splits text 2026-09-24 22:57:38 -07:00
0033-every-candidate-is-scored-on-its-own-cosine.md fix(search): score every candidate on its own cosine 2026-09-24 23:49:37 -07:00
0034-vision-is-the-runtimes-answer-stored-nowhere.md feat(local): send images to chat models that can read them 2026-09-30 05:10:33 +05:30
0035-docker-compose-runs-the-desktop-stack.md docs(proposals): run the desktop stack in Docker, with CPU and CUDA images 2026-10-01 10:58:26 +05:30
0036-the-index-records-its-embedder.md docs(local): keep the FP16 note in ADR 0007's Status line and fix the cosine's scope 2026-10-04 19:05:48 -05:00
0037-embedding-is-a-type-not-a-slot.md feat(local): choose the embedding model in onboarding 2026-10-02 01:45:10 +05:30
0038-chatgpt-plans-sign-in-through-openai-not-codex.md Merge remote-tracking branch 'upstream/dev' into fix/chatgpt-subscription 2026-10-07 03:37:37 +05:30
0039-document-scripts-run-without-approval.md docs: cover the work since 2.1.0 so others can pick it up 2026-10-11 17:55:51 -07:00
0047-premium-plugins-are-source-available.md chore(license): put plugins/remote/proprietary/ under BSL, changed only by maintainers 2026-10-11 21:50:01 +05:30
0048-the-api-is-the-only-path-to-a-text-model.md Merge upstream/dev into feat/resumable-stream 2026-10-06 03:29:25 +05:30
0049-prompts-grow-at-the-end.md Merge upstream/dev into feat/prompt-caching 2026-10-06 03:29:38 +02:00
0050-a-model-answers-on-its-own-route-with-its-connections-credential.md Merge remote-tracking branch 'upstream/dev' into fix/chatgpt-subscription 2026-10-07 03:37:37 +05:30
0051-plugins-are-mcp-servers-behind-one-tool-gateway.md docs(plugins): give agent threads plugin servers through opencode directly 2026-10-11 03:27:30 +05:30
0052-the-chat-model-never-calls-tools.md docs(plugins): bring plugins to agent threads first, the chat engine later 2026-10-10 22:34:03 +05:30
0053-plugins-are-listed-in-a-registry-and-third-party-ones-start-off.md refactor(plugins): name plugins first-party and third-party, and settle the tables 2026-10-10 23:19:58 +05:30
0054-surfsenses-plugins-run-on-one-plugin-host.md docs(plugins): hosting in general terms, and the registry in its own container 2026-10-12 13:13:02 +05:30
README.md docs(plugins): hosting in general terms, and the registry in its own container 2026-10-12 13:13:02 +05:30

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, in part superseded by 0038
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, in part superseded by 0047
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, in part superseded by 0039
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
0038 A ChatGPT plan is a connection signed in through OpenAI's open-source flow, not the Codex CLI's client Accepted, its request body superseded by 0050
0039 Model-written document scripts run without approval in SurfSense's script runner, and the agent's general shell is off Accepted
0047 SurfSense's paid plugins are source-available under the Business Source License and unlocked by the SurfSense license Accepted, in part to be superseded by 0054
0048 The API is the only path to a text model, so one gate can count everything that generates Accepted
0049 A prompt changes only at its end, so the cached start of it is never read again Accepted
0050 A remote text model answers on the route its manifest entry records, with its connection's credential, and SurfSense owns a ChatGPT plan's sign-in for both engines Accepted
0051 A plugin is an MCP server, and SurfSense decides which of its tools each caller gets, remote servers first Proposed
0052 The chat engine's model never calls tools; SurfSense chooses a plugin tool through structured output and calls it Proposed, deferred
0053 First-party plugins are listed in the app, third-party plugins in a registry in this repository, and third-party plugins start off Proposed
0054 SurfSense's own plugins, free and paid, run on one plugin host, with the registry served beside it Proposed