> ## Documentation Index
> Fetch the complete documentation index at: https://cona.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Reconciliation & Payments

> Matching incoming payments to open invoices — deterministic matching, the AI suggestion agent, and bank handoffs

# 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

```mermaid theme={null}
flowchart TB
    Bank["Bank payments<br/>finAPI · bank-account adapter"]
    PSP["PSP payments<br/>Stripe · PayPal · Mollie"]

    Payments[("payment documents")]
    Open[("open invoices")]

    Exact["payment-matching<br/>exact-open-invoice-candidate.ts<br/>evaluate-payment-amount.ts"]

    Agent["reconciliation-agent<br/>score-single-invoice-candidates.ts<br/>generate-bulk-payment-invoice-combinations.ts"]
    Suggest[("agent suggestions")]
    Human["human review<br/>webapp"]

    Apply["apply-confirmed-payment-match.ts<br/>apply-candidate-allocation.ts"]
    Group[("reconciliation_groups")]
    Complete["complete-reconciliation-group.ts<br/>process-reconciliation-group-with-auto-booking.ts"]
    GL[("general_ledger")]

    Bank --> Payments
    PSP --> Payments
    Payments --> Exact
    Open --> Exact

    Exact -->|"confident match"| Apply
    Exact -->|"ambiguous"| Agent
    Agent --> Suggest
    Suggest --> Human
    Human -->|approve| Apply
    Human -->|reject| Reject["reject-reconciliation-agent-suggestion.ts"]

    Apply --> Group
    Group --> Complete
    Complete --> GL

    classDef pkg fill:#e8eafd,stroke:#4967E6,color:#111827
    classDef data fill:#fde8e8,stroke:#c53b3b,color:#111827
    classDef external fill:#fff4dd,stroke:#c98a12,color:#111827
    classDef actor fill:#eceff4,stroke:#64748b,color:#111827

    class Exact,Agent,Apply,Complete,Reject pkg
    class Payments,Open,Suggest,Group,GL data
    class Bank,PSP external
    class Human actor
```

## Deterministic matching

`payment-matching` handles the cases that need no judgement:

| File                               | Role                              |
| ---------------------------------- | --------------------------------- |
| `exact-open-invoice-candidate.ts`  | find an unambiguous open invoice  |
| `evaluate-payment-amount.ts`       | amount comparison and tolerance   |
| `remittance-memo.ts`               | parse the payment reference text  |
| `apply-confirmed-payment-match.ts` | commit the match                  |
| `run-bank-matching-handoff.ts`     | escalate to the bank handoff flow |

`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.

| Capability                         | File                                            |
| ---------------------------------- | ----------------------------------------------- |
| Score single-invoice candidates    | `score-single-invoice-candidates.ts`            |
| Score with IBAN evidence           | `score-single-invoice-candidates-with-iban.ts`  |
| Bulk payment → many invoices       | `generate-bulk-payment-invoice-combinations.ts` |
| Create suggestions                 | `create-reconciliation-agent-suggestions.ts`    |
| Execute an approved suggestion     | `execute-reconciliation-agent-suggestion.ts`    |
| Reject                             | `reject-reconciliation-agent-suggestion.ts`     |
| Learn from approvals               | `learn-approved-candidate-allocation-iban.ts`   |
| Few-shot examples from manual work | `get-manual-reconciliation-examples.ts`         |
| Candidate shape contract           | `candidate-contract.ts`                         |

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](/architecture/copilot-agents).

Every agent action is written to `copilot-audit` (7 imports from `reconciliation-agent`,
the strongest edge into that domain).

## IBAN evidence

```mermaid theme={null}
erDiagram
    customer_ibans ||--o{ customer_iban_evidence : supported_by
    entities ||--o{ customer_ibans : belongs_to
    customer_ibans }o--o{ reconciliation_groups : informs
```

`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

| Workflow                                 | Queue                   | Purpose                      |
| ---------------------------------------- | ----------------------- | ---------------------------- |
| `syncReconciliationGlobalWorkflow`       | `reconciliation-global` | fan out across organisations |
| `syncOrganizationReconciliationWorkflow` | `reconciliation-org`    | one organisation             |
| `reconciliationAgentSuggestionsWorkflow` | `reconciliation-org`    | generate suggestions         |
| `syncBankMatchingHandoffsWorkflow`       | `accounting-queue`      | bank handoff processing      |

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.
