How I Built an AI Fraud Investigator With a Graph Database I'd Never Used Before
Fraud doesn't hide in individual transactions. It hides in connections. A $4,800 bank transfer looks completely normal on its own. But trace the graph: that money flows to a second account, then a third, then circles right back to the original sender — all in amounts carefully kept below the $5,000

Fraud doesn't hide in individual transactions. It hides in connections.
A $4,800 bank transfer looks completely normal on its own. But trace the graph: that money flows to a second account, then a third, then circles right back to the original sender — all in amounts carefully kept below the $5,000 reporting threshold. The three accounts share the same device, connected through a VPN from a country none of them are registered in. None have verified KYC.
That pattern is invisible in a spreadsheet. It's invisible in a SQL database unless you write recursive self-joins that slow to a crawl at scale. But in a graph database? It's a single query.
I built FraudGraph to explore this idea: what happens when you give an AI agent a graph to reason over?
Most fraud detection systems work with rules applied to flat data. Flag any transaction over $10,000. Flag any account with more than 20 transactions per day. Flag any international transfer to a high-risk country.
These rules catch the obvious cases. They completely miss the sophisticated ones.
Money laundering, for example, works precisely because each individual transaction looks legitimate. The fraud lives in the pattern — the circular flows, the layered transfers, the shared identifiers across seemingly unrelated accounts. To catch it, you need to follow connections across multiple hops, not just scan individual rows.
This is what graph databases were built for.
I'd never used FalkorDB — or any graph database — before this hackathon. Honestly, this project was more learning than building. And the biggest thing I learned is this: in a graph database, the relationships are the point.
In a relational database, relationships are an afterthought. You define tables, then bolt on foreign keys to connect them. The data lives in rows; the connections are just plumbing. In FalkorDB, it's the opposite. The connections between nodes are first-class citizens — they have types, properties, and direction. That inversion changes everything about what you can do with the data and what you can get from it.
When my agent needs to trace a money flow three hops deep — Account → Transaction → Account → Transaction → Account → Transaction → Account — that's a single Cypher pattern match. In a relational database, I'd be writing nested joins that get exponentially slower with each hop. In FalkorDB, I'm just walking the graph.
My graph has 232 nodes across 6 types (accounts, transactions, merchants, devices, fraud cases, alerts) connected by 709 edges across 9 relationship types. I seeded it with realistic data including four injected fraud patterns: a money laundering ring, an account takeover cluster, a card fraud network, and a synthetic identity ring — all hiding among legitimate activity.
( Account ) - [:SENT ] -> ( Transaction ) - [:RECEIVED_BY ] -> ( Account ) ( Transaction ) - [:AT ] -> ( Merchant ) ( Account ) - [:USES ] -> ( Device ) ( Account ) - [:SHARES_DEVICE ] -> ( Account ) ( Account ) - [:FLAGGED_IN ] -> ( FraudCase ) ( Account ) - [:HAS_ALERT ] -> ( Alert ) Enter fullscreen mode Exit fullscreen mode Building the Agent FraudGraph's AI agent (powered by Google Gemini via Vercel AI SDK) has 7 tools, each wrapping Cypher queries against FalkorDB. The agent decides which tools to call, in what order, and synthesizes the results into a coherent investigation report.
The tools break down into three categories:
Investigation tools that read the graph:
Action tools that write back to the graph:
The key design decision was making the tools do the heavy lifting rather than relying on the LLM to reason about raw data. The circular flow detection, the temporal ordering, the structuring thresholds — all of that logic lives in the Cypher queries, not in the prompt.
My first version of trace_money_flow had a subtle but critical bug. It would follow any structural path through the graph, regardless of timestamps. So it would report a "money flow" where the supposed downstream transaction happened before the upstream one — a flow that looks connected in the graph but couldn't have actually happened.
The fix was adding temporal ordering constraints:
MATCH ( start: Account { id: $id }) - [:SENT ] -> ( tx1: Transaction ) - [:RECEIVED_BY ] -> ( a2: Account ) - [:SENT ] -> ( tx2: Transaction ) - [:RECEIVED_BY ] -> ( a3: Account ) WHERE tx2.timestamp > tx1.timestamp RETURN start.name, a2.name, a3.name, tx1.amount, tx2.amount, tx1.timestamp, tx2.timestamp Enter fullscreen mode Exit fullscreen mode That WHERE tx2.timestamp > tx1.timestamp is the difference between finding structural coincidences and finding actual causal money chains. This matters even more for circular flow detection — without temporal ordering, you'd flag any cycle in the graph, even if the transactions didn't happen in the right sequence to constitute actual laundering.
Detecting money that returns to its origin was the feature that made the project click. Here's the 3-hop circular flow query:
MATCH ( a: Account { id: $id }) - [:SENT ] -> ( tx1: Transaction ) - [:RECEIVED_BY ] -> ( b: Account ) - [:SENT ] -> ( tx2: Transaction ) - [:RECEIVED_BY ] -> ( c: Account ) - [:SENT ] -> ( tx3: Transaction ) - [:RECEIVED_BY ] -> ( a ) WHERE tx2.timestamp > tx1.timestamp AND tx3.timestamp > tx2.timestamp RETURN a.name, b.name, c.name, tx1.amount, tx2.amount, tx3.amount Enter fullscreen mode Exit fullscreen mode This finds the pattern A→B→C→A where each transaction happened after the previous one. In my seed data, the agent discovers a ring where three accounts cycle money in amounts between $4,500 and $4,999 — classic structuring just below the $5,000 reporting threshold.
The graph makes this query natural. In SQL, you'd need a three-level self-join on a transactions table with subqueries for the account linkages. In FalkorDB, the relationships are the data model.
Beyond the web UI, I built a standalone Model Context Protocol server that exposes all 7 tools via stdio transport. Any MCP-compatible agent — Claude Desktop, Cursor, or a custom integration — can connect and run fraud investigations through the same FalkorDB graph without my frontend.
This means the investigation capability isn't locked into one UI. A compliance team could wire it into their existing tools. A developer could query it from their IDE. The graph and the tools are the product; the chat interface is just one way to access them.
Relationships are the superpower. Coming from relational databases, I kept thinking in terms of tables and joins. The mindset shift with FalkorDB is that you stop asking "what data do I have?" and start asking "how is my data connected?" Once I started thinking in relationships — SENT, RECEIVED_BY, SHARES_DEVICE, USES — queries that would have been painful to even conceptualize in SQL just fell out naturally. The graph doesn't just store connections; it makes you think in connections.
Tool design beats prompt engineering. I spent hours tuning the system prompt to get better investigation reports. The quality barely moved. Then I added temporal ordering and 3-hop circular detection to the tools, and the agent's output improved dramatically — because it was working with better data, not better instructions.
Graph databases lower the bar and raise the ceiling. As someone who'd never touched a graph database before this week, I was writing useful Cypher queries within hours. The syntax reads like a description of the pattern you're looking for — (a)-[:SENT]->(tx)-[:RECEIVED_BY]->(b) literally looks like the relationship it describes. But the ceiling is high too: the same intuitive syntax lets you express circular flow detection and multi-hop temporal chains that would be a nightmare in SQL.
Test your tools, not just your prompts. My first demo failed because the circular detection only checked 2-hop circles, but the laundering ring in my data was 3 hops. The LLM was doing its job — calling the right tool with the right parameters. The tool just wasn't looking deep enough. I would have caught this earlier if I'd tested the Cypher queries directly against the database instead of only testing through the agent.
Try asking it to investigate Priya Miller — the agent will find the circular money ring, the structuring pattern, and the shared device cluster, and explain exactly what it found.
Built for Graph Hacks: Context for AI Agents — Track 1: Agents That Act on Connected Data.
Tech stack: FalkorDB, Next.js, Vercel AI SDK, Google Gemini, MCP SDK, Canvas 2D