Why an Update Made Our App Re-Index Everything — and How We Made Sure It Can't Happen Quietly
One of the least pleasant messages a developer can get is a user saying the app is doing days of work it already did. That happened to us: people updated, and their machines began re-computing a semantic index that had been finished for weeks. Nobody had asked for it and nothing in the release notes had warned about it.
The interesting part is not that we made a mistake. It is that the mistake looked, in code review, like an unambiguously good change.
What a semantic index actually stores
Keyword search stores words. Semantic search stores vectors — long lists of numbers that encode what a passage means, so that "the contract review comments" can find a document that says "feedback on the draft agreement" without sharing a single word.
The critical property is that a vector means nothing on its own. It only has meaning relative to other vectors produced the same way. Two vectors can be compared if and only if they came from:
- the same embedding model,
- the same tokenizer — the component that decides how text is cut into pieces before the model sees it,
- the same settings around both.
Change any one of those and the old vectors are not slightly wrong. They are meaningless in the new coordinate system, the way measurements in feet are meaningless if the rest of your data is in metres. There is no partial credit and no migration; the only correct response is to compute them again.
How a correctness fix becomes a data-invalidating change
Here is the shape of what happened. A defect was found in how text was being cut into pieces before embedding. The fix was right — it made the splitting match what the model actually expected, and it improved results.
But cutting text differently means the model receives different input. Different input produces different vectors. Every vector already on disk had been produced by the old, incorrect splitting, which means every one of them now disagreed with anything computed after the update.
Read that as a code review and you see a small, obviously correct patch. Read it as a data change and you see: this invalidates one hundred percent of a user's stored index. Those two readings do not look alike, and the review process only had eyes for the first one.
That is the general trap, and it is not specific to us. Anywhere a stored artifact depends on a pipeline, a change deep in that pipeline can invalidate everything downstream while looking local. The diff is small; the blast radius is total.
Why "just detect it and migrate" isn't available
A reasonable instinct is to convert the old vectors instead of recomputing them. That is not possible. There is no function that turns a vector made under one tokenizer into the vector the same text would have produced under another; the information needed to do it is exactly the information that was lost. Recomputation is not a lazy choice, it is the only correct one.
So the problem is not whether recomputation happens. It is whether it happens knowingly — whether the people shipping the release, and the people installing it, both understood the cost in advance.
What stands in the way now
The fix was not "be more careful." Care does not survive contact with a small, correct-looking diff. Three things changed instead.
The identity of a vector set is now explicit. Rather than being implied by whatever the code happens to do, the model and settings that a stored vector belongs to are named by constants. A vector on disk is stamped with which set it came from, so the app can tell — cheaply, at startup — whether what it has still matches what it would compute today.
Those constants are pinned by tests. There are now regression tests whose only job is to fail the moment those values change. One asserts that an upgrade which does not change them re-computes nothing at all; another asserts that an upgrade which does change them re-queues everything, and states that plainly rather than leaving it as an emergent behaviour nobody looked at. You cannot alter the axis and have the build stay green.
Changing them is now a declared act. The project's own development rules were amended so that touching those constants is treated like changing search ranking: the tests must be updated first, deliberately, and the release notes must say what will be recomputed and roughly what that costs. The point is not that re-indexing never happens again — sometimes it is genuinely the right call. The point is that it can no longer happen quietly.
What this means if you use LocalSynapse today
Two practical consequences.
First, an ordinary update does not touch your index. Upgrades that leave the vector identity alone re-compute nothing — that is now asserted by a test rather than assumed, which is a meaningfully different guarantee.
Second, if a future release genuinely needs to recompute, you will read it in the release notes before you install, not discover it from your fan. And while any recomputation runs, keyword search keeps working the whole time: the two indexes are separate, so you are never left without search.
There is a broader lesson we took from this, and it is the reason this post exists. When a system stores derived data, the question "what does this change do to data already on disk?" has to be asked somewhere that is not a person's memory. Ours now lives in tests that fail. That is the only kind of reminder that still works at eleven at night before a release.