DEV Community

Vishesh Pandey
Vishesh Pandey

Posted on

Investigating Fraud with a Graph, Not Just a Prompt

September 2026
We built an agent that investigates flagged card transactions the way a fraud analyst would: pull the transaction graph, check the bank's actual policy, decide if it has enough to act, and revise that decision when new evidence comes in. Built for the TigerGraph "Agentic Fraud Investigation" hackathon (Hacker House Goa 2026), on the IEEE-CIS dataset extended with customers, closed cases, and a 20-case benchmark.
What we built
Given a trigger — a risk-scored transaction, a customer complaint, or an analyst request — the agent:

  1. Pulls the transaction's recent card activity, the cardholder's baseline behavior, and whether the device used shows up on any other cards, from a live graph.
  2. Checks the flagged activity against five documented fraud patterns (or flags it as undocumented) using the bank's own written policy as grounding, not the model's assumptions about what a bank should do.
  3. Assesses a fraud probability and decides whether it has enough evidence to act. If not, it asks a question — customer validation, step-up authentication, analyst input — and re-assesses once that answer is in.
  4. Recommends one or more actions with an approval route (auto, L1 team lead, or L2 fraud manager), citing the specific policy rule behind each one.
  5. Writes the finished case back into the graph, so the next investigation can find it. That write is the agent's memory. We ran it against all 20 cases in the hackathon's benchmark, each triggered by a real risk score, customer complaint, or analyst request, and produced a graded answer file for each: the case record, a suspicious activity report where policy required one, and the before/after next-best-action with its approval route. All 20 investigations are also browsable directly in a live dashboard: fraud-agent-tigergraph.streamlit.app -- evidence, uncertainty, before/after recommendations, and the fraud-ring subgraph for each case, no setup required. Architecture Trigger (risk score / customer report / analyst request) | v gather evidence --------> TigerGraph MCP --------> TigerGraph Savanna | (7 allow-listed tools) graph + native vectors v assess (Gemini, similar closed cases <---. structured output) policy / pattern text <--| vectorSearch | | v ClosedCase.emb, DocChunk.emb enough evidence? --no--> request evidence (simulated, |yes grounded in retrieved outcomes) v | decide action | (policy rules R1-R10, v coded, not prompted) re-assess --> decide again | v write case to graph + emit the graded answer JSON The investigation loop's state machine and its decision logic were built and validated first against a mock graph, which is what proves the mechanism — the evidence-gathering loop, the revised recommendation, the round cap — works independent of any one day's API quota. The graded run reuses that same decision logic and state model against the real graph, real MCP session, and real Gemini calls. How TigerGraph is used Fraud signals are relational. The same device showing up on two different cards, a billing region a customer has never used, a transaction pattern that matches (or breaks) a prior confirmed case — these are graph questions, not spreadsheet questions. Our schema (9 vertex types, 590,742 transactions, 14,322 cards, 13,553 customers, 5,565 closed cases) models Customer, Card, Transaction, DeviceProfile, EmailDomain, BillingRegion, ClosedCase, InvestigationCase, and DocChunk. Graph traversal as agent tools. Six installed GSQL queries are the agent's entire domain-specific tool surface: recent activity on a card, a customer's baseline profile, which other accounts share a device, and a two-hop device-based ring check that cross-references closed cases for confirmed fraud history. Native vector search, not a bolt-on vector database. Both case memory and policy grounding live as native TigerVector attributes in the same graph: ClosedCase.emb (5,565 closed-case narratives) and DocChunk.emb (the bank's fraud policy and five documented patterns), both 768-dimensional, cosine similarity. "Have we seen this before?" is a single vectorSearch call against the same database that holds the transaction graph, not a second system to keep in sync. TigerGraph MCP as the only path to the graph. The agent never queries TigerGraph directly. Every graph access goes through TigerGraph MCP over one held session, scoped to 7 tools via an allow-list rather than the full tool surface, which the server's own documentation notes can exceed roughly 29,000 tokens on its own — enough to blow out an agent's context before it does any reasoning. Agentic capabilities This is not a single prompt with tools attached. Three things make it agentic in the sense the brief asks for: Uncertainty is a first-class state, not an afterthought. The agent tracks a fraud probability, decides whether it clears a stopping threshold, and if it doesn't, takes a controlled action (simulating a customer or analyst response, since real replies aren't available in this exercise) before re-assessing. The recommendation before that extra evidence and after it are both recorded, along with what changed and why — which is exactly the behavior the hackathon's next-best-action scoring rewards. The decision logic is code, not a prompt. The bank's policy has ten numbered rules (verify before blocking on a weak signal, what to do when a customer denies a charge, when a report must be filed alongside a case, when to escalate). We implemented all ten as an explicit decision_matrix function with rule citations attached to every recommended action, rather than hoping an LLM reliably reproduces numbered policy text from a system prompt. The LLM's job is evidence synthesis and explanation — the policy itself is deterministic. Memory that a later investigation can actually use. Every closed case — 5,565 historical ones plus every new one this agent closes — is embedded and searchable. A new investigation can retrieve genuinely similar past cases and use their confirmed outcomes as evidence, not just as a nice-to-have citation. What we learned Raw graph signals need a plausibility filter before they count as evidence. Our first ring-detection query flagged a legitimate $77 purchase as connected to 59 confirmed-fraud cards. The cause: it seeded from a customer's entire multi-year device history rather than the one flagged transaction, so years of unrelated activity got pulled in. A second, subtler version of the same problem: a generic device fingerprint ("iOS Device," a common OS and browser combination, no specific model) matched 168 unrelated transactions purely because Vesta's original data doesn't always capture a specific device. Both are exactly the kind of over-eager pattern-matching the dataset's own README warns about ("half the cases are legitimate; an agent that blocks everything scores badly") — a graph traversal returning a large result isn't automatically a signal, it can just as easily be a query that isn't specific enough. Free-tier LLM quotas are not what the documentation implies. One reasoning model's free tier turned out to cap at 20 requests per day, not per minute — easy to exhaust with a handful of manual tests before ever starting a real run. A lighter model had independent, workable quota. The embedding endpoint had its own separate daily cap that we hit mid-run. The fix wasn't a smarter retry loop; it was making the pipeline degrade gracefully (skip an enrichment, don't lose the whole case) rather than treating every dependency as must-succeed. Infrastructure surprises cost more time than agent logic did. A pyTigerGraph authentication bug on Savanna, a GSQL SPLIT() call that fans out correctly for a vertex attribute but not for an edge, header rows that silently load as garbage data through one upload path but not another — none of these were exotic, but each one looked like our bug before it turned out to be a library or platform quirk. Verifying against the real system early, rather than trusting an API's documented behavior, was the difference between finding these in an hour and finding them at 11pm the night before the deadline. Hand-checking individual cases caught bugs no aggregate metric would have shown. Two real bugs surfaced only by reading full case narratives end to end, not by any summary statistic: a card's own high transaction volume (structurally just an active shopper, not a fraud ring) was being counted as ring evidence, fabricating a suspicious-activity report on an otherwise ordinary $112 purchase; and a case's free-text SAR narrative cited a different policy rule than the one the decision code had actually fired on, because the narrative step re-derived its own citation instead of being told the real one. Spot-checking three cases by hand -- not a bigger validation script -- is what found both, and it's why we kept doing it throughout the build. What we'd improve with more time • Real TigerGraph graph algorithms. Our ring detection is a hand-written two-hop traversal. TigerGraph's packaged algorithm library (Louvain, weakly connected components, k-core, PageRank) would give a principled community-detection signal instead of a bounded manual traversal, and is the natural next step for catching coordinated rings the current query can't see. • The external regulatory references. The dataset points to FinCEN, FATF, FFIEC, and OFAC guidance as optional reading. We grounded the agent in the bank's own policy and the five documented patterns, but didn't have time to chunk and embed the regulatory documents as well — they'd sharpen the suspicious-activity-report narratives in particular. • A tuned, evaluated ring-detection threshold, not a fixed cutoff. Our fix for the false-positive rings was a plausibility threshold picked from one observed case; a version calibrated against the closed-case history would be more defensible. • Finish embedding the full closed-case history and re-run the benchmark once daily API quotas reset, so every case gets the case-memory signal rather than the graceful-degradation fallback a few hit during the quota-constrained run. • A richer UI. The current dashboard is a Streamlit viewer over the finished answer files. A live view of the investigation as it happens — the graph traversal, the evidence arriving, the recommendation changing in front of you — would make the "revises its own decision" behavior easier to see than reading it after the fact.

Top comments (0)