Inspiration

Every month, accountants tick bank lines against ledger lines by hand. Most of that work is mechanical, and the rest is detective work: a mangled bank memo, a transposed digit, a duplicate check, an unexplained fee. Most "AI bookkeeping" demos let the model decide what gets posted. We wanted the reverse: the model explains, code decides, and a human approves. An auditor could sign off on that.

What it does

Bridge reconciles a bank statement against the general ledger's cash account and produces a reconciliation statement whose difference closes to 0.00.

  • Matches 112 bank lines to 114 GL lines: exact, check number, one-to-many (subset sum), rounding, and fuzzy matches on messy descriptors like ONLINE PMT TECHSOLNS INC CONF#780589.
  • Classifies and explains every leftover item (bank fee, interest, transposition, duplicate GL entry, outstanding check, deposit in transit, NSF pair, unidentified receipt).
  • Drafts a balanced journal entry for each exception, checked against the chart of accounts.
  • Closes only what policy allows. Rounding write-offs and NSF pairs auto-resolve, and timing items become reconciling items. Everything else goes to an approval inbox (J/K to move, A/R/E to approve, reject or edit) with the reason, the evidence and the draft entry.
  • Learns from approvals. Tick "remember this rule" on a bank fee, and the next run resolves it as "Applied rule #1 (approved by …)".
  • Writes the report. Nemotron Ultra writes the narrative from numbers that code has already computed. A number checker rejects any figure the model invents.
  • Guardrails you can see. PII is masked before every model call (a "sent to model" toggle shows exactly what the model saw), and a planted prompt injection in BANK074 is flagged and stays data.
  • Live run trace. Each pipeline stage streams its model, tier, latency and tokens, and an append-only audit log records every agent and human action.

On the demo dataset, one run takes the raw difference from 864.09 to 106 matched, 2 auto-resolved, 2 reconciling items and 5 items for a human. After 5 approvals the statement reads 0.00.

How we built it

Three Nemotron tiers on Nebius Token Factory, routed by difficulty:

Tier Model Job
Nano NVIDIA-Nemotron-3-Nano-30B-A3B Fuzzy match on ambiguous candidates, classify leftovers
Super nemotron-3-super-120b-a12b Tool-using resolver agent (search_bank, search_gl, get_policy, list_accounts, find_rules, propose_entry) plus a critic
Ultra Nemotron-3-Ultra-550b-a55b Final retry on the hardest items, narrative report

Pipeline (an Inngest durable workflow, reconcile-run): load → guard → rule match → fuzzy match → classify → resolve → review → decide → report. The review loop runs code checks (does the entry balance, do the accounts exist, are the cited IDs real, does it close the delta) and then a Super critic. On failure it retries with the critic's feedback, retries once more on Ultra, and only then escalates.

Design rules we held to:

  1. Code first, agents second. Deterministic Python handles matching, sums and policy. The LLM gets only the judgement calls.
  2. The LLM never does arithmetic. Every amount is a Decimal computed in code, and code re-checks every claim the model makes.
  3. Gate on facts, not confidence. An item auto-resolves only if its kind is on the policy list and the code checks pass. Model confidence is display-only.
  4. Untrusted text stays data. Memos are NFKC-normalised, PII-masked, scanned for injection, and wrapped in <untrusted> tags.
  5. Workflow outputs carry IDs and counts only, so no transaction text is stored in the orchestrator.

Stack: FastAPI + Pydantic + psycopg 3 (Python 3.12, uv) · Inngest Cloud · Supabase Postgres + Auth · React + Vite + Tailwind + shadcn/ui + TanStack Query + Recharts · one Docker image serving UI andAPI · deployed on Render · inference on Nebius Token Factory.

Evals: a harness scores every pipeline against a hidden answer key that runtime code never reads. ⟨650⟩ backend tests and ⟨125⟩ frontend tests run, and the test suite blocks any real LLM call.

Results (live, Token Factory)

Pipeline False auto-resolutions Escalations caught Draft entries correct After approvals Latency Cost / run
Tiered (Nano → Super → Ultra) 0 5/5 6/6 0.00 45 s $0.018
Super only 0 5/5 6/6 0.00 22 s $0.018
Nano only 0 5/5 6/6 0.00 320 s $0.009

Match precision and recall: 111/111. Kind accuracy: 114/115.

What the models add over a code-only floor: 4 extra fuzzy matches, 4 kind labels, and 8 fewer human reviews (13 → 5). The code-only floor also scores 0 false auto-resolutions, so safety doesn't depend on the model. The models remove toil.

Challenges we ran into

  • Tool calling on reasoning models. We spiked native OpenAI-style tools against a JSON action loop on Super. Native tools emitted valid calls 10/10 and picked the right tool 10/10, at about 1.0 s median per turn (vs 8/10 and 1.8 s), so the resolver uses them, with Pydantic validation and a 6-turn cap.
  • Keeping models from making up numbers. The first report drafts made up totals. We added a number checker (digits, number words, k/m suffixes) with one repair attempt, then a code-written fallback.
  • Ambiguous matches. Same-amount rows with several partners. The matcher widens the date window one day at a time and pairs only unique partners. Everything else goes to Nano with a closed candidate list.
  • Prompt injection in financial data. BANK074 carries an "ignore previous instructions" memo. It's flagged, quoted as data, and can never create or trigger a learned rule.
  • Deploy churn. Railway paused signups mid-hackathon, so we moved to Render with Inngest Cloud in one day.

Accomplishments that we're proud of

  • Zero false auto-resolutions across every model tier. That guarantee comes from code, not from the model.
  • The statement closes to 0.00, end to end, live.
  • A tiered agent run costs under 2 cents.
  • A learning loop with strict safety rules (exact pattern, counterparty, direction, amount cap, identical entry; never for unknowns, duplicates or injection-flagged rows).

What we learned

  • Smaller models do the cheap judgement calls well when code narrows the choices to a closed list. Super was the sweet spot for tool use.
  • In finance, being able to explain a result beats raw accuracy. Every item carries its reason, its evidence and the exact text the model saw.
  • Evals against a sealed answer key kept us honest. They also showed that the models' value is fewer human reviews, not safety.

What's next for Bridge

PDF statement upload via Nemotron Super VL, payee lookup for unidentified receipts, retrieval of similar past resolutions (pgvector), multi-account and multi-currency support, and ERP connectors (QuickBooks, Xero, NetSuite) to post approved entries.

Built With

Share this project:

Updates

Submission history