Skip to content

Architecture

What this page covers: how groundkit's moving parts fit together, and which mechanism enforces each of the project's non-negotiable principles — not just what they claim. Who needs it: anyone about to change code here, or anyone who wants the shape of the system before diving into one component. If a term below — chunk, BM25, hybrid, eval harness, ADR — is new to you, Concepts explains each one in plain language first; this page assumes you already know what those words mean and goes straight to how groundkit's version of each is wired together.

groundkit is a retrieval service built around one structural commitment, from which most of the rest follows: the core is deterministic, and the LLM sits at the boundary. Ingestion, chunking, indexing, scoring, fusion, and citation resolution are pure, typed, tested code. No model runs in the retrieval path.

The corollary is that retrieval quality is measurable rather than impressionistic — which is why the eval harness landed in Phase 2, before hybrid retrieval and rerank, instead of after them.

Components

ingestion/          loaders (path-contained), chunking (offset-preserving)
index/              bm25 (persisted), dense (LanceDB), metadata (SQLite)
retrieval/          search orchestration, RRF fusion, rerank, citations
providers/          embeddings (Ollama | OpenAI-compat), redaction,
                    optional query-rewrite/synthesis
service/            FastAPI api, MCP server
evals/ (package)    deterministic metrics engine
cli                 grk

Every component boundary is a typing.Protocol seam, and every seam has a conformance test. That is a structural fix for a specific failure: the predecessor project drifted its implementations away from their interfaces because nothing checked, and ADR-0001 hazards 3–4 record what that cost.

The principles, and what enforces each

An architecture principle with nothing enforcing it is a preference. Each of SPEC.md §2's non-negotiables has a mechanism:

Principle What enforces it
Deterministic core, LLM at the boundary No model import anywhere under retrieval/; the rerank model import is deferred and optional. See The LLM boundary.
Citations are verifiable Every result carries document ID, chunk ID and char offsets; retrieval.verify_citation re-reads the source and checks them.
Anonymization at the LLM boundary Enforced on cloud chat egress (build_chat wraps it in RedactingChat, no opt-out); deliberately absent on the embedding boundary (ADR-0017 decision 5). The boundary is documented in full here, including exactly what the pattern pass does and does not cover.
Fail closed Frozen Pydantic models with extra="forbid"; unconfigured provider raises a typed error; no cross-provider embedding fallback, ever.
Real data only No metric value is written into any document by policy. Numbers come from generated eval artifacts or live badges.
No in-process state shadowing persisted state SQLite is the durable truth; the BM25 index is rebuilt at open (ADR-0002).

Data model

Frozen Pydantic v2 models with extra="forbid": Document, Chunk (carrying content_hash, document_id, chunk_index, and char offsets start/end), RetrievalResult, and Citation. Confidence thresholds are named constants rather than inline literals. See the contracts reference.

Persistence

One index directory per collection: a LanceDB table for vectors and chunk refs, SQLite for documents, chunks, offsets, and the ingest state that makes re-indexing incremental. Content-hash dedup on write.

Two consequences worth knowing before you deploy anything:

  • SQLite here is content-bearing data, not just an index. It holds your document text. Deletion behaviour, file permissions, backup scope and retention are product decisions, not operational details to settle later.
  • Deleting a collection means deleting both files — its .sqlite3 and its .lance sibling. Neither is inferable from the other's absence, and ADR-0011 covers the failure mode where they disagree.

Where the decisions are recorded

Every irreversible decision behind this page gets an ADR — a short record of what was decided, what alternatives were rejected, and why (see Concepts if that's a new term). The ADR index is the authoritative list. The ones that explain the most about how retrieval behaves:

  • ADR-0002 — why SQLite is the truth and BM25 rebuilds at open.
  • ADR-0005 — rank-based RRF and why rerank scores are sigmoid-normalized.
  • ADR-0007 — why BM25 is still the default even though hybrid measures better.
  • ADR-0008 — why a hybrid search against a vector-less collection is an error rather than a silent fallback.