Most fraud demos end with a big red "FRAUD 0.94". Real fraud desks don't work like that. An analyst gets a thin signal, pulls the account's history, finds that half of it points one way and half the other, and then decides what they're allowed to do next and who has to sign off.
For the TigerGraph x Hacker House Goa challenge I built GraphSentinel, an agent that investigates card-fraud cases the way an analyst would: it asks a transaction graph specific questions, keeps a receipt for every answer, weighs the evidence for and against, and hands the hard call to a human when the evidence doesn't settle it.
- Live demo (all 20 benchmark cases): https://graphsentinel-five.vercel.app
- Code: https://github.com/Fretzzz/tigergraph-agentic-fraud-investigator/pull/2
The problem
The organizers gave us about 590,000 card transactions from the IEEE-CIS dataset, with no "is fraud" label. Each transaction carries only the bank model's risk score. There are also four months of closed investigations, a fraud policy with rules R1-R10, five documented fraud patterns, and 20 benchmark cases. Each case comes from one of three triggers: a model alert, a customer dispute, or an analyst request.
The job for each case: decide fraud or not, recommend next actions under the policy, route each action to the right approval level, and file a suspicious activity report (SAR) when the policy requires one.
One case, start to finish: HHG-003
Customer C08623 writes in: "I never made this $49.00 purchase. Please check my card."
GraphSentinel runs eight graph queries. Here is what comes back:
| Question | What the graph says |
|---|---|
| Is region 330 new for this customer? | No. 42 of 985 prior transactions were there, going back to July 2016. |
| Is $49 unusual? | No. 68 prior transactions were within $1 of $49. |
| Is the purchaser email domain new? |
Yes. me.com never appeared before that day, and it also sits on a $116.93 purchase earlier the same day with a bank risk score of 0.88. |
| Devices shared with other customers? | No. |
| Prior cases on this card? | 6 closed cases, 5 of them confirmed fraud. |
| Links to other customers? | None in the 48-hour window. |
The place and amount fit the customer. The email domain doesn't. So GraphSentinel says uncertain, fraud probability 0.55. It recommends a card block that waits for L1 analyst approval, escalates to an analyst with the conflict written out, and records why no SAR is filed.
The trap it avoided
The tempting answer here is "out-of-region use, fraud 0.9". Region 299 is the customer's most common region (117 of 986 transactions), so 330 looks foreign next to it. But that's the wrong baseline. Across the full history this card used 54 distinct billing regions, and 14-25 different regions in a normal week. In the week before the dispute, region 330 showed up 8 times. A confident "out of region" call would rest on an assumption the data contradicts.
How TigerGraph is used
TigerGraph is where the investigation happens.
The graph. The HHG-003 case slice is loaded into a TigerGraph Savanna graph called GraphSentinel: customers, cards, transactions, billing regions, email domains, devices and closed cases as vertices, with typed, directed edges between them. (A lesson learned: every edge in the schema is directed, so an undirected -(:e)- pattern quietly matches nothing. You need typed directed hops like -(GS_BILLED_IN>:e)-.)
Eight installed GSQL queries. Each investigation question is its own installed query, so the traversal and filtering run inside TigerGraph and only the answer comes back:
| Query | Question |
|---|---|
gs_seed_context |
What is the flagged transaction? |
gs_region_history |
Has the customer used this region before, and since when? |
gs_email_domain_history |
Is the email domain new, and where else does it appear that day? |
gs_amount_profile |
Is this amount typical? |
gs_customer_timeline |
What happened in the 48 hours before the case? |
gs_device_context |
Do this customer's devices link to other customers? |
gs_historical_cases |
What prior cases exist on this card and customer? |
gs_shared_origin |
Does this transaction connect to other customers or cards? |
A live run with a parity check. With GRAPH_BACKEND=tigergraph, the investigator calls all eight queries on Savanna over REST. It then runs each one again on a local in-memory copy of the same graph and compares the results. If any result differs, the run fails. The committed HHG-003 run matches 8/8. Every claim in the case file points to the receipt of the query that produced it. The saved HHG-003 run on the site went through the official tigergraph-mcp server instead: same eight installed queries, called as MCP tools, read-only allowlist. It matched 8/8 too.
Honest labels. HHG-003 is the case that ran live on TigerGraph. The other 19 ran on the local graph with the same queries, and the site labels each page with the backend that produced it.
Architecture
organizer CSVs -> case slice -> graph (TigerGraph Savanna or local)
| 8 investigation queries
v
investigator (deterministic code)
| evidence + receipts
v
policy rules R1-R10 -> verdict, probability, actions, approval routes
|
v
GraphRAG-lite context -> LLM narrative (NVIDIA NIM, explains only)
|
v
case file (JSON) -> written back to TigerGraph -> analyst view (web)
- Case slicing: each case gets only the data it needs: the customer's full history plus a 48-hour neighborhood of other customers around the flagged transaction.
- Investigator: turns query results into weighted factors for and against fraud, each tied to a receipt. The weights are a visible rule, not a black box.
-
Policy gate: the agent may recommend anything, but actions carry approval routes (
auto,L1,L2). Money-moving actions likeBLOCK_CARDwait for a human. Nothing is executed on its own. - Controlled evidence gathering: when the agent needs the customer to confirm something, it records the request. It never invents a reply. The build uses one fixed default ("no reply within 24 hours") for every case.
- Stop rule: each case records why the agent stopped. Usually it's because the remaining questions (the customer's answer, the merchant's identity) aren't in the data.
- LLM narrative (explains, never decides): after the rules decide, an LLM (nvidia/nemotron-3-super-120b via NVIDIA NIM) writes the case summary. Every citation must match a receipt, rule, policy section or closed case from that case's context, and the summary must state the rule-based verdict and probability. Otherwise the narrative is rejected. All 20 cases have one; one case needed a retry after its first answer failed the check.
- GraphRAG-lite: the model's context is retrieved from the graph and the policy: the case's query receipts, similar closed cases from the graph, and the policy sections the rules cited. There is no vector index; retrieval is by graph query and rule ID.
-
Case write-back: each finished case is written to TigerGraph as a
GS_InvestigationCasevertex (verdict, probability, actions, SAR flag, stop reason, evidence) and read back to verify. All 20 are written. HHG-003 is also linked to its transaction, card and customer; the other 19 have no edges yet because their slices aren't loaded in TigerGraph.
Results on the 20 benchmark cases
All 20 cases have answer files anchored on each case's exact customer, card and transaction IDs. All 20 verdicts are "uncertain". That is a deliberate result, not a missing feature.
The organizer history shows why:
- Model alerts lean legitimate. 0 of 900 model-scored closed cases were confirmed fraud. Those cases land at p 0.30-0.60, and most ask the customer to confirm instead of blocking.
- Customer disputes lean fraud. 4,656 of 4,656 cardholder-reported closed cases were confirmed fraud. Those cases land at p 0.50-0.80, open a case, and recommend a block that waits for L1 approval.
- Conflicts get escalated. 6 cases go to an analyst under rule R8 with the conflict in the summary.
- SARs only when policy requires them. 2 of 20 cases file one. The other 18 say why not.
The fact that would settle most of these cases, the customer's answer, isn't in the dataset. An agent that turns a lean into a confident verdict anyway looks good on a slide and fails on the desk.
What's next
These are planned and not built yet:
- TigerGraph MCP for all 20 cases: only HHG-003 runs on TigerGraph (through MCP) today; loading the other 19 slices is next.
- All 20 cases on TigerGraph, matching the five documented fraud patterns, and graph algorithms for ring discovery.
- Vector retrieval: adding embeddings on top of the graph retrieval, if it improves the narratives.
What I learned
- The baseline is the investigation. The HHG-003 region question flips depending on whether you compare against the top region or the full history. Graph queries make the full-history comparison cheap, so there's no excuse to skip it.
- Receipts beat confidence. Tying every claim to a query result made bugs visible fast. A wrong sentence in the summary points straight at the query that fed it.
- Parity checks catch silent errors. Running each query on TigerGraph and locally caught schema mistakes (like undirected patterns that return nothing) that would otherwise have looked like "no evidence found".
- "Uncertain" is an answer. Knowing when to stop and ask a human is the hard part of fraud investigation, and it's the part most demos skip.
Built for the TigerGraph x Hacker House Goa hackathon. Thanks to the @TigerGraphDB team and the Hacker House Goa organizers.
Top comments (0)