Skip to content
Yoann Frayce
All systems
July 2026Dell × NVIDIA AI Hackathon · Seattle

Obligation Ledger

Air-gapped retrieval where the evidence check is code

A local contract-obligation service where every extracted value must quote its source page, the quote is verified in Python, and every derived date is discarded from the model and recomputed.

Sole author of this component — one of four on the productView source ↗View architecture
Tests
128
including the hallucinating-model ablation
Ablation result
approval blocked
with a hallucinating model, verification catches the invented date
Retrieval on the lease fixture
7 of 7
material clauses retrieved by lexical search alone
Network calls leaving the machine
0
weights loaded locally only; one outbound connection, to the local model

Figures with a dotted underline carry their source. Hover one to see whether it was verified in code or reported in project documentation.

Context

The situation

Contract obligations — renewal dates, notice windows, escalation caps — are buried in prose and in filled form templates. Missing one has a direct financial consequence, which is exactly why nobody trusts an unaudited model to find them. The build had a hard constraint on top: a one-day hackathon where everything had to run fully offline on a single Dell Pro Max with an NVIDIA GB10, with no cloud inference of any kind.

The problem

Retrieval-augmented extraction fails in a specific way: the model returns a plausible value with a plausible-looking quote, and nothing checks that the quote exists, that it is on the page claimed, or that the arithmetic derived from it is right. The output is confident and unfalsifiable.

What I built

Make each of those checks a piece of code with a test. Verification normalises the text and matches the claimed quote against the claimed page, failing a quote that is real but located elsewhere. Date arithmetic is removed from the model entirely and recomputed from verified inputs. And an adversarial model adapter that deliberately hallucinates is wired into the test suite, so the guardrail is proven rather than asserted.

A model reading a contract will confidently produce a notice deadline, and if it is wrong someone misses a window. This service is built on the bet that the engineering around the model is what makes its output trustworthy. Three rules are enforced in code rather than in interface copy: every value must quote the contract and that quote is verified against the page it claims; every derived date is discarded on principle and recomputed in Python with its formula stored alongside; and the service never commits anything, returning a hard "cannot approve" whenever any field fails. It runs fully air-gapped against a locally served open-weights model, on a single on-premise box.

Architecture

How it fits together

  1. Contract PDFinput

    page numbers preserved

  2. Page-aware chunkingdeterministic

    offsets and overlap

  3. Hybrid retrievaldeterministic

    BM25 + local vectors, fused by rank

  4. Local modellanguage model

    proposes values and quotes

  5. Quote verificationguard

    matched against the claimed page

  6. Date arithmeticguard

    recomputed in code, never trusted

  7. Approval gateoutput

    blocked if any field fails

No weights are downloaded and no request leaves the machine. The only outbound connection is to a locally served model endpoint.

Key decisions

The choices that shaped it

Each of these was a fork in the road where the obvious option would have produced something that looked the same and behaved worse.
  1. 01

    A real quote on the wrong page fails

    Verification normalises Unicode punctuation, ligatures, non-breaking spaces and soft hyphens, then tries exact substring match against the claimed page, falling back to a bounded fuzzy match anchored on the longest common block and capped by both a similarity threshold and a length ratio. A quote that exists elsewhere in the document fails, with a reason naming the page it was actually found on.

    verification.py

  2. 02

    Filled form templates break the naive check

    A lease template expresses values as ticked options, so “☐ Rent will NOT be increased” is a genuine quote of a value that was not agreed. A naive "the quote is in the document" check passes it. Verification resolves the nearest checkbox governing the quoted span and fails an unticked one.

  3. 03

    The product says QUOTE MATCHED, not VERIFIED

    The status a matched value carries is deliberately narrow, and the legend sits in the interface rather than in the pitch: a matched quote means the value appears in the text it cited. It does not mean the model cited the right clause. That distinction is the difference between a check that can be trusted and one that quietly overclaims, and putting it in front of the reviewer rather than in a footnote is what makes the approval step meaningful.

  4. 04

    The model never verifies its own quote

    Asking a model to check its own output is asking the same failure mode to audit itself. Verification is deterministic Python with no model in the loop, which is also what makes it testable.

  5. 05

    Reciprocal rank fusion, for a stated reason

    BM25 scores and cosine similarities live on different, document-dependent scales, so any fixed weighting between them is a guess. Ranks are directly comparable, so the two retrievers are fused by rank rather than by score.

    retrieval.py

  6. 06

    Arithmetic leaves the model entirely

    The model reads a term-end date and a notice duration off the page; Python computes the deadline. Month arithmetic clamps to short months, and every computed value stores its formula and the ids of the verified obligations it was derived from.

    date_math.py

  7. 07

    An adversarial adapter proves the guardrail

    A model adapter that deliberately invents values is part of the test suite. The ablation asserts that verification catches the invented date and that approval is blocked. A guardrail with no failing case is a claim, not a control.

  8. 08

    Degrade loudly, not silently

    If the embedding model fails to load, the service logs and falls back to lexical retrieval rather than failing outright — which means the absence of errors proves nothing. The health endpoint reports which retrieval mode is actually active, and that is the thing to trust.

Evidence

What it actually looked like

The approval interface showing an extracted party name with a QUOTE MATCHED badge, the sentence it came from, and its page and character offsets. The legend above reads: QUOTE MATCHED means the value appears in the text it cites, not that the model cited the right clause.

The status is deliberately narrow, and the legend is in the product rather than in the pitch. The contract is a synthetic fixture.

Interface
The four of us at the Dell × NVIDIA hackathon in Seattle. In the middle, the Dell Pro Max with NVIDIA GB10 that the whole system had to run on.

The four of us, and the machine. That box in the middle is the Dell Pro Max with GB10 — every model call, every embedding and every byte of the register stayed on it.

Hardware

Measured

Every figure, with its source

Tests
128
including the hallucinating-model ablation
Ablation result
approval blocked
with a hallucinating model, verification catches the invented date
Retrieval on the lease fixture
7 of 7
material clauses retrieved by lexical search alone
Network calls leaving the machine
0
weights loaded locally only; one outbound connection, to the local model
Obligation types
11

Figures with a dotted underline carry their source. Hover one to see whether it was verified in code or reported in project documentation.

My contribution

Sole author of the retrieval and extraction service. Three teammates owned the frontend, the main application backend, the dataset and the local model runtime.

What I owned

  • PDF ingestion with page numbers preserved, and page-aware chunking with offsets
  • Hybrid retrieval: lexical BM25 and local dense vectors fused by reciprocal rank fusion
  • Structured obligation extraction against a typed schema shared with the main backend
  • Deterministic source-evidence verification, including the filled-form checkbox case
  • Deterministic date arithmetic with stored formulas and input provenance
  • Real, fake and deliberately-hallucinating model adapters, and the ablation test built on the last one
  • Two integration paths — an HTTP service and an in-process module contract — sharing one pipeline

What I did not

  • The frontend and the approval interface
  • The main application backend, the committed ledger and its hash chain
  • The local model runtime and the contract dataset

Stack and limits

Built with

  • Python 3.12
  • FastAPI
  • Pydantic
  • PyMuPDF
  • sentence-transformers
  • BM25
  • SQLite
  • NumPy
  • Docker (multi-arch)
  • pytest
  • ruff

Techniques

  • Hybrid lexical + dense retrieval
  • Reciprocal rank fusion
  • Deterministic evidence verification
  • Adversarial model adapters for guardrail testing
  • Air-gapped local inference

What it is not

  • One component of a four-person product. I did not build the interface, the committed ledger or the model runtime.
  • Evaluated on two synthetic fixtures — a prose services agreement and a filled lease form — not on a real contract corpus.
  • Scanned documents with no text layer are rejected rather than OCR’d. Out of scope by design.
  • Ambiguous day-first versus month-first dates are read as US format, which is a decision worth revisiting against real data.

What carried forward

The pattern that keeps recurring: the model is good at reading and terrible at being accountable, so give it the reading and keep the accountability in code. Every rule here is a function with a test, and the adversarial adapter is what turns "we verify quotes" from a sentence in a README into something a reviewer can run.