Skip to main content

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.ts records the IBAN→customer association once a human approves an allocation, so the next payment from that account scores higher.
  • get-manual-reconciliation-examples.ts feeds prior manual reconciliations back as examples.
The agent runs both in-app and from a Temporal activity — 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.ts and process-reconciliation-group-with-auto-booking.ts post matched groups to the ledger without human involvement when the account is configured for it.
  • quarantine.ts holds groups that cannot be safely posted.
  • cancel-reconciliation.ts and cancel-reconciliation-groups.ts unwind a completed reconciliation — which is why reconciliation imports documents four times and general_ledger imports reconciliation twice.

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.