Skip to content
Ahmed Hamza

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 min

Repository snapshot

Satori

Local-first code mapping for AI coding agents.

Commits
1,377
MCP tools
7
Default
Local-first

Work done

Potion + BM25 + LanceDBAtomic publicationsBounded source reads

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

Satori local-first evidence route Satori routes a coding agent through MCP tools, hybrid retrieval, freshness-aware publications, owner-oriented evidence, and bounded source reads. Satori / evidence route before edit FRESH SOURCE → OWNER EVIDENCE → BOUNDED READ Coding agentMCP client 7 MCP toolssearch / continue /outline / graph / read Hybrid retrievalexact + BM25+ dense retrieval Owner evidencesymbols + spans + graphthen bounded source OFFLINE DEFAULT Potion + BM25 → LanceDB Optional LateOn reranking · no model API key CONNECTED OPTIONS Voyage / Ollama → LanceDB or Milvus Runtime choice changes vectors, not source authority PUBLICATION RULE Readers see a complete generation, never half-published search state.
Local-first was not just a provider swap. It forced source authority, vector state, publication lifetime, and agent disclosure to become separate contracts.

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:

RuntimeDense retrievalStorage
Local defaultPotion Code 16M v2LanceDB
Local alternativeselected Ollama modelLanceDB
ConnectedVoyage Code 3LanceDB
Connected MilvusVoyage Code 3Milvus / 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:

Local-first made Satori easier to install. More importantly, it made those boundaries harder to ignore.