Project Retrospectives
What changed when Satori became local-first
Moving Satori from a connected-vector-store story to a local-first code map forced clearer ownership between source evidence, model-specific vectors, publications, runtime compatibility, and agent-facing tools.
Date
Read
4 minRepository snapshot
Satori
Local-first code mapping for AI coding agents.
- Commits
- 1,377
- MCP tools
- 7
- Default
- Local-first
Work done
Satori used to be easiest to explain as semantic code search behind MCP.
That explanation stopped being useful once the default runtime no longer needed a hosted embedding API or remote vector database.
Making Satori local-first forced a better question: which parts of a code map come from the repository, and which parts only belong to the current retrieval model?
In 30 seconds
- Problem: when source truth, embeddings, vector storage, navigation, and index readiness are treated as one blob, changing the runtime can make stale or incompatible state look valid.
- Decision: separate source-derived evidence from model-specific vectors and publish complete searchable generations atomically.
- Proof: the current default combines Potion Code 16M v2, BM25, and LanceDB locally; optional LateOn reranking stays local; connected Voyage/Milvus and Ollama remain explicit alternatives rather than hidden requirements.
The old mental model was too coupled
An early Satori path could be summarized as:
repository -> chunks -> embeddings -> vector store -> semantic search
That is enough for a demo. It is weak as a lifecycle model.
A repository can change while an embedding publication stays old. A vector backend can be reachable while only part of a new generation exists. A new model can produce vectors with a different dimension. Navigation evidence can still be valid even when model-specific state needs rebuilding.
If all of that is called “the index,” the tool eventually has to guess what “ready” means.
Local-first made the separation unavoidable
The current default runtime uses bundled Potion embeddings with BM25 and LanceDB. No model API key is required on the qualified Linux x64 / WSL2 path.
That choice matters for privacy and setup, but the more important architecture change is what had to remain stable around it.
Source-derived lexical/navigation evidence belongs to the repository generation. Dense vectors belong to an embedding/runtime configuration. The active publication has to prove that those pieces are compatible before search reports readiness.
That lets Satori support explicit alternatives without pretending they are interchangeable state:
| Runtime | Dense retrieval | Storage |
|---|---|---|
| Local default | Potion Code 16M v2 | LanceDB |
| Local alternative | selected Ollama model | LanceDB |
| Connected | Voyage Code 3 | LanceDB |
| Connected Milvus | Voyage Code 3 | Milvus / Zilliz |
Changing provider, model, dimension, backend, or persisted projection can require a reindex. Satori does not silently convert the previous publication to make the new runtime look ready.
Publication became more important than storage
A vector database is a storage detail. The agent needs a stronger guarantee: the evidence it receives belongs to one complete observable repository generation.
Satori now activates searchable state as a complete publication. Readers use the complete previous generation or the complete replacement generation. Failed candidate work does not replace the active one halfway through a sync.
That also changes repair semantics. If source or compatibility authority is ambiguous, the system can require a reindex instead of constructing a convenient story from partial metadata.
Search became less semantic-only
Local-first also reinforced something the project had already been learning: code retrieval should not throw away exact identifiers just because embeddings are available.
The current path combines exact evidence, BM25, and dense retrieval. Results are grouped around likely owners, and the agent can move from search into outlines, advisory graph relationships, and exact bounded source reads.
The public MCP surface is now seven tools because continue_search can disclose more of one frozen search result without rerunning retrieval:
search_codebase
continue_search
file_outline
call_graph
read_file
manage_index
list_codebases
The tool count is not the goal. The useful property is that an agent can ask for more evidence without accidentally changing the result set it is reasoning from.
The measurements got more useful when the claims got narrower
The repository now carries its own qualification evidence. On a sealed local Potion + LanceDB run over Satori itself, warm-search p95 was recorded at 154.543 ms and zero-change sync p95 at 185.662 ms.
A separate exploratory two-task OpenCode comparison found correct answers in both arms. The Satori arm used fewer tool calls, fewer tool-output bytes, and less wall time than native file discovery on those two tasks, while total model-token usage was effectively unchanged.
That is useful evidence for a shorter investigation route. It is not a universal claim that Satori always saves tokens or always wins a benchmark.
The narrower statement is more credible.
The product is the evidence contract
The visible feature is still search. The system I care about now is everything that has to be true before search deserves to guide an edit:
- the source generation is fresh enough to use;
- lexical identifiers remain in the retrieval path;
- model-specific state is compatible with the selected runtime;
- readers never see a half-published generation;
- results point toward owners rather than repeated chunks;
- the agent can inspect exact source before editing;
- degraded or incompatible state produces an explicit next action instead of a confident guess.
Local-first made Satori easier to install. More importantly, it made those boundaries harder to ignore.