How RNSR DocDB Works

A plain-English guide to the system that turns a pile of documents into a database an AI can interrogate — with exact numbers, verified quotes, and a full audit trail.

1. What DocDB is, in one paragraph

DocDB is the heart of RNSR. It reads your documents once — PDFs, Word files, spreadsheets, emails, plain text — and packs everything it finds into a single database file called corpus.db. That file contains the complete original text of every page, plus useful extras layered on top: every table converted into a real, queryable database table; a full-text search index; and a machine-built "table of contents" (the manifest). When you ask a question, an AI model doesn't just read the documents — it writes small Python programs against that database to look things up, count, add, and cross-reference. The final answer must come with supporting quotes that the system checks, character by character, against the original text.

The core idea AI models are good at judgment ("is this clause a termination clause?") but unreliable at arithmetic and exhaustive counting. DocDB splits the work: the model handles the judgment calls, and plain code and SQL handle everything countable — sums, dates, joins, exact quotes. Nothing that can be computed exactly is ever left to the model's memory.

2. The problem it solves

Suppose a lawyer has a matter file: a master services agreement, two amendments that override parts of it, forty invoices, a chain of breach letters, and a superseded draft with the wrong numbers in it. Now they ask: "What is the total value of all unpaid invoices?"

The two standard AI approaches both struggle here:

ApproachWhat goes wrong
Stuff everything into the model's context window A modest matter file already exceeds what a model can hold at once. Even when it fits, models make silent arithmetic slips when summing forty numbers by reading them.
Retrieval (RAG): fetch the "top 10 most relevant" snippets Forty invoices can't fit in ten snippet slots, so aggregation questions are structurally impossible. Retrieval also can't prove a clause is absent, and it happily returns the superseded draft's numbers.

DocDB's answer: don't make the model read everything, and don't gamble on fetching the right snippets. Put the documents in a database and let the model query it. Summing forty invoices becomes one SQL statement that is exact by construction. In RNSR's benchmarks, this is precisely where DocDB wins: it went 24/24 on multi-document matter files while every retrieval variant missed all of the aggregation questions.

3. The big picture: two phases

Everything in DocDB happens in one of two phases. Ingestion runs once per set of documents and produces the database file. Querying runs every time you ask a question, and only needs that file.

Your documents PDF · Word · Excel PowerPoint · email Markdown · text PHASE 1 Ingestion parse · extract tables validate · index · manifest runs once, offline corpus.db one self-contained SQLite file PHASE 2 Query loop AI writes Python + SQL against the database runs per question "What's the total of unpaid invoices?" Answer + verified quotes
Figure 1 — Documents go in once; the resulting database answers many questions.

A key design commitment: the database only ever adds views on top of the original text — it never replaces or throws any of it away. The full text of every page is retained inside corpus.db, and every index entry, table row, and search hit can be traced back to an exact character range on an exact page of an exact document.

4. Phase 1 — Ingestion: from files to a database

You run ingestion with one command:

rnsr ingest report.pdf exhibits.docx ledger.xlsx -o corpus.db

Behind that command is a pipeline. Here is what happens to each file, step by step:

1. Look at the file type and pick the right parser for it 2. Parse the document extract text, page by page, plus any tables found (with their page positions) Scanned page? (no text layer) A vision model transcribes it — no separate OCR engine needed 3. Cut text into chunks following headings where possible, else ~1,500-character windows with overlap 4. Turn each document table into a real SQL table with typed columns (numbers as numbers) 5. Validate: do the rows add up to the stated totals? yes → trusted no Re-extract (vision model) and re-check. Still failing? Mark table "untrusted" in the manifest — never hidden 6. Finish the artifact build the full-text search index · write the manifest (contents list) · freeze source data so nothing can ever alter the original text Safety detail The file is built under a temporary name and only renamed to corpus.db when every step succeeds.
Figure 2 — The ingestion pipeline. Every file passes through these steps; the result is one database file.

The steps in plain English

Pick a parser. PDFs go through a high-quality PDF parser (Docling), or a faster one when speed matters. Office documents (Word, Excel, PowerPoint and friends) go through an office-format converter. Markdown, plain text, and email files are handled directly. The choice is made purely by file extension.

Extract the text. The parser produces the full text of every page, recording exactly where each piece came from. If a page is a scan with no digital text, a vision-capable AI model reads the image and transcribes it.

Cut it into chunks. Search works better on passages than on whole documents, so the text is cut into pieces — along section headings when the document has structure, or into ~1,500-character windows with a 200-character overlap when it doesn't. Every chunk remembers its document, page, and exact character positions.

Convert tables. This is DocDB's signature move. A table spotted in a PDF isn't kept as a picture or a blob of text — it becomes an actual SQL table with properly typed columns, so "$1,250.00" is stored as the number 1250.00 that SQL can sum. The original cell text is kept alongside in shadow columns, and every row records the page and position it came from.

Validate the tables. Table extraction from PDFs is error-prone, so DocDB checks its own work: if a table has a "Total" row, do the extracted numbers actually add up to it? Tables that pass are marked trusted. Tables that fail get a second chance through a vision-model re-extraction; if they still fail, they are honestly labelled untrusted in the manifest so the query phase knows to fall back to the raw text instead.

Index, describe, and freeze. Finally, a full-text search index (SQLite FTS5, with BM25 ranking) is built over the chunks; a manifest is written — a machine-generated inventory listing every document, every table, its columns, and its trust status; and database triggers are installed that make all source data immutable. From this moment, nothing — not even the AI at query time — can modify or delete the original text and tables.

Deliberately boring by default Ingestion is deterministic and uses no AI at all unless you opt in (with --llm) for scanned-page transcription and table re-extraction. Same input, same output, every time. AI-generated summaries never go into the manifest — it contains only facts a machine measured.

5. Inside corpus.db

Everything lives in one ordinary SQLite file. You can open it with any SQLite tool. Here is its anatomy:

corpus.db — one SQLite file Retained source text (the ground truth — never modified, never evicted) documents — one row per file: path, content hash, page count, parser used doc_text — the complete text of every page, with character offsets Search layer chunks — text passages with doc, page, heading path, and character range fts_chunks — full-text search index (FTS5) Extracted data tables t_invoice12_001, t_report_004, … one SQL table per document table: typed columns + raw-text shadows + page/position provenance on every row Manifest (the map) manifest — corpus-level facts: documents, chunk stats, software versions manifest_tables — per-table schema, checks, trust status Working layer (grows over time) annotation_log + annotation columns — AI judgments saved as real columns (audited) vec_chunks — embeddings, built lazily on first use Everything above the working layer is frozen by database triggers after ingestion. Every row in every layer traces back to a document, page, and character range.
Figure 3 — The anatomy of corpus.db. Indexes and extracted tables are extra views over the retained text, never replacements for it.

Two properties are worth underlining:

Provenance everywhere. A chunk knows its document, page, and character range. A table row carries the page and bounding box it was extracted from. This is what makes verified quotes (section 9) possible — any claim can be walked back to the exact original characters.

Frozen source, writable margins. After ingestion, triggers reject any insert, delete, or update against the source columns. The only things that can ever be written later are additions in the working layer: annotation columns the AI adds (section 8), the log recording how they were made, and cached embeddings. The originals are tamper-proof.

6. Phase 2 — Answering a question

Now the interesting part. You ask:

rnsr query corpus.db "What was FY2023 segment revenue?"

RNSR does not paste your documents into an AI prompt. Instead, it runs a loop — called the RLM loop (recursive language model) — in which a "root" AI model is given the manifest (the map of what's in the database) and a Python workspace connected to corpus.db. The model investigates by writing code; the system runs the code and shows the model what came back; the model writes more code; and so on until it is confident enough to commit to an answer.

Question arrives root model receives it + the manifest Root model writes a snippet of Python / SQL e.g. search("segment revenue"), db.execute(...) Sandbox runs the code a separate process with no network access, preloaded with: db · doc · manifest · search · semantic_annotate · verify · schema_map · FINAL AI/embedding calls are brokered by the parent process Result goes back to the model query rows, search hits, errors — all visible Confident in an answer, with quotes to back it? not yet — investigate more (max 20 rounds) yes → FINAL(answer, quotes) Verifier: are the quotes really in the source text? (exact string match) quote not found — rejected, back to the loop Answer delivered, with verified quotes + full trace
Figure 4 — The query loop. The model investigates by running code; it can only finish with an answer whose quotes survive verification.

The model's toolbox

Inside the sandbox, the model finds these ready-made tools:

ToolWhat it does, in plain English
dbA live connection to corpus.db. The model can run any SQL — counting invoices, summing columns, joining tables. Triggers ensure it can't damage the source data.
docReads the full original text of any document or page on demand (recently used documents are kept in memory).
manifestThe inventory: what documents exist, what tables were extracted, which are trusted.
search(...)The escalating search ladder — see section 7.
semantic_annotate(...)Applies an AI judgment to every row of a table at once and saves the results as a new column — see section 8.
schema_map(...)Suggests which columns in two different tables mean the same thing (e.g. "revenue_m" vs "net_revenue"). The model must apply the join itself, visibly, so it's auditable.
verify(...) / FINAL(...)Check quotes against the source; commit to a final answer. FINAL requires quotes.
llm_query(...)Ask a smaller, cheaper "sub-model" a focused question about a specific piece of text — many can run in parallel.
Root model vs. sub-models The root model is the smart, expensive one that plans and writes code (e.g. Claude Sonnet or GPT-5-class). Sub-models are cheaper, faster models used in bulk for simple judgments — "does this paragraph mention a guarantee, yes or no?" — with up to 16 running at once. This is the "recursive" in RLM: a language model that delegates to language models.

Why a sandbox?

The model's code runs in a separate process with no network access. It can touch the database and nothing else. When its code needs an AI call (for annotation or search), the request is passed to the parent process, which makes the call on its behalf under strict concurrency and budget limits. If a code cell runs away, the sandbox is restarted. Every code cell, result, and model call is written to a trajectory file — a complete, replayable record of how the answer was reached.

7. The search ladder: cheap first, expensive only if needed

"Find where the documents talk about X" is the bread-and-butter operation, and DocDB refuses to pay for an expensive search when a cheap one will do. search() is a ladder of six rungs, tried from cheapest to priciest. It stops at the first rung that finds hits.

cost per search → Rung 0 — SQL over extracted tables manifest-guided lookups in the typed tables. Free and exact. Rung 1 — Pattern match (grep) literal / regex scan of the chunks. Free. Rung 2 — Full-text search (FTS5 / BM25) ranked keyword search over the index built at ingestion. Free. Rung 3 — Sub-model query expansion a cheap AI suggests synonyms and related terms, then re-searches. Small cost. Rung 4 — Semantic embeddings meaning-based similarity. Built lazily on first use, then cached in the database. Rung 5 — Exhaustive sweep (opt-in only) a sub-model reads every chunk. Never runs automatically — a cost estimate comes first. escalate only if nothing found automatic (rungs 0–4) manual
Figure 5 — The search ladder. Cheapest rungs first; the expensive sweep never runs without an explicit request and a cost estimate.

Three details matter:

Every rung resolves to real text. No matter which rung produced a hit, the result carries provenance — document, page, character range — pointing into the retained source text. A hit is never just a score; it's a place you can look.

Embeddings are lazy. Vector embeddings (the technology behind "semantic" search) are not built at ingestion. The first time a question actually needs rung 4, the chunks are embedded and the vectors are cached inside corpus.db itself — so the cost is paid once, and only if ever needed.

The sweep asks permission. If rungs 0–4 all come up empty, the ladder does not silently read every chunk with an AI. It returns a cost estimate, and the root model must explicitly decide the question is worth it. This is how DocDB can prove absence — "there is no such clause" — by actually enumerating everything, but only deliberately.

8. Semantic annotation: AI judgments become database columns

Some questions need a judgment applied to every row: "How many of these 200 contract clauses are termination clauses?" A naive approach asks the AI to read all 200 and count — slow, expensive, and error-prone.

semantic_annotate does it differently: it sends each row's text to a cheap sub-model in parallel batches, collects the yes/no (or label) for each, and writes the results back as a new real column on the table. Now counting is just SQL:

# One batched AI pass adds the column...
semantic_annotate("t_contract_003", "is_termination",
                  "Is this clause a termination clause? Answer yes or no.")

# ...and the count is exact, forever after:
db.execute("SELECT COUNT(*) FROM t_contract_003 WHERE is_termination = 'yes'")

Three guarantees come with it:

GuaranteeMeaning
IdempotentRunning the same annotation twice (same table, column, prompt, and model) is a no-op — the work is never paid for twice.
AuditedEvery annotation is recorded in annotation_log: which model, which prompt, when, and how many rows failed.
PersistentThe column lives in corpus.db. The next question that needs the same judgment finds it already there.

In complexity terms: reasoning that would take the model O(N²) reading becomes O(N) cheap calls plus one exact SQL query.

9. Verified quotes: the answer must show its receipts

In DocDB mode, the model cannot simply declare an answer. The FINAL call requires supporting quotes, and a verifier then checks each quote by exact string matching (after light whitespace normalisation) against the retained source text.

Why this matters This is the mechanism that resists confabulation. In the CUAD legal benchmark, DocDB's edge over the plain-reading baseline came almost entirely from absent-clause questions — it declined to "find" plausible-sounding clauses that weren't actually in the contract, because a fabricated quote cannot pass an exact string match.

10. Budgets and safety rails

Every query runs under hard caps, so a confused run costs a bounded amount and then stops:

Limit (defaults)Purpose
20 loop iterationsThe model gets at most 20 rounds of "write code, see result".
300 sub-model callsCaps bulk annotation and search-expansion spend.
600 seconds wall timeNo run hangs forever.
$2 of model spendThe hard financial ceiling per question.
16 concurrent sub-callsParallelism without stampeding the API provider.

Additional rails: damping against pointless re-verification loops, a variable-recovery fallback (if the loop dies but the model had already computed the answer into a variable, it is salvaged), sandbox restart on runaway code cells, and timeouts on every root-model call. When many questions share one batched loop (the answer-csv path), the caps scale sub-linearly — each extra question adds only half a question's budget — so a confused batch can't burn the full per-question budget times the batch size.

11. Why it gets cheaper over time

DocDB is built for working sessions — many questions against the same documents — and several kinds of work are paid once and reused:

Ingestion parse, tables, index paid once per corpus Embeddings (rung 4) built on first use cached in corpus.db Annotations AI judgments as columns persist across questions Batched loops 8 questions share one exploration of the corpus Measured: a second pass over the same corpora answered at half the cost, with a median of one model call per question.
Figure 6 — Four kinds of work that amortize across questions.

There is also a corpus cache: when you point answer-csv at a folder of documents, the resulting corpus.db is keyed by a hash of the files' identities, so re-running never re-ingests unchanged documents. Long runs checkpoint their partial answers, so an interrupted job resumes instead of re-paying.

12. When to use DocDB (and when not to)

RNSR's own benchmarks are candid about this — DocDB is not always the right tool.

Use DocDB when…
  • The matter spans many documents or exceeds a context window
  • Answers are computed over sets: totals, counts, chronologies
  • Absence must be provable ("there is no such guarantee")
  • The file contains superseded drafts or overriding amendments
  • You'll ask many questions of the same documents
  • Citations must survive independent scrutiny
Just read the document into context when…
  • It's a single document under ~150k tokens
  • It's a one-off question
  • The answer is lookup/reading, not computation
  • No independently verifiable citation is required

Measured accuracy is at parity in this regime, and simple stuffing is cheaper.

One line: read a document → use the context window; interrogate a matter → use DocDB.

Where to go deeper

ResourceWhat it covers
docdb-rlm-design-spec.mdThe authoritative design document — every decision above, with rationale.
README.mdBenchmark results, install instructions, usage examples.
HANDOFF.mdThe practical runbook for answering question sets over a matter folder.
rnsr/ingest/, rnsr/db/, rnsr/env/, rnsr/harness/The source code for ingestion, the database schema, the query environment, and the RLM loop respectively.