Inspiration
Every AR team has a version of this nightmare: a payment lands, the amount matches an invoice down to the rupee, someone clicks "close," and three weeks later finance is untangling a mess because that "match" was actually a coincidence. Amount-matching feels like solved deterministic logic — but it lies constantly. Two invoices can add up to the same number for completely different reasons, and only the evidence — remittance advice, credit note status, whether a credit was already spent elsewhere — tells you which story is true. We wanted to build the tool that stops and asks "wait, does the evidence actually support this?" before money gets allocated to the wrong place.
What it does
DoubleTake takes an incoming customer payment and asks the question an AR operator actually has to answer: which invoice did this really pay?
It generates every mathematically valid candidate — exact matches and invoice-minus-linked-credit-note matches — then investigates. If there's only one candidate, it allocates. If there are several, it checks the remittance reference and the cited credit note's status before deciding. And if the evidence contradicts the obvious answer — say, a remittance cites a credit note that the ledger says was already consumed elsewhere — DoubleTake doesn't shrug and pick the amount-match anyway. It escalates the case, flags the contradiction, and hands a human a fully evidenced review case instead of a silent mistake.
Every decision, resolved or escalated, is deterministic and explainable, and gets a plain-language rationale written by Gemini — not deciding, just narrating the decision that already happened.
How we built it
We split responsibility deliberately: a pure Python candidate generator does the arithmetic (Decimal-safe, no floating point sins), a deterministic investigator applies a fixed evidence hierarchy (remittance first, credit status second), and a FastAPI workflow layer ties it to a persisted SQLite ledger — invoices, credit notes, payments, remittances, allocations, and review cases as first-class models. Gemini is called after the decision is made, purely to phrase the explanation from the facts we hand it, with instructions not to invent IDs or amounts.
On the frontend, a React + TypeScript + Vite workspace lets you open a payment, see the candidates side by side, inspect the evidence, and watch the outcome — and reload it later without re-running anything, because the investigation is persisted, not recomputed.
We used AO (Agent Orchestrator) to structure development into isolated, named build sessions — scaffold, candidate generation, investigation logic, workflow, evaluation, frontend — each with its own branch and merged PR, so the build history is traceable end to end.
Challenges we ran into
The hardest bug wasn't in the "obvious" cases — it was a citation mismatch: a remittance that correctly named the right invoice but cited the wrong credit note. Our early selection logic would quietly fall back to whatever credit balanced the math, treating "the numbers work" as good enough. That's exactly the kind of silent error DoubleTake exists to prevent, and it was humbling to catch it in our own logic. We fixed it with an explicit citation check, a regression test, and a new evaluation case — and it reshaped how seriously we took the "fail closed, not fail creative" principle for the rest of the investigator.
We also had to be honest about scope: the one-candidate path bypasses investigation entirely, credit notes aren't atomically consumed on allocation yet, and Gemini is a synchronous, unfallback'd dependency for ambiguous cases. Rather than hide those gaps, we documented them as explicit boundaries — because a reconciliation tool that overstates its own certainty is worse than one that's upfront about where the humans still need to be in the loop.
Accomplishments that we're proud of
13 out of 13 labeled synthetic cases scored correctly — 100% precision on automatic allocations, 100% escalation correctness, and a 100% catch rate on all four planted contradictions. Zero false allocations, zero missed contradictions. We're proud that the system's one wrong instinct — the credit-citation fallback — got caught by our own evaluation harness before it shipped, not by a customer.
We're also proud of the architecture discipline: the AI never touches the decision boundary. It can't select an invoice, it can't flip a contradiction flag — it only narrates a decision that deterministic Python already made and can be tested and audited independently of any model call.
What we learned
That "the math works" is often the least interesting part of reconciliation — and the most dangerous place to stop looking. Evidence has a hierarchy, contradictions are information (not noise to explain away), and the right failure mode for an ambiguous case is escalation with receipts, not a confident guess. We also relearned a very old lesson in AI system design: the most trustworthy place for a language model is narrating a decision, not making one.
What's next for DoubleTake
- Close the single-candidate gap — run evidence and credit checks even when there's only one mathematical candidate, so "obvious" cases get the same scrutiny.
- Atomic credit consumption — mark linked credit notes consumed the instant they're used in an allocation, closing the door on double-spending a credit across two payments.
- Human-in-the-loop resolution — right now review cases are persisted and visible; next is letting a reviewer actually approve, reject, and resume the workflow from the UI.
- Resilience around Gemini — fallback rationale generation and bounded retries, so a flaky API call can never block a legitimate allocation.
- Real-world inputs — partial payments, multi-currency, and eventually bank-feed integration, moving from structured synthetic fixtures toward the messy data AR teams actually deal with.
Built With
- agent-orchestrator
- decimal
- fastapi
- gemini-api
- npm
- pytest
- python
- railway
- react
- react-router
- rest-api
- sqlalchemy
- sqlite
- swagger
- tailwindcss
- typescript
- uvicorn
- vercel
- vite
Log in or sign up for Devpost to join the conversation.