← Back to the Build Log Building with AI

Searching my notes and email by meaning, on my own hardware

D
Daniel · July 12, 2026 · 5 min read
A flat illustration of a magnifying glass over a scattered field of document and envelope icons, a few related ones glowing teal and linked by thin lines, in navy and teal

I have kept notes for a very long time. Close to twenty thousand of them by now, scattered across a personal vault, plus an email archive of around 127,000 messages going back to the nineties. The problem with a pile that big is not storing it. It is finding anything in it later.

Keyword search only works when you remember the words. Half the time I don't. I remember the shape of a thing. That time someone laid out a cleaner way to do the backups. The note where I worked out some math I never want to redo. I know it is in there somewhere. I just can't reproduce the exact phrase I typed three years ago, so a plain text search gives me nothing and I give up and go do the work again.

So a few weeks ago I built the thing I actually wanted. Search by meaning, with the indexing and the search running entirely on my own hardware.

What "by meaning" even means

The trick is embeddings. You run each chunk of text through a small model that turns it into a list of numbers, a few hundred of them, that capture roughly what the passage is about. Similar meaning lands close together in that number space, even when the words are completely different. Then when I search, my question gets turned into the same kind of number list, and the box just finds the passages that sit nearest to it.

The model I use is bge-small-en-v1.5. It is tiny, the vectors are 384 numbers each, and it runs through ONNX on ordinary CPU. No API key, nothing leaving the machine, no special hardware to buy. It chews through the whole archive in a couple of hours the first time, then sits at basically nothing after that.

The search itself is boring in the good way. Every passage is a vector sitting in a file. My question becomes a vector. A dot product against the pile, sort by score, done. It comes back in well under a second even across the whole thing. There is no database engine, no service to babysit. Numpy and a couple of files on disk.

The database everyone expects

Here is the part where most people would stop me. Embeddings live in a vector database, everyone knows that. The default reach is pgvector, or Pinecone, or Chroma, or Qdrant, and for the fast search underneath, a FAISS or HNSW index on top. The whole ecosystem is built and marketed around that one sentence, so it has become the reflex, even for a project this small.

And that is where a lot of people quietly lose the weekend. They stand up a vector database for ten thousand documents because that is what the tutorial did, and inherit the whole tail of it: a service to run, a schema to design, connection pools, migrations, backups, one more thing that can be down at four in the morning. Real, ongoing database administration, for a personal pile of notes.

I skipped all of it, and not because I did not know better. I have spent a career in databases, Oracle going back to 7.3 and then years of SQL Server. I know exactly what one buys you, and I know when you are paying for it without using it. At this size the search is a single matrix multiply against a couple of files on disk. No service, no schema, no locking, no corruption, nothing to babysit, and the whole index rebuilds from scratch if I ever throw it away.

The real limit is that this does not scale forever. It scans everything on every query, so at millions of vectors it slows down, and that is the moment you reach for a proper index like FAISS or HNSW, which is a different tool than a relational database anyway. But that is a problem for someone with a hundred times my data, not for my notes. Where a database is the right tool, I still use one. Here it simply is not, and the whole thing is faster, smaller, and calmer without it.

The nightly bit

An index is only useful if it stays current, and I did not want to think about it. So the reindex is a job that runs in the middle of the night, right after the box pulls in the day's new mail. It is incremental. It looks at what actually changed since yesterday, embeds only those pieces, and leaves the rest alone. A note I edited this afternoon, an email that landed this morning, both are searchable by breakfast. New stuff never gets missed, and I never run anything by hand.

This is the part you cannot skip at real volume. At a few hundred notes you could re-embed the whole lot every night and never feel it. At 127,000 emails and close to twenty thousand notes, that is an hour of the machine grinding for the sake of a handful of changes, every night, forever. Do it lazily and the thing either falls behind or turns into a chore, and then you stop using it. The incremental job, and keeping the vectors as plain files on disk instead of rebuilding them, are the difference between something I actually reach for every day and a demo I ran once and forgot.

When I do want to poke at it, rebuild from scratch or watch a run go by, I open TerminalNexus and SSH into the box and kick it off. Same as any other job on there.

Then it learned to answer

Finding the right note is good. But some questions are not "find me the note," they are "read across all of this and tell me the answer." What did I decide about the roof. Which of these threads actually mattered. That is the retrieve-then-reason pattern, and it turned out to be simpler to bolt on than I expected.

It works in two steps. First the same meaning-search from above pulls the most relevant few dozen passages out of the archive. Then those passages, and only those, get handed to a capable model along with the question, and it reasons over them and answers in plain words, with a link back to every note or email it leaned on so I can check it.

That second step is the one piece that is not just numpy on my CPU. The reasoning model is the expensive part, so I keep it pluggable. Point it at a hosted model for the sharpest answers, or a smaller local one if you would rather nothing leave the room at all. The retrieval half, the part that reads the whole archive, is fully local either way. The model only ever sees the handful of passages the search already picked, never the pile.

The first real test of it genuinely surprised me. I asked a vague, human question, no keywords a plain search would catch, and it came back with the right handful of items and a straight answer, each claim pointing at the actual source. I have written a lot down over the years without ever really being able to talk to it. Now I can.

Where I drew the line

A couple of boring but real notes. This is search over my own notes and my own email, and that is where I stopped. Nothing about it reaches out and indexes things it has no business touching. And when the answer touches anything private, the whole thing lives behind my own network, reachable only by me. It is a personal tool, not a service, and I built it to stay that way.

This is all the homelab-and-tinkering side of what I do, not the product I actually ship. The nice thing about that side is how much of it now fits in a chat window and a terminal on my own box. A small model, a nightly job, a handful of files on disk, and no monthly bill for the search itself. Just my own stuff, finally findable by what I meant instead of what I typed.

Thanks for reading. If you have built something like this, or you are about to, send me a line, I would like to hear how you approached the reasoning half.

Comments