Skip to content

RAG on Ironflow — the series

Most RAG tutorials build one big app that does everything, then leave you to guess which part matters. This series does the opposite. Each part is a small, standalone app that teaches one slice of a production retrieval system. Each one runs from a fresh clone.

Read them in order or pick the one that matches the problem in front of you.

Why build RAG on an event-driven platform

A vector index is a read model. It is derived data — you can always rebuild it from the source documents. The trouble is that most RAG systems treat the index as the source of truth, so a bad chunking change or a swapped embedding model means a full, expensive re-crawl.

On Ironflow the chunks are events, and the index is a projection of those events:

Documents flow through an ingest workflow into an immutable stream of rag.chunk.embedded events. A projection replays those events into a vector store, which the ask stage queries to answer a question. The index can be rebuilt from the events without re-paying the embedding API.corpusingest workflowevent streamprojectionvector storeask*.md*.pdfreadchunkembed (durable step)rag.chunk.embeddedrag.chunk.embeddedrag.chunk.embeddedexternalhandlervectors+ textretrieve+ answerdurable, idempotentimmutable, replayablerebuildablequeryablerequest/responsereplay — rebuild the index without re-paying the embedding API

Three things fall out of that shape:

  • Rebuildable. Change the store, the schema, or the ranking, then replay. The embeddings ride in the events, so a rebuild costs no API calls.
  • Auditable. Every chunk in every answer traces back to the event that created it, with a timestamp.
  • Recoverable. A crash halfway through a 10,000-document ingest does not corrupt the index. The run is reclaimed once the crashed worker’s concurrency lease expires, resumes from the last checkpointed step, and every emit carries a content-derived idempotency key, so anything a replayed step re-emits is dropped and the final index is exactly right. The wait before it resumes is longer than you would guess. Part 1 walks it: Kill it mid-ingest.

The parts

#PartWhat you buildStatus
1rag-coreIngest → chunk → embed → store → retrieve → answer. Zero Docker: the index is a local SQLite file with sqlite-vec.Ready
2rag-freshnessChange detection, deletes and updates, re-indexing, and schema-versioned chunk events with upcasters.Coming
3rag-evalsA golden set, a shadow index, and a promote-or-rollback saga so a bad index never goes live.Coming
4rag-tenantsPer-tenant corpora, hard retrieval boundaries, and per-tenant keys.Coming
5rag-agenticMulti-step retrieval, query rewriting, reranking, and streaming answers.Coming
6rag-opsFailure recovery, observability, cost controls, backpressure — and swapping the vector store for pgvector.Coming

Every part uses pull mode (createWorker). Part 1 uses sqlite-vec to keep the first run free of Docker; the rest use pgvector, and part 6 shows the swap.

Want the full thing today

examples/financial-rag is a complete, production-shaped RAG application: recurring SEC-filing ingest, hybrid search over pgvector and tsvector, an agentic query loop, and an eval gate that blocks a bad index from being promoted. It is bigger than any single part here, and it is the best reference until this series is finished.