How it is built
This page is for the person who will be asked whether this is sound. It says what the system is, and — where a decision was close — what the alternative was and why it lost.
One instance per team
Every customer runs their own container. Its own database file, its own index directory, replicated to its own storage prefix.
There is no shared index with a customer column in it, and the API has no customer id parameter to pass. A request reaches one instance because of the hostname it arrived on and the token it carried, and that instance has only ever held one team’s data. This is what “isolation is architecture, not a WHERE clause” means concretely: there is no filter to get wrong, because there is nothing to filter.
The cost is real and worth stating. Per-customer containers mean per-customer cold starts, and a wake has to stay routine rather than becoming an incident — which is the constraint that drove the storage decision below.
Sqlite is the system of record
An append-only changes log plus a content-addressed blobs table is the truth.
Everything else — full-text search, embeddings, summaries — is a derived index that can be
thrown away and rebuilt.
This was git, and the reversal is instructive. The original design made a bare git repo the system of record: a change was a commit, a revision a commit id, a proposal a ref. It buys real things — snapshot semantics, content addressing, and the story that your context is a repo you could clone.
It was reversed because of what it cost around the edges. Git meant a second durable store beside the sqlite the tenant already ran, with its own replication and its own restore path. A cold start became one fetch per object of history, landing squarely on the wake path that had to stay routine. And the part git is uniquely good at — branching and merging — had already been designed out, so we were paying for a model we had decided not to use.
Sqlite gives one storage engine, one replication mechanism, one restore, one boot phase, and writes that are transactional across content and metadata. The queries git supposedly makes easy turn out easier here: “what changed between A and B” is a range over sequence numbers, and a proposal is a set of rows with a parent revision.
The rule that falls out of it: never answer a query by walking the log. Reads are point lookups or small ranges; search, ranking and navigation belong to the derived indexes.
Content addressing
Recording what a path already says records nothing — writes are idempotent by content. The same principle runs down the stack: a chunk’s id is a hash of the blob, the chunker version and the offset, and an embedding’s id is a hash of the chunk text and the model version.
So re-indexing costs only what actually changed, and a chunker or model upgrade is a recompute of the affected entries rather than a rebuild of everything.
Retrieval
Full-text search and vector search run side by side and their rankings are fused. Exact terms still win on exact terms; a paraphrase still finds the thread that answered it.
Embedding never runs in the write path. Recording a change publishes to an outbox on the write’s own transaction, and indexing reacts to that — so a commit is never waiting on a model, and full-text indexing is never stalled behind embedding. Every retrieval change is run against a fixed evaluation set before it lands.
Results come back ranked and nothing more is claimed. There is no relevance score in the API, because a fused ranking has no calibrated score to expose and a number that looked like one would be read as a promise.
Permissions
Filtering happens inside the index query, before ranking — never as a pass over results that already came back. Post-filtering leaks: counts, gaps and latency all carry signal about what was removed. Pre-filtering means a hidden resource is indistinguishable from one that does not exist.
Agents carry tokens bound to a person and see exactly what that person sees. No service tokens are issued anywhere in the system.
The two doors
Your instance speaks MCP for agents and REST for everything else. Both are generated from one OpenAPI contract, so they cannot describe different systems — a drift check fails the build if they diverge.
The web app is served per plane and is same-origin with the data it reads: account pages by the control plane, your instance’s own pages by your instance. No data call crosses between them.
Comparisons with the alternatives are on the comparisons page; the vocabulary used throughout is on the glossary.