Reconciliation & Payments
Two layers solve the same problem at different confidence levels: deterministic matching for the clear cases, and an AI agent that proposes candidates for the rest. A human confirms anything the agent suggests. Combined this is the second-largest area of core —reconciliation-agent (26 files,
7,376 lines) plus reconciliation (18 files, 7,499 lines) plus payment-matching
(11 files).
The two paths
Deterministic matching
payment-matching handles the cases that need no judgement:
remittance-memo.ts has its own package export
(@cona/core/payment-matching/remittance-memo) — it is used outside the domain.
The reconciliation agent
reconciliation-agent proposes, it does not decide. Its outputs are suggestions that a
person approves or rejects.
Two feedback loops make this adaptive rather than static:
learn-approved-candidate-allocation-iban.tsrecords the IBAN→customer association once a human approves an allocation, so the next payment from that account scores higher.get-manual-reconciliation-examples.tsfeeds prior manual reconciliations back as examples.
activities/reconciliation/run-reconciliation-agent.ts — using @cona/agents over
Vercel AI Gateway. See Copilot Agents.
Every agent action is written to copilot-audit (7 imports from reconciliation-agent,
the strongest edge into that domain).
IBAN evidence
customer_ibans plus customer_iban_evidence accumulate the association between a bank
account and a customer. payment-matching imports customer_ibans twice and
reconciliation-agent three times.
Bank matching handoffs
Two tables,bank_matching_handoffs and tier2_bank_matching_handoffs, back a tiered
escalation: unmatched bank payments move to a handoff queue, and a second tier catches
what the first cannot resolve.
syncBankMatchingHandoffsWorkflow runs this on the accounting-queue.
Workflows
The
reconciliation worker group is deliberately small — 2 workers, 15 activity slots —
with a documented transaction fan-out guard: at most three organisation activities at
once, two groups per batch, and serial initialisation. Its Prisma pool reserves eight
client slots, and a configured pool below eight fails at worker startup
(WORKER_GROUPS.md).
Auto-booking and quarantine
resolve-account-auto-booking-config.tsandprocess-reconciliation-group-with-auto-booking.tspost matched groups to the ledger without human involvement when the account is configured for it.quarantine.tsholds groups that cannot be safely posted.cancel-reconciliation.tsandcancel-reconciliation-groups.tsunwind a completed reconciliation — which is whyreconciliationimportsdocumentsfour times andgeneral_ledgerimportsreconciliationtwice.
Notes
Reconciliation participates in real cycles.documents ↔ reconciliation and
general_ledger ↔ reconciliation both have edges in both directions. Unwinding a
reconciliation has to touch documents and the ledger, so the coupling is inherent.
FX is involved. reconciliation imports fx twice — a payment in one currency
against an invoice in another needs a rate at match time.
submit-reconciliation-gid-later.ts defers GID submission, and
general_ledger_reconciliation_gids records the association — the ledger side of a match
is not always written synchronously.