Follow the money: a connection graph for fraud and AML investigators
12 October 2026 · Semurg · 5 min read · Markdown
Verified: every command below ran on a fresh node of r11.78.20261012.bc0d94f (how)
build-on-semurgfraudamlgraphblueprint
Money laundering hides in the connections. An account on its own looks ordinary; the same account, two transfers away from a company whose director also controls the account the money came from, does not. Investigators reconstruct those connections by hand from exports, one join at a time. This blueprint keeps them as a graph from the moment the data arrives, so a flagged account leads straight to where the money went, who sits behind each hop, and whether it comes back.
It is a tool for investigators, not a screening or reporting system: it holds what your monitoring and case work put into it, and it shows the connections. Deciding what is suspicious stays with the investigator.
Everything below uses only what the published release does.
The shape of the data
- One scope per investigation, so a case's working graph stays apart from every other case, and one shared scope for what the whole team has established.
- Entities: accounts, the people who hold or control them, companies, addresses.
- Relationships:
TRANSFERRED_TObetween accounts,HOLDSfrom a person or company to an account,DIRECTOR_OFfrom a person to a company,REGISTERED_ATto an address. - Detail as passages: the amounts, dates and references of each transfer go into a passage that names the accounts involved, so the detail comes back with the connection.
A person who controls ten accounts is one entity with ten edges. An address shared by four companies is one entity, and the moment the fourth company is written, the walk from any of them reaches the other three.
Step one: write what monitoring found
The transfers your monitoring flagged, with their owners. The call lists both ends of each relationship, so it stands on its own:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/remember \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"case-harbour","date":"2026-09-30","source":"transaction monitoring alert batch",
"facts":{"entities":[{"name":"Account Alpha","type":"Account"},{"name":"Account Bravo","type":"Account"},
{"name":"Account Charlie","type":"Account"},{"name":"Harbour Holdings","type":"Company"},
{"name":"Dana Kovac","type":"Person"}],
"relations":[{"source":"Account Alpha","target":"Account Bravo","type":"TRANSFERRED_TO"},
{"source":"Account Bravo","target":"Account Charlie","type":"TRANSFERRED_TO"},
{"source":"Account Charlie","target":"Account Alpha","type":"TRANSFERRED_TO"},
{"source":"Harbour Holdings","target":"Account Bravo","type":"HOLDS"},
{"source":"Dana Kovac","target":"Account Alpha","type":"HOLDS"}]},
"passages":[{"text":"Account Alpha sent 48,500 to Account Bravo on 2026-09-22; Account Bravo sent 47,900 to Account Charlie on 2026-09-23; Account Charlie returned 47,100 to Account Alpha on 2026-09-25.",
"mentions":["Account Alpha","Account Bravo","Account Charlie"],"key":"alert-ring-1"}]}'Expected: a JSON answer with "ok": true
Step two: follow the money
Start from the flagged account. Context finds every entity the question names, walks out from them up to three steps, and returns the connections with the passages that describe them:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/context \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"case-harbour","query":"Where did money from Account Alpha go?","hops":3}'Expected: "state": "answered": the transfers from Account Alpha to Account Bravo, on to Account Charlie and back to Account Alpha, with the passage holding the amounts and dates
The loop is visible in what comes back: Alpha to Bravo, Bravo to Charlie, Charlie back to Alpha. Who sits behind each hop is one recall away. Recall returns every connection of one entity, in both directions:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/recall \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"case-harbour","subject":"Account Bravo"}'Expected: "state": "answered": money in from Account Alpha, out to Account Charlie, and Harbour Holdings as the holder
Step three: add what the investigator finds
A company registry search shows that Dana Kovac, who holds Account Alpha, is also a director of Harbour Holdings, which holds Account Bravo. One relationship, added on its own:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/relate \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"case-harbour","from":"Dana Kovac","to":"Harbour Holdings","type":"DIRECTOR_OF"}'Expected: a JSON answer with "ok": true
From now on, a walk from either account reaches the same person on both sides of the transfer, and so does every investigator who opens the case.
An agent on the case
An investigation agent can call the same memory over MCP, with the case's scope in the X-Semurg-Scope header: it reads what the team has established before it proposes the next step, and it writes what it confirms. The agents page has the configuration for each agent and framework we test.
Scale and cost
A single node is free, with no licence key, and it is the full engine. A cluster is licensed per node; talk to us about the one you need. Our graph results are being re-measured on new hardware and land on the benchmark page with the release they ran on. We have not measured this blueprint's own workload.
What it does not do today
- Walks on this surface go at most three steps from the names in the question. A chain longer than that is found by starting a second walk from where the first one ended. The engine's deep traversal, which goes much further, answers only with counts over the public interface today, not with the paths.
- A relationship carries a type, not an amount or a date. The amounts and dates live in the passage that names the accounts, and the date of the write comes back with every fact it sourced.
- A name is matched exactly, by letter case or by a close spelling, so two very different spellings of one person stay two entities until an investigator relates them.
- It does not score risk, screen against sanctions lists or file reports. It holds the graph your monitoring and your investigators build, and it shows the connections.
When something is refused
401: the token is missing or wrong;422missing 'scope'on a call without a scope.- A passage whose mention does not appear in its text is refused, naming the mention.
- A relationship whose far end is not listed in the same call is not written on today's release, although the call answers ok: list both accounts every time.
"state": "nothing_stored"means nothing in the case matched the names in the question; it is an answer, not an error.
Next
- Give your agent persistent memory with MCP in five minutes: the node and the seven memory tools.
- A research graph of claims and papers: another graph that follows the connections.
How these commands were checked
Our test farm ran each of the 4 command blocks marked runnable, exactly as written, with the answer checked against the expectation printed under it, against a fresh node of the published release r11.78.20261012.bc0d94f. A post is published only when every one of them passed there.