Ethan Chen
All work

FirmGround Ledger

A reconciliation workbench for accountants. A vision model turns receipts into ledger entries, an algorithm matches them against bank statements and accounting-system exports, and agents only handle the cases the algorithm can’t settle.

Role
Design and engineering
Stack
FastAPI, LangChain, Redis, React
Note
All figures in the examples on this page are synthetic.
Bank statementLedger1,240.0086.503,900.00412.752,015.3058.00730.101,240.003,900.0086.50412.75730.102,015.0058.00Accountant confirms

1The job

Every month, accountants check that a company’s own books agree with the outside world. That check is called reconciliation, and it is often done by hand in Excel.

Two things make it slow:

  • Every source arrives in its own format. Each bank and each accounting system exports different columns, date formats and sign conventions, so the files have to be lined up before anything can be compared.
  • The hard part is the exceptions. Most lines match cleanly. The time goes into the few that don’t: finding out why, and deciding what to do about each one.

2The rule

The whole design follows one rule: algorithms do the matching, agents handle the exceptions, accountants decide.

  1. Algorithm

    Every line, every time

    Ingestion, matching and break classification are deterministic. Known file formats are read with zero model calls.

  2. Agents

    Only the ambiguous cases

    Three narrow agents propose a mapping, a match or a cause. What they produce is a suggestion, nothing more.

  3. Accountant

    Every decision

    Nothing an agent suggests is applied until an accountant confirms it. Confirmed decisions are recorded and can be replayed.

Reconciliation has to be repeatable and explainable: the same statement should produce the same result, and an auditor should be able to see why each line was matched. An algorithm gives that for free. A language model doesn’t, so it is kept to the cases where judgement is actually needed.

3The life of a statement

  1. Ingestsaved template, or a new mapping
  2. Matchexact, tolerance, fuzzy, subset
  3. Classify breakswhat is left gets a type
  4. Agent suggestsonly for ambiguous cases
  5. Accountant confirmsnothing applies before this
  6. Audit logconfirmed decisions replay deterministically
model decidesperson confirms
  1. Ingest

    Receipts are read by a vision-language model, which turns each one into a ledger entry. Bank statements and ERP exports are read through a format template: a saved mapping from that file’s columns to the workbench’s own fields.

    A file in a known format uses its template, with zero model calls. A file in a new format goes to the format-mapping agent, which proposes a mapping. Once an accountant approves it, the mapping is saved as a new versioned template, so the next file in that format needs no model at all.

    A format template, illustrated
    bank_hk_statement · v2
    Txn Date        →  date         (DD/MM/YYYY)
    Particulars     →  description
    Withdrawal      →  amount       (negative)
    Deposit         →  amount       (positive)
    Ref No.         →  reference
  2. Match

    The algorithm runs four passes, from strictest to loosest. Each pass only sees the lines the earlier ones left.

    The four matching passes
    PassWhat it catchesExample
    1ExactSame amount, same date, same reference.Bank 1,240.00 on 3 Mar ↔ ledger 1,240.00 on 3 Mar
    2ToleranceSmall, allowed differences in date or amount, such as a payment that clears a day late.Bank 412.75 on 5 Mar ↔ ledger 412.75 on 4 Mar
    3FuzzyDescriptions that refer to the same thing but are written differently.“ACME HK LTD PMT” ↔ “Acme (Hong Kong) Ltd”
    4SubsetOne line that equals the sum of several on the other side, such as a single deposit covering three invoices.Bank 3,900.00 ↔ ledger 1,300.00 + 1,300.00 + 1,300.00
  3. Classify breaks

    Whatever is still unmatched is a break, and the algorithm sorts each one by type. Typical breaks in reconciliation include a payment that hasn’t cleared yet, a bank fee nobody recorded, an amount that is slightly off, or an entry recorded twice.

  4. Agent suggests

    Only the ambiguous cases reach an agent. Each of the three agents runs as a ReAct agent in its own sandboxed context, so one task’s material never leaks into another’s decision. What comes back is a suggestion card, not a change to the books.

    Match reviewSuggestion
    Bank2,015.3015 Mar · ACME HK LTD
    Ledger2,015.0014 Mar · Acme (Hong Kong) Ltd

    Same customer, one day apart, 0.30 difference. Likely a currency-conversion rounding difference. Suggest: match, and post 0.30 to exchange differences.

    ConfirmReject
    Illustration with synthetic figures.
  5. Accountant confirms

    The accountant confirms or rejects each suggestion. Until then the agent’s output stays a suggestion: nothing is posted, matched or closed on an agent’s word alone.

  6. Audit log

    Every confirmed decision is recorded. Because the steps before it are deterministic, replaying the log on the same files reproduces the same result. That gives a full audit trail: for any line, you can show how it was matched and who approved it.

4The three agents

Each agent has one job, its own tools and its own isolated context.

AgentWhen it runsWhat it returns
Format mappingA statement or export arrives in a format with no saved templateA proposed column mapping
Match reviewA line has candidate matches the algorithm can’t settle on its ownA suggested match, with its reasons
Root-cause investigationA break is left unexplained after matching and classificationA likely cause, with the evidence behind it

Every output in the right-hand column goes to an accountant as a suggestion. None is applied automatically.

5Result

Time to process a statement (manual Excel = 100)
Manual Excel bookkeeping
100
FirmGround Ledger
< 40

10 multi-page bank statements. The Ledger time includes the accountant’s review and correction.

On 10 multi-page statements, processing took more than 60% less time than manual Excel bookkeeping, counting the time to review and correct the output.

Not measured yet

  • How much reaches an agent. The share of lines that the algorithm settled on its own, and the share that needed an agent.
  • How often suggestions are accepted. The share of agent suggestions accountants confirm unchanged, by break type.
  • Scale. Ten statements is a small test. A larger set, across more banks and formats, would show how the time saving holds up.

6Trade-offs

Why not ask a model to match every line?

Most lines match exactly. An algorithm settles them faster, at no cost, and gives the same answer every time, which is what an audit trail needs. A model would add cost and variation to the easy cases without improving them.

Why three agents instead of one?

Each task needs different tools and different material. Mapping a file format, reviewing a match and investigating a break have little in common. Keeping them in separate contexts stops one task’s material from influencing another’s decision, and makes each agent’s behaviour easier to test.

Why make every suggestion wait for a person?

It is slower than letting agents post changes directly. But an accountant signs off on the books, so they need to see and approve every judgement call. The time result above already includes that review.