Taha @ MJTHD
/
GitHub
Blog
·3 min read

Dokki: a RAG pipeline in three planes

Documents in, grounded answers with sources out — split into a data, control, and query plane on the existing MJTHD stack.

airagarchitecturedokki

Dokki is a RAG pipeline: documents — PDFs, eBooks, internal notes — go in, and out come LLM answers that are grounded against exactly those sources and cite them. No hallucinating about knowledge that was never in the corpus.

The interesting part isn't the LLM, it's everything around it: how does a document reliably reach a searchable, versioned state — and how do you prevent a broken import from poisoning production? Dokki answers that with three NestJS microservices, cut by responsibility.

Three services, three planes

ServicePlaneStateResponsibility
IngestData Planestateless, scalabledocument → blob → hash → parse → chunk → embed → vectors
KnowledgeControl PlaneSingle Source of Truthregistry, dedup, versions, snapshots, quality gate, promotion/rollback
RAG / InferenceQuery Planestatelessquestion → embed → top-k from active snapshot → prompt → answer with sources

The separation is the central design decision. The ingest only produces chunks and numbers — it never decides whether a result is "good". That decision belongs to the Knowledge Service alone. And the RAG service is pure read side: it only ever queries the active state.

How it fits together

Decoupling runs over NATS JetStream: the ingest worker listens on ingest.requested, reports back ingest.completed, and a successful promotion emits snapshot.activated, upon which the RAG service invalidates its cache. Each collection (a bounded knowledge corpus) gets its own subject namespace with its own token.

The stack

Dokki invents no infrastructure; it builds on what already runs in the MJTHD cluster:

  • CNPG / pgvector — vectors and metadata, HNSW index on halfvec
  • NATS JetStream — event bus, 3-replica, per-collection token
  • MinIO — original blobs, content-addressed by their hash
  • Ollama (bge-m3) — embeddings, as a shared deployment for ingest and RAG
  • Claude — the answer LLM, with the native Citations API for sources
  • k3s + ArgoCD — GitOps deployment like the rest of the stack

Anthropic has no embeddings API — so Ollama handles text-to-vector, and Claude only answer generation. Two different jobs, two different models.

The one idea that carries everything

Re-ingest never produces an update, always a new immutable version. A state goes live only through active promotion.

Almost the entire rest of the architecture follows from this: versions, snapshots, rollback as a mere pointer flip, and a quality gate that keeps bad states from going live at all. The next posts in this series take exactly these pieces apart — starting with why a bad ingest can't break Dokki.