Semantic Workshop
Search in the Wild

A Private Semantic Search System for a Home Document Archive

A Private Semantic Search System for a Home Document Archive
Build a private semantic search system for your home document archive using local hardware like a Mac Mini. By combining EmbeddingGemma 2 with BM25 and running entirely within Docker, this architecture ensures complete data privacy without cloud uploads. Learn how proper data preparation, text normalization, and incremental indexing make local retrieval fast, practical, and secure for everyday personal notes and scanned files.

Here is the short version.
I wanted to search years of personal notes, scanned receipts, old project files, and half-finished drafts without uploading any of them to a cloud service. The system that resulted runs entirely on a Mac Mini under my desk, uses EmbeddingGemma 2 for semantic matching, keeps BM25 for exact tokens, and never sends a single document outside the house. It is not perfect. It is good enough that I now reach for it instead of scrolling through Finder or hoping I remembered the right filename.

This post describes the concrete design, the data decisions, the failure modes that appeared once real documents replaced clean demo files, and the maintenance habits that keep the system usable months later. Everything stays within the hardware and software constraints already measured in the Small-System Reality posts.

Why a Home Archive Is Different from a Demo

Demo collections are tidy. Real home archives are not. Mine contained:

  • Markdown notes from five different apps

  • PDFs of old tax forms and appliance manuals

  • Plain-text exports from messaging apps

  • Scanned images that had been OCR’d with varying quality

  • Duplicate or near-duplicate files created by successive backup schemes

  • Documents that mix English with occasional other languages or code snippets

The retrieval problem is therefore not “find the closest vector in a clean corpus.” It is “find the note I half-remember writing three years ago even though the filename is useless and the OCR is noisy.” Privacy is non-negotiable; these files include personal financial and family-related material. Upload-to-cloud was never an option.

Design Goals I Actually Enforced

  1. All data and all computation stay on local hardware.

  2. The system must remain usable after a reboot with a single command or a systemd/Docker restart.

  3. Adding new documents should not require a full re-embed of the entire archive.

  4. Exact matches (invoice numbers, exact phrases, error strings) must still work.

  5. The interface can be minimal—initially just a command-line query or a simple local web form.

These goals led directly to the hybrid stack already built in the Reproducible Builds series: EmbeddingGemma 2 + BM25 + Qdrant, running in Docker on the Mac Mini.

Data Preparation That Mattered More Than the Model

Computer screen showing text files and code in an editor.

The first version used raw files and produced mediocre results. The improvements that moved the system from “interesting” to “actually useful” were almost all on the data side:

  • Text extraction and normalization. PDFs went through a local OCR pass only when no text layer existed. Markdown kept its structure but lost decorative front-matter that confused embeddings.

  • Aggressive near-duplicate removal. I computed a cheap hash on the first and last 500 characters and collapsed obvious copies. This alone removed roughly 15 % of the collection and reduced ranking noise.

  • Chunking with overlap. Long notes were split into 300–400 word chunks with a 50-word overlap so that a relevant paragraph was less likely to be cut in half.

  • Lightweight metadata. Each chunk carried its source filename, creation date (when available), and a short path. The metadata never left the local machine but made the result list readable.

None of these steps required a GPU. All of them improved the ranked lists more than switching embedding models would have.

The Running System

The deployment is the same Docker Compose layout described earlier, with two practical additions:

  • A small watch script that detects new or changed files in the archive folder and triggers an incremental embed + upsert into Qdrant and a BM25 update.

  • A minimal FastAPI endpoint that accepts a query, runs the hybrid retrieval, and returns the top results with source path and a short snippet.

I query it from the terminal most days:

Bash

curl -s -X POST http://localhost:8000/search/hybrid \
  -H "Content-Type: application/json" \
  -d '{"text": "warranty information for the dishwasher", "limit": 5}' | jq

A simple local HTML form sits on the same port for days when I want to click instead of type. No authentication is required because the service listens only on localhost and the machine is not exposed to the wider network.

What Worked Well

Semantic matches on paraphrased memories.
Queries such as “the note about the roof leak repair estimate” surface the right document even when the original text used different wording.

Exact token rescue via BM25.
Invoice numbers, model numbers, and distinctive error strings are recovered by the sparse side and survive the hybrid fusion.

Cold-start tolerance.
Because the Mac Mini stays on, the model remains warm. When I do reboot, the 15–20 second load time is acceptable for a personal tool.

Privacy by architecture.
There is no API key, no external network call, and no telemetry. The threat model is “someone who already has physical or login access to the machine,” which is the same threat model as the files sitting in the filesystem anyway.

Developer typing on a keyboard in front of terminal search results.

Failure Modes That Appeared with Real Data

OCR garbage.
Some scanned pages produced embeddings that matched other low-quality OCR documents more strongly than the correct clean note. Filtering out chunks with abnormally high non-alphabetic character ratios helped.

Temporal confusion.
The system has no built-in notion of time. A query for “the latest insurance policy” can still surface an older version if the older version happens to be closer in embedding space. I mitigated this by including the year in many queries and by sorting final results by file date when scores are close.

Over-chunking.
Very short notes that were left un-chunked sometimes ranked higher than the more complete but split version of the same information. Keeping a minimum chunk size and preferring the longer original when scores were near-identical reduced the problem.

Index drift.
If the watch script missed a file (permission change, temporary lock, etc.), the dense and BM25 sides could fall out of sync. A weekly full reconciliation job that compares document counts and re-processes anything missing fixed the quiet failures.

Maintenance Habits That Keep It Alive

  • Weekly automated backup of the Qdrant storage volume and the BM25 index to an encrypted external drive.

  • Monthly full re-embed only when the model revision changes or when I have made large changes to the chunking logic.

  • A short text file in the repository that records the exact model revision, alpha value, and chunk settings used for the current index.

  • One “sanity query” I run after every update—a question whose correct answer I know by heart—so I notice ranking regressions immediately.

These habits take less than thirty minutes a month when nothing is broken and have prevented the slow decay that kills most personal search projects.

Resource Footprint on the Mac Mini

  • Idle with model loaded: ~7–8 GB RAM, 6–8 W

  • Light query load: negligible additional CPU

  • Incremental update of a few dozen new documents: 1–3 minutes

  • Full re-index of the current archive (~4,500 chunks): ~18 minutes

The machine remains usable for ordinary desktop work while the service runs. I do not run heavy local LLMs at the same time; the 16 GB ceiling becomes visible when both are resident.

Who This Design Is For (and Who It Is Not)

This approach fits people who:

  • Already keep most of their documents in local folders

  • Care more about privacy than about multi-device instant sync

  • Are willing to spend a weekend on initial setup and a few minutes a month on maintenance

  • Have a machine that can stay powered on or wake quickly

It is less suitable if you need search from a phone while away from home, if your archive lives primarily in proprietary cloud formats that resist clean export, or if you require collaborative editing and real-time sharing.

Closing

A private semantic search system for a home document archive does not require exotic hardware or constant cloud fees. It requires careful data preparation, a hybrid ranking stack that respects both exact and semantic matches, and a few boring maintenance habits. The system I run today is the direct descendant of the pipelines built in the earlier posts; the only material changes were the document handling and the incremental update logic.

The next post applies the same principles to a more constrained and more personal collection: a searchable library of children’s books that never leaves the house. After that we will look at small-team FAQ search and the practical differences that appear when the users are not only yourself.

Until then, start with the documents you already have on disk. The first useful query results appear long before the system is perfect, and the privacy guarantee is present from the first line of code.

The benchmark is the conversation starter, not the verdict.

Updated · 2026-10-21 11:36
Feedback

No feedback yet — submit the first.

Submit feedback
© 2026 Semantic Workshop. All rights reserved. data-driven, published weekly ♥