document

Store format contract

This is the cross-language contract. A reader in any language that honours this document and passes the conformance suite is a ContextPull reader. The schema itself is in system-design.md §1; this page states the rules a reader must follow.

Format version: 1.0 (in meta.schema_version). Readers supporting major 1 must open any 1.x store.

Opening

  1. Open read-only. Never write.
  2. Read meta.schema_version. If missing, the file is not a store. If the major differs from what the reader supports, refuse with both versions in the message.
  3. Check that FTS5 is available: SELECT count(*) FROM sections_fts LIMIT 1 must succeed. If not, refuse with a message naming the binding.
  4. Read meta.index_text, meta.index_mode, meta.index_tokens, meta.corpus_fingerprint once; they are immutable for the life of the file.

Identifiers

Tokenisation

Ingest writes sections_fts with tokenize = "unicode61 remove_diacritics 2 tokenchars '-_.'". Because those characters are token characters everywhere, ingest first normalises the indexed columns (heading_path, text in the FTS table only): every run of -, _, . at the start or end of a token is removed, so --no-cache indexes as no-cache, cache. as cache, while tx-4419, 3.12 and tool.uv.sources stay whole. Readers apply the same normalisation to query text before tokenising. sections.text is verbatim; snippets come from the normalised column and may lack a leading dash or trailing full stop. Readers must build FTS queries so that the same tokeniser applies, which SQLite does automatically for MATCH. When a reader tokenises query text itself, for sanitisation or for grep candidates, it uses the regex [^\W_][\w.\-]* (a letter or digit, then letters, digits, ., _, -) on the lower-cased text and strips leading and trailing ._- from each token.

Given query, in, limit, mode:

  1. Tokenise query. If no tokens, return empty hits with the hint no searchable words.
  2. Build orq = '"t1" OR "t2" …' with each token as an FTS5 string (double quotes doubled inside).
  3. If two or more tokens, also build phrase = '"t1 t2 …"' and andq = '"t1" AND "t2" …'.
  4. Path filter: documents.path GLOB ? for each entry in in, joined with OR; an entry with no *, ? or [ also matches path GLOB entry || '/*'.
  5. Run, in order, the phrase query, the AND query, then orq, each ordered by bm25(sections_fts, 0.0, 2.0, 1.0) (weights per FTS column: id, heading_path, text), then id, each limited to limit. Concatenate, dropping ids already seen, and take limit. Precision descends and the candidate set for the expensive OR pass is only consulted when the stricter passes did not fill the limit.
  6. Snippet: snippet(sections_fts, 2, '**', '**', '…', 20), then all runs of whitespace collapsed to one space.
  7. score is the negated bm25 value, rounded to two decimals, so higher is better.
  8. mode = 'hybrid': if embeddings has rows for this store, embed the query with the model named in embeddings.model, compute cosine over all rows, and fuse the two rankings by reciprocal rank with k = 60. If embeddings is empty, run lexical and set mode in the result to lexical with hint = 'no embeddings in store'. The conformance suite does not cover hybrid because it depends on an external model.

Read

Grep

Neighbours

Index

Ordering and ties

Wherever this document says "ordered by rank", the full order is rank, then id ascending. Two conforming readers return identical id lists.

Things readers must not do