SurfSense/docs/adr
DESKTOP-RTLN3BA\$punk 3430154551 docs(adr): 0067, SurfSense's B2B jobs ship as paid plugins
The security questionnaire, RFP responses, the contract redline, bank
statements, medical chronologies and contract and lease registers ship as
paid bundles that run on the user's machine and unlock with a SurfSense
license, the 30-day trial included. The app, the general agent, the engines
and the shared job infrastructure stay free under Apache-2.0, and a user's
own files never need a license. Supersedes ADR 0047's job-content clause.
2026-10-21 21:39:57 -07: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 docs(import): align thread recovery documentation 2026-10-16 01:24:52 +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 perf(local): run the bundled embedder at fp32 on x86-64 2026-10-16 00:32:11 -07: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 docs(adr): a Claude plan runs through the user's own Claude Code 2026-10-16 11:20: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 docs(adr): 0067, SurfSense's B2B jobs ship as paid plugins 2026-10-21 21:39:57 -07:00
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 docs(adr): a Claude plan runs through the user's own Claude Code 2026-10-16 11:20:37 +05:30
0051-plugins-are-mcp-servers-behind-one-tool-gateway.md docs(plugins): fold the built remote plugins into architecture, keep only what is not built as the proposal 2026-10-14 13:47:47 +05:30
0052-the-chat-model-never-calls-tools.md docs(plugins): fold the built remote plugins into architecture, keep only what is not built as the proposal 2026-10-14 13:47:47 +05:30
0053-plugins-are-listed-in-a-registry-and-third-party-ones-start-off.md feat(registry): Verified by maintainers, and CI connects to each new or changed plugin 2026-10-14 15:07:04 +05:30
0054-surfsenses-plugins-run-on-one-plugin-host.md docs(plugins): plugins.surfsense.com is live 2026-10-14 14:00:04 +05:30
0055-verified-is-a-maintainers-judgement-and-ci-contacts-each-server.md feat(registry): Verified by maintainers, and CI connects to each new or changed plugin 2026-10-14 15:07:04 +05:30
0056-speech-recognition-runs-on-a-second-audiocpp-server.md Merge remote-tracking branch 'origin/dev' into feat/local-speech-recognition 2026-10-14 14:51:34 -07:00
0057-a-claude-plan-runs-through-the-users-own-claude-code.md fix(local): close more Claude plan gaps and say above the composer when the plan is on extra usage 2026-10-17 11:57:53 +05:30
0058-the-bundled-embedder-runs-at-fp32-on-x86-64.md perf(local): run the bundled embedder at fp32 on x86-64 2026-10-16 00:32:11 -07:00
0059-a-born-digital-pdf-is-read-from-its-text-layer.md docs(local): ADR 0059 and documents.md match the merged figures pass, mixed PDFs and re-measured answers 2026-10-16 08:37:02 -07:00
0060-one-consent-question-per-user-action.md Merge remote-tracking branch 'upstream/dev' into feat/claude-plan 2026-10-17 02:22:00 +05:30
0061-claude-code-reaches-anthropic-and-in-the-agent-the-turns-plugins.md Merge upstream dev into feat/plugins-homepage 2026-10-21 00:31:35 +05:30
0062-a-scan-is-read-by-ocr-alone-before-docling.md fix(local): a long OCR reading gives the queue up as a long Docling reading does 2026-10-17 15:29:58 -07:00
0063-a-plugins-connection-is-a-transport-and-one-sign-in.md feat(plugins): an OAuth server added by URL is saved signed out and signed in from its row 2026-10-21 02:58:09 +05:30
0064-agent-runtimes-reach-plugins-through-surfsenses-gateway.md Merge upstream dev into feat/plugins-homepage 2026-10-21 00:31:35 +05:30
0065-a-turns-plugin-tools-are-sized-by-the-window-not-counted.md Merge upstream dev into feat/plugins-homepage 2026-10-21 00:31:35 +05:30
0066-web-pages-are-html-the-model-writes-without-network.md docs: accept ADR 0066 and fold the web pages proposal into the architecture 2026-10-22 00:13:35 +05:30
0067-b2b-jobs-ship-as-paid-plugins.md docs(adr): 0067, SurfSense's B2B jobs ship as paid plugins 2026-10-21 21:39:57 -07:00
README.md docs(adr): 0067, SurfSense's B2B jobs ship as paid plugins 2026-10-21 21:39:57 -07:00

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, amended by 0058
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, its Claude consequence by 0057
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 superseded by 0054 and 0067
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, in part narrowed by 0057
0051 A plugin is an MCP server, and SurfSense decides which of its tools each caller gets, remote servers first Accepted
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 Accepted, in part superseded by 0055
0054 SurfSense's own plugins, free and paid, run on one plugin host, with the registry served beside it Accepted
0055 Verified is a maintainer's judgement, and CI contacts each server a pull request adds Accepted
0056 Speech recognition runs on a second audio.cpp server that only the API calls Accepted
0057 A Claude plan is reached through the user's own Claude Code, and SurfSense holds no Claude credential Accepted, its network decision superseded by 0061
0058 The bundled embedder runs at FP32 on x86-64, upcast in memory, and its vector space is still the pinned FP16 file Accepted
0059 A born-digital PDF is read from its own text layer, and Docling reads only a PDF with a page that needs it Accepted
0060 One consent question asks for every host a user action needs, including hosts its background work will reach Accepted
0061 Claude Code reaches Anthropic's two hosts, and in the agent also the hosts of the turn's ready plugins Accepted
0062 A scan or a photo is read by OCR alone, RapidOCR or Apple's Vision, and Docling reads only a page OCR could not Accepted
0063 A plugin's connection is a transport and one sign-in, detected from the server Accepted
0064 Agent runtimes reach plugins only through SurfSense's gateway, and never hold a plugin's credential Accepted
0065 A turn's plugin tools are sized by the model's window, not counted, and Claude Code searches its own Accepted
0066 Web pages are HTML the model writes, run in a frame that has no network Accepted
0067 SurfSense's B2B jobs ship as paid plugins that run on the user's machine and unlock with a SurfSense license Accepted