eqlx.aiOpen research

eqlx.org / eqlx-aml

Python library · MCP server · v0.1 · Apache-2.0

Find money-laundering patterns. Show the rows that prove them.

eqlx-aml reads a file of money transfers and finds the shapes launderers use to hide where money came from. Every alert names the accounts, the dates and the exact rows, and AI agents use it read-only.

What goes in, what comes out

Nobody can read millions of transfers by hand, and a language model left alone with a large table miscounts. So the agent decides what to look at; the tool does the counting.

You give

  • a file of transfers
  • who paid whom, when, how much
  • the patterns to look for
  • your limits, or the defaults

eqlx-aml computes

  • every match of each written rule
  • the accounts in each match
  • the rows that form it
  • where the money went next

You get

  • alerts, one per match
  • accounts, dates and totals
  • the exact rows as evidence
  • the same answer every time

No model inside. Banks and payment companies must report suspicious transfers (in Türkiye, to MASAK); an investigator has to be able to point at the evidence.

One alert, start to finish

The made-up sample file that ships with the tool: 4,034 transfers between 452 accounts, 5 January to 1 February 2026, all in EUR.

1You give the file

One row per transfer. Two of the 4,034 rows matter here; nothing marks them.

RowTimeFromToAmount
175816 Jan 23:56ACC-0245ACC-00251,327.80
175917 Jan 00:00SG-ASG-M07,000.00
176017 Jan 00:00ACC-0349ACC-0223220.31
… 166 rows …
192717 Jan 23:52ACC-0120ACC-0007337.78
192818 Jan 00:00SG-M0SG-B6,900.00
192918 Jan 00:33ACC-0337ACC-0000648.17

2You ask for a pattern

Here scatter-gather, with its default rule.

Patternscatter-gather
A pays several accounts, each then pays the same Bat least 3
All of it within7 days
Skip middle accounts busier than50 transfers

aml scan demo.csv --pattern scatter_gather

3It finds the shape

SG-A pays five accounts on 17 January. Each pays SG-B a day later.

17 Jan18 Jan AB SG-M0SG-M1SG-M2SG-M3SG-M4 SG-ASG-B

4It lists the rows

Ten transfers, in pairs: money in from SG-A, money out to SG-B.

Middle accountIn, 17 JanOut, 18 Jan
SG-M07,000 row 17596,900 row 1928
SG-M17,050 row 17726,950 row 1937
SG-M27,100 row 17897,000 row 1950
SG-M37,150 row 18037,050 row 1963
SG-M47,200 row 18277,100 row 1975

5Where the money ended up

Added up from the ten rows, in EUR.

SG-A sent
35,500
SG-B received
35,000
Left on the way
500
100 each stayed in every middle account

6Trace it back from SG-B

Only along payments made before the money arrived.

1 step backSG-M0 … SG-M4
2 steps backSG-A
Transfers on the trail6

aml trace demo.csv SG-B --backward

5middle accounts · 10 transfers

What comes back

"SG-A sent money through 5 different accounts that each passed it on to SG-B within 7 days"

Accounts SG-A, SG-B, SG-M0, SG-M1, SG-M2, SG-M3, SG-M4 · From 17 Jan 00:00 to 18 Jan 08:00 · Total moved 70,500.00 EUR

rows 1759, 1772, 1789, 1803, 1827, 1928, 1937, 1950, 1963, 1975

scatter_gather

Every figure above is the tool's output on this file. The same scan returns one more scatter-gather: three ordinary accounts that happen to form the shape. An alert is a reason to look.

What it looks for

Seven patterns from FATF typologies and monitoring practice. Each is an exact rule; the defaults look within seven days.

ABCD

Loop

Money returns to where it started.

Moderate signal
AB

Scatter-gather

A splits money across middle accounts; each pays the same B.

Strong signal

Gather-scatter

One account collects from several and pays out to several.

Weak to moderate signal

Fan-out and fan-in

One account pays many, or is paid by many. So does every payroll and shop.

Weak signal on its own
50,00030,00018,500

Pass-through

An account sends on 90 to 100 % of what it received, within a day.

Weak signal on its own
reporting limit 10,000

Structuring

Several deposits, each just under a reporting limit you set.

Depends on the limit you set (not in the IBM data)

Accounts with more than 50 transfers in the week are not followed as middle accounts of loops and scatter-gather. It is a setting.

How an agent uses it

Point the MCP server at one folder of transfer files. The agent can read and analyse them, and nothing else.

SummariseTransfers, accounts, dates, currencies
ScanPatterns, with stricter or looser rules
ProfileAll that one account sent and received
TraceThe money, hop by hop, forward or back
QuoteThe exact rows as evidence
pip install "eqlx-aml[mcp]"
claude mcp add aml -- aml mcp --root /path/to/transfers

A real run

Claude Code got only this server and the sample file above, and was asked in Turkish to separate suspicious activity from ordinary business.

7 of 7hidden patterns found, with their rows
23 stepsin about 70 seconds
5 of 7rated suspicious; the fan-out and fan-in as possibly payroll and collections that deserve a question
Normal tradeits verdict on the many small patterns among ordinary accounts
SG-A sent 7,000–7,200 EUR to five accounts (rows 1759, 1772, 1789, 1803, 1827); exactly a day later each passed the money on to SG-B (rows 1928–1975), keeping 100 EUR. Instead of one direct payment the money went through five accounts: the typical picture of layering.From the agent's report, translated from Turkish.

How it was tested

Two questions: does each detector do exactly what its rule says, and does that rule catch planted laundering?

1. Exactly what the rule says

✓Brute forcea slow second version tries every possibility; both agreed on hundreds of random transfer sets
✓Shuffled rowsthe result does not change with the order of the file
✓Fresh processnor with Python's randomised hashing
✓360 testsrun on every change

2. On IBM's public laundering data

A simulated world of banks, people and companies (Altman et al., NeurIPS 2023). No real people, so it can be used openly. Every detector ran with its default settings.

5,078,345transfers
515,088accounts
370laundering attempts planted by the simulator, each labelled with its shape
2.5 minutesabout, for every pattern on one core

Did it find the planted attempts? 140 of the 144 big enough to match a rule.

The three loops it missed run through a busy account or have payments out of time order: what the rule leaves out.

How many alerts are real? It depends on the shape.

Share of each detector's alerts that are about an account taking part in laundering. Only 1.2 % of all accounts do: the black line is what picking accounts at random would give.

2.1 %A fan-out on its own says little: ordinary payments look the same1.7 times chance
20 %Money split across three or more accounts and gathered again16 times chance
26 of 30alerts were real laundering with five or more middle accounts70 times chance

Stricter settings give fewer, better alerts and miss small schemes. The full table of settings is in the repository, and the script reproduces it from your own copy of the data.

What it does not do

Shapes, not verdictsAn alert is a reason to look. Filing a suspicious-activity report stays with a qualified person.
Only the file you give itNo customer data, sanctions lists or account history beyond the file.
No currency conversionAmount-based rules compare within one currency.
Busy accounts are skippedAs middle accounts of loops and scatter-gather, unless you raise the limit.
Three IBM shapes not detected yetStacked layers, bipartite and random chains: 133 of the 370 attempts.
The benchmark is simulatedIt shows how the rules behave, not how many criminals a given bank would catch.
pip install "eqlx-aml[mcp]"
aml sample demo.csv     # synthetic transfers with hidden patterns
aml scan demo.csv