SurfSense/surfsense_local
CREDO23 20293586bf chore: add AGENTS.md and a small set of vendor skills
Give agents a closest-file-wins ops map and new-code organization rules, and install only the skills that match files already in the tree.
2026-09-18 08:51:48 +02:00
..
backend fix(egress): gate the catalog's own model download, not just the manual pull 2026-09-18 03:53:29 +05:30
electron feat(electron): swap the desktop icon to the marketing black mark 2026-09-18 08:27:40 +02:00
frontend fix(frontend): catch the studio and update tests up with their components 2026-09-18 07:03:08 +02:00
packaging chore(community-local): scaffold the local app tree and readme 2026-09-03 18:28:38 +02:00
scripts fix(license): send GitHub tag-release download links in license mail 2026-09-17 23:02:55 +02:00
AGENTS.md chore: add AGENTS.md and a small set of vendor skills 2026-09-18 08:51:48 +02:00
README.md feat(local): studio and ingest drain their own queues, studio on 4 threads 2026-09-15 03:40:49 +02:00
VERSION chore(local): set the app version to 2.0.0 2026-09-17 04:05:55 +02:00

SurfSense Community Local

Local-first desktop app for research over your own documents. Runs fully offline with your own models — no Docker, no account, no data leaving the machine.

Features

  • Workspaces — group documents per project
  • Ingest — PDFs and files parsed with Docling, chunked and embedded locally
  • Chat with citations — hybrid retrieval (BM25 + vector) grounded in your documents
  • Studio — generate artifacts (summaries, podcasts) from selected documents
  • Airgapped — models and parsers on disk; nothing calls home

Requirements

Node.js 22.12+ (Electron 44 engine floor)
Python 3.12+
Local LLM Ollama or llama.cpp on 127.0.0.1

Development

One command brings the whole app up — Electron spawns the API and worker sidecars, waits on /health, and loads the Vite SPA, reaping the sidecars on quit:

cd electron
pnpm install
pnpm --dir ../frontend install # the SPA is a separate pnpm project
pnpm dev                      # frontend + Electron (spawns the Python sidecars)
pnpm check:sidecars           # asserts the spawn/health/kill loop leaves no orphans

Or run the backend on its own, one process per terminal:

cd backend
uv sync
uv run scripts/fetch_embedding_model.py  # one-time local retrieval model
uv run main.py                # API on http://127.0.0.1:8000
uv run worker.py ingest       # consumer; uploads stay pending without it
uv run worker.py studio       # consumer; Studio jobs stay pending without it
uv run pytest

Interactive docs at /docs, schema at /openapi.json. SURFSENSE_LOCAL_HOST, SURFSENSE_LOCAL_PORT, and SURFSENSE_LOCAL_DATA_DIR override the defaults.

Migrations run on startup and are written by hand — --autogenerate is switched off deliberately, because it renders a rename as a drop plus an add and the database it runs against is the user's only copy. After changing a model, write the revision yourself; a test fails if models and migration history disagree:

uv run alembic revision -m "add x to y"

Anything touching a table that already holds rows should read the live schema first (op.get_bind(), sa.inspect) rather than assuming its shape.

Architecture

Electron spawns three Python sidecars: the API and one worker per queue. The UI only talks HTTP to the API; heavy work is queued. Ingestion is CPU-bound and runs one job at a time; Studio mostly waits on a model and runs four at once, so an import never sits in front of a summary.

Electron ─┬─> FastAPI (127.0.0.1)        ──> surfsense.db
          ├─> Huey worker ingest (-w 1)  ──> surfsense.db, huey.db
          │                              └─> Docling, embeddings
          └─> Huey worker studio (-w 4)  ──> surfsense.db, huey.db
Vite SPA  ───> FastAPI                       Ollama / llama.cpp
Path Contents
frontend/ Vite + React SPA, shadcn/ui
electron/ Main process, sidecar lifecycle
backend/api/ App factory, session dependency
backend/modules/ One folder per feature: models, schemas, routes
backend/worker/ Huey consumer, ingest and Studio pipelines
backend/shared/ Engine, session, Alembic entrypoint
backend/alembic/ Migration history; the only thing that creates schema
packaging/ PyInstaller + electron-builder specs

Data directory

All runtime state lives outside the repo:

~/.surfsense/
├── surfsense.db              # workspaces, documents, chunks, chats
├── huey.db                   # job queue
├── models/                   # LLM + parser packs
└── data/workspaces/<id>/     # originals, extracted text, artifacts

License

Apache 2.0 — see LICENSE.