Skip to content
Prompt Consulting
de
financeagents

Where agent reconciliation actually goes wrong

Automated ledger matching fails in a handful of predictable ways. Near-duplicate amounts, timing, partial payments, FX rounding, and one line covering many invoices.

Thilo Krause

Reconciliation looks like the easiest thing to hand to an agent. Two lists, one rule, match the amounts. It demos beautifully. Most lines clear on the first pass and everyone in the room decides the month-end close is solved.

The last line is where the money is. Matching is not a lookup, it is a search with several plausible answers, and the interesting failures are the ones where the agent finds an answer that balances and is wrong. A missed match costs someone a few minutes. A confident wrong match gets booked, and you find it a quarter later in an ageing report nobody believes.

Here are the failure modes worth designing for before the first line of code.

Near-duplicate amounts

The same vendor invoices 1,240.00 twice in one week. Two bank debits of 1,240.00 arrive. An agent matching on amount and date finds two candidates for each payment and has no basis at all for choosing between them.

What makes this dangerous is that the wrong choice balances. Total payables clears, the bank reconciles, the control account is flat. The damage is in the allocation. One invoice sits open while a paid one is marked settled, so the dunning run chases an invoice that was paid and the open one ages quietly.

The fix is not a cleverer amount rule. It is a second identifier: the invoice reference in the payment narrative, the remittance advice, the purchase order number. If neither payment carries a reference, the agent has two identical candidates and should say so rather than pick. Round-robin allocation across duplicates is the single worst behaviour you can build in, because it produces no errors you can see and a permanent low rate of wrong allocation.

Timing differences

Almost nothing settles on the day it is dated. Bank value date and posting date differ. Card settlement runs two to three working days behind the transaction. SEPA direct debits land on their own schedule. A weekend or a public holiday pushes everything and the holiday calendar is different in each country you bill in.

So every matching rule carries a date window, and the window is a genuine tradeoff rather than a setting to get right. A window of plus or minus one day silently fails to match real pairs, which the team reads as the agent being useless. A window of plus or minus seven days doubles or triples the candidate set, and a bigger candidate set means more of the near-duplicate problem above.

Narrow the window and make the reference the primary key wherever one exists. Then treat date distance as a tiebreaker rather than a filter. A pair matched on reference nine days apart is almost certainly right. A pair matched only on amount two days apart is a guess.

Partial payments and deductions

A customer owes 9,500 and pays 8,000, deducting 1,500 they are disputing. Sometimes they pay 9,455 and keep the early settlement discount. Sometimes the bank takes a 12.50 transfer fee out of the middle.

Exact amount matching fails, which is the good outcome. The bad outcome is an agent that treats the amount as a search key and finds a different open invoice for exactly 8,000. Now two records are wrong instead of one open item being flagged, and the audit trail says the agent was confident.

Partial payment needs to be a modelled case, not a fallback. The agent should be able to allocate 8,000 against the 9,500 invoice, leave 1,500 open, and tag the residual with a reason it did not invent. A discount taken inside terms is arithmetic the agent can check against the payment date. A short payment with no explanation is a human decision, because the answer is a credit note, a dispute, or a collections call, and none of those are matching.

FX and rounding

Invoice in USD, payment in EUR, and the rate you applied on the invoice date is not the rate the bank used on the settlement date. The result is a difference of a few cents, or a few hundred, depending on the size of the invoice and the two weeks in between.

Tolerance rules are where this goes wrong. An absolute tolerance of 0.02 is loose on a 12 EUR line and meaningless on a 120,000 EUR one. A percentage tolerance of 0.1 percent is sensible on the large invoice and allows nothing on the small one. Use a percentage with an absolute floor, something like 0.5 percent up to a stated cap, and set the cap where your controller says it sits rather than where the code is convenient.

One rule matters more than the tolerance shape. Never absorb the difference into the match. The variance goes to a named FX or rounding account with the original amounts preserved on both sides, so somebody can add up the account at quarter end and see whether the tolerance is being used as intended or as a wastebasket. An agent that quietly makes numbers agree is not reconciling, it is tidying.

One line covering many invoices

A single bank credit of 43,210.00 pays seventeen invoices. This is subset sum and it will eat your agent.

The arithmetic is unforgiving. Sixty open items give you more subsets than you can enumerate, so any working implementation has to constrain the search before it starts: this customer only, inside a date window, and ideally against the line items on a remittance advice rather than the whole ledger. That turns an impossible search into a small one.

The real danger is not runtime. It is that subset sum has multiple correct answers. Several different combinations of invoices add up to 43,210.00, especially in a ledger full of round numbers and repeating monthly charges. When the agent finds more than one exact subset it has no way to choose, and choosing is exactly what a naive implementation does, silently, by returning the first one it found. Make multiple solutions an explicit outcome. If two or more subsets match, hand over all of them and let a person pick.

What an exception should contain

An unmatched line thrown into a queue with no explanation gets less useful over time, because reviewers start with the same blank page the agent had. Hand over the work, not the leftovers.

A usable exception carries the line itself, what the agent tried, the candidates it considered with the amount and date distance for each, why it rejected or could not separate them, and what it would do if somebody confirmed its best guess. That last part turns a ten minute investigation into a yes or no. It also creates the training data for the next iteration, because the pattern in what humans override is the most valuable list in the project.

Two hard limits belong in code rather than in a prompt. The agent never posts a journal to make a match balance, and it never writes off a residual. Both are reasonable things to do and both are decisions with an owner and a threshold, which makes them somebody's job and not the agent's.

The target exception rate is not zero

An agent that returns no exceptions is not finished, it is lying. Real ledgers contain genuinely ambiguous lines, and a system that reports none of them has resolved the ambiguity by guessing.

So treat the exception rate as a designed number, agreed with the person who reviews them, and sized against how many they can get through in a day. Then watch its shape rather than its level. A rate that drifts up means something upstream changed, a new payment channel, a new entity, a customer who started paying in batches. That drift is the most useful signal the system produces, and you only get it if the agent was allowed to say it did not know.

All notes

Next step

Tell us what your team still does by hand.

Thirty minutes on a call. You describe the work that eats the week. We tell you whether an agent can take it and what building it would cost, including when the answer is that it cannot.

  • Built on your current stack
  • Nothing to migrate
  • Three clients at a time

Analytics and spam protection

We would like to count visits with Google Analytics, and to load Google's spam check on the contact form. Both load only if you accept. Either way we store one entry in your browser so this does not ask again, and the contact form works the same whichever you press.

What we collect, in full