Projects

Project Details

Paper Trail — Adversarial Evals for Document AI 2026

Paper Trail round-by-round results: recall, precision and false-alarm rate over rounds beside a recall-by-error-type heatmap

Project Overview

Static evals of document AI measure a fixed test set, so they only report how a model does on the errors someone already thought to write down. Paper Trail runs a deterministic simulated economy instead — contracts, invoices and bank statements with errors injected from a tagged taxonomy — and puts an adaptive adversary in the loop that reweights injections toward whatever the auditor misses, then lets an LLM propose entirely new error types.

Each round the simulator generates honest economic activity, the adversary picks which errors to inject and at what difficulty, and an agentic LLM auditor investigates the rendered documents through four read-only tools before emitting structured findings. A scorer matches findings against a canonical ground-truth event log; the adversary updates a Thompson-sampling posterior over 24 arms, rewarded when the auditor misses.


The Headline Is Negative — And That's the Point

Across 25 audited rounds the adversary never found a recall blind spot. The adaptive arm scored 0.959 mean recall against 0.960 for the static control on an identical seed — no measurable effect, and no error type collapsed.

  • No reward signal: the auditor missed 4 of 94 scored injections. Thompson sampling over 24 arms cannot concentrate on a ~4% base rate — final posterior means span 0.12–0.29, so sampling stays effectively uniform.
  • The exploration floor eats the budget: 24 arms forced once every 5 rounds is 4.8 mandatory slots out of 10 per round — ~48% of every plan is exploration by construction.
  • Wrong axis: reward is 1 iff the auditor missed. Recall is saturated at ~0.96 while precision is the real weakness — 0.658 mean, 1–10 false alarms per round. A miss-based objective is structurally blind to it.
  • LLM-proposed errors didn't help: two novel templates passed the schema and both were caught every time they were injected — neither beat any hand-built type.

Two Results That Were Not Results

Rounds 10 and 13 recorded recall 0.00 with zero findings — exactly the headline this project was built to find, and false. The tell was that both rounds also missed error types the auditor catches reliably. Re-running round 10 against its saved documents returned 24 findings where the original returned 0.

Root cause: the final answer must carry every finding in one JSON object and max_tokens_per_turn was 4096. A round with many findings truncated the JSON mid-object, the retry truncated identically, salvage returned an empty list, and the scorer faithfully recorded recall 0.00 — which the adversary then learned from. Uncorrected, the data claimed an LLM-invented error type defeated the entire hand-built taxonomy (recall 0.55, lowest of any type); excluding the two broken rounds it is 1.00.

Fixed by retrying the final answer with a 16k ceiling on stop_reason == "max_tokens", persisting raw text whenever a report parses empty, and three regression tests. An earlier bug of the same shape: ground truth tags transaction ids, but transactions have no rendered document — so every payment-side error was unmatchable regardless of auditor performance.


What This Implies for Reconciliation Systems

  • The difficulty axis is inverted: pooled recall was 0.89 on blatant errors and 1.00 on subtle ones. Subtle errors contradict a specific other document, so checking them is a well-defined cross-reference; blatant ones are large deviations that still look like business variance.
  • Confidence is not a triage signal: mean confidence ran 0.89–0.97 on true positives and 0.75–0.88 on false positives — overlapping enough that no threshold usefully partitions them.
  • Precision is the exploitable surface: recall sits at ~0.96 while precision ranges 0.47–1.00, with 1–10 false alarms per round against ~110 clean documents. Alarm fatigue needs its own metric.
  • A constrained DSL beats generated code: both accepted proposals scaled a line-item price and the invoice total; the interpreter recomputes totals, so the operations compounded and produced the most obvious tell in the corpus. The model reasoned about operations individually but not about how they compose — and the effect stayed bounded, inspectable and recorded, which generated code would not have been.

Engineering & Rigor

  • Deterministic by construction: every random choice flows from one seeded numpy Generator passed explicitly — no global state, fixed iteration order. Identical config and seed produce byte-identical round logs, asserted by test. That is what makes a negative result trustworthy, and what makes resume-after-failure sound; both experiments were interrupted and resumed mid-run.
  • Ground truth is sacred: injectors mutate a deep copy and never the original, every change is tagged with type, difficulty, parameters and affected documents, and an operation that would produce no observable change raises rather than tagging a guaranteed false miss.
  • Mutation DSL instead of LLM-generated code: proposals are validated against a closed pydantic schema — five whitelisted operations, enumerated fields, range-bounded parameters — and interpreted with no eval, no dynamic import, so unknown operations are unrepresentable rather than merely rejected.
  • Tested: 250 tests with no network, including 39 guardrail rejection fixtures covering fabricated ops carrying shell commands, field names reaching for __class__ and path traversal, and injection-flavoured memo payloads.
  • Everything traces to an artifact: every number above resolves to a committed file under runs/, browsable through a static Next.js dashboard that puts each finding beside the documents it cites.

Project Information

Role

Solo Developer

Type

AI Evaluation / Research Engineering

Stack

Python, numpy, pydantic, Claude API, pytest

Dashboard

Static Next.js viewer over committed run artifacts

Methods

Thompson sampling over 24 arms, agentic tool-loop auditor, schema-validated mutation DSL

Scale

25 audited rounds across two experiments, 250 tests

Links
GitHub Repository