<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krishna Kanoje</title>
    <description>The latest articles on DEV Community by Krishna Kanoje (@krishnakanoje207debug).</description>
    <link>https://dev.to/krishnakanoje207debug</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4141743%2F7087b3f1-222a-47e3-ab46-b9839e7da029.jpg</url>
      <title>DEV Community: Krishna Kanoje</title>
      <link>https://dev.to/krishnakanoje207debug</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/krishnakanoje207debug"/>
    <language>en</language>
    <item>
      <title>Building a fraud investigator that argues with itself</title>
      <dc:creator>Krishna Kanoje</dc:creator>
      <pubDate>Thu, 24 Sep 2026 18:15:18 +0000</pubDate>
      <link>https://dev.to/krishnakanoje207debug/building-a-fraud-investigator-that-argues-with-itself-18e9</link>
      <guid>https://dev.to/krishnakanoje207debug/building-a-fraud-investigator-that-argues-with-itself-18e9</guid>
      <description>&lt;p&gt;&lt;em&gt;Technical write-up for the TigerGraph Agentic Fraud Investigation hackathon (Hacker House Goa).&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The numbers, up front.&lt;/strong&gt; On 1,376 of the bank's own closed cases that nothing was fitted on:&lt;br&gt;
&lt;strong&gt;99.3%&lt;/strong&gt; verdict accuracy when the agent decides, only &lt;strong&gt;2 of 1,258&lt;/strong&gt; frauds called&lt;br&gt;
legitimate, &lt;strong&gt;99.6%&lt;/strong&gt; of its fraud calls right, and the fraud pattern named correctly&lt;br&gt;
&lt;strong&gt;92.8%&lt;/strong&gt; of the time. Our model scores &lt;strong&gt;0.886&lt;/strong&gt; AUC where the bank's own risk score scores&lt;br&gt;
&lt;strong&gt;0.052&lt;/strong&gt;. All &lt;strong&gt;20 of 20&lt;/strong&gt; exam answers are valid and written back to the graph.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;A fraud team's real job isn't scoring transactions — a model already does that, badly, at the&lt;br&gt;
edges. The job is investigation: take an alert, figure out how far it goes, decide whether it's&lt;br&gt;
actually fraud, and pick a defensible action. That's what we built an agent to do.&lt;/p&gt;

&lt;p&gt;Given a trigger — a risk-score alert, a customer's "I never made this purchase," or an&lt;br&gt;
analyst's "these cards are all touching the same weird device" — the agent opens a case, pulls&lt;br&gt;
the relevant neighbourhood out of a TigerGraph knowledge graph (the card's history, who else&lt;br&gt;
shares its device or billing region, similar past cases), and argues both sides of the question&lt;br&gt;
before deciding anything. A deterministic policy engine — not the LLM — turns that evidence into&lt;br&gt;
a recommendation and an approval route. If the policy engine can find evidence that would&lt;br&gt;
actually change the recommendation, the agent asks for it; if nothing would, it stops. Every&lt;br&gt;
closed case gets written back into the graph, so the next investigation that touches the same&lt;br&gt;
device or customer finds it.&lt;/p&gt;

&lt;p&gt;We ran this against IEEE-CIS card transaction data (590,742 transactions, ~13,500 customers,&lt;br&gt;
six months, no fraud label — replaced with a risk score the brief tells you not to trust), with&lt;br&gt;
5,565 of the bank's own closed investigations as labelled history and 20 held-out cases as the&lt;br&gt;
exam. Two things along the way surprised us, and we'll get to both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Five pieces, one graph. &lt;strong&gt;TigerGraph Savanna&lt;/strong&gt; holds the graph &lt;em&gt;and&lt;/em&gt; the vectors — customers,&lt;br&gt;
cards, transactions, device profiles, billing regions, closed cases, and the agent's own cases,&lt;br&gt;
all in one place, with 384-dimension embeddings (&lt;code&gt;BAAI/bge-small-en-v1.5&lt;/code&gt;, run locally) as vector attributes on &lt;code&gt;ClosedCase&lt;/code&gt;, &lt;code&gt;Doc&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;AgentCase&lt;/code&gt; vertices rather than a bolted-on vector database. &lt;strong&gt;TigerGraph MCP&lt;/strong&gt; is the agent's&lt;br&gt;
only door into that graph — no raw driver call anywhere in its code path, and the allowlist is&lt;br&gt;
deliberately short: the 8 installed queries, neighbour lookups, vector search, and node/edge&lt;br&gt;
writes for case memory. Nothing that can touch schema.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;GraphRAG context builder&lt;/strong&gt; takes what those queries return — a card's transaction window, a&lt;br&gt;
device's other cardholders, the nearest closed cases by embedding, the nearest policy and&lt;br&gt;
regulatory passages — and condenses it into one context block. The brief is explicit that the&lt;br&gt;
LLM should get &lt;em&gt;context&lt;/em&gt;, not raw data, so the query layer makes that the easy path:&lt;br&gt;
&lt;code&gt;device_neighbors&lt;/code&gt; doesn't return a table of transactions, it returns which cards touched a&lt;br&gt;
device profile in a window and what happened to them. &lt;strong&gt;The LLM&lt;/strong&gt; (Gemini Flash-Lite, free&lt;br&gt;
tier, OpenAI-compatible endpoint) plans which tool to call next and writes prose — the case&lt;br&gt;
summary, the SAR narrative, an undocumented-pattern description. It never sees a decision to&lt;br&gt;
make. That's the &lt;strong&gt;policy engine&lt;/strong&gt;'s job: &lt;code&gt;policy.py&lt;/code&gt; is pure Python, no LLM call inside, taking&lt;br&gt;
a plain findings dict — probability, pattern, independent-evidence count, shared-origin flags —&lt;br&gt;
and returning the action list, the approval route for each, whether a case or SAR is warranted,&lt;br&gt;
and whether to keep investigating. Same inputs, same outputs, every time — which is what makes&lt;br&gt;
the answer files auditable: every action carries a reason that cites a rule number.&lt;/p&gt;

&lt;h2&gt;
  
  
  How TigerGraph is used
&lt;/h2&gt;

&lt;p&gt;We started from the README's suggested schema — &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Transaction&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;DeviceProfile&lt;/code&gt;, &lt;code&gt;EmailDomain&lt;/code&gt;, &lt;code&gt;BillingRegion&lt;/code&gt;, &lt;code&gt;ClosedCase&lt;/code&gt; — and extended it with &lt;code&gt;AgentCase&lt;/code&gt;&lt;br&gt;
(the agent's own case memory) and &lt;code&gt;Doc&lt;/code&gt; (the GraphRAG corpus: policy text, the five documented&lt;br&gt;
patterns, and chunks from FinCEN's SAR narrative guidance, its account-takeover and&lt;br&gt;
identity-fraud advisories, the FFIEC manual, and FATF's cyber-enabled-fraud report). Edges are&lt;br&gt;
undirected, because an investigation walks them both ways — card-to-device is the same query as&lt;br&gt;
device-to-every-card-it-touched. The first real engineering decision was &lt;code&gt;card_id&lt;/code&gt;: the dataset&lt;br&gt;
has no card ID column, only a customer ID and anonymized card-network/type fields, so we derived&lt;br&gt;
&lt;code&gt;card_id = (customer_id, card4, card6)&lt;/code&gt; and checked it against the card on every transaction of&lt;br&gt;
all 5,565 closed cases and the 20 case-pack rows before trusting it: 14,975 of 14,975 matched.&lt;/p&gt;

&lt;p&gt;Eight installed GSQL queries do the investigation work, each scoped to a question rather than a&lt;br&gt;
table: &lt;code&gt;card_history&lt;/code&gt; (a card's own timeline), &lt;code&gt;device_neighbors&lt;/code&gt; (who else shares a device —&lt;br&gt;
the query that lit up the HHG-014 ring: a Samsung SM-G935F behind Chrome Android and an&lt;br&gt;
anonymous proxy, used as a new device by 19 other cards in the same window, by only 52 cards&lt;br&gt;
ever, with 44 closed cases already linked to them),&lt;br&gt;
&lt;code&gt;region_cluster&lt;/code&gt; (who's new to a billing region), &lt;code&gt;customer_cases&lt;/code&gt; (R10's "two cards already&lt;br&gt;
confirmed" check), and two &lt;code&gt;vectorSearch&lt;/code&gt; queries — &lt;code&gt;similar_cases&lt;/code&gt; and &lt;code&gt;search_docs&lt;/code&gt; — the&lt;br&gt;
GraphRAG retrieval itself. Two more, &lt;code&gt;device_ring_scan&lt;/code&gt; and &lt;code&gt;near_threshold_scan&lt;/code&gt;, don't answer&lt;br&gt;
a single case; they scan the whole November–December window for device profiles or near-$500&lt;br&gt;
purchase clusters recurring across cards. That's sentinel mode, and how we found two typologies&lt;br&gt;
outside the bank's five documented patterns: the device ring, and sub-$500 structuring (four&lt;br&gt;
purchases in thirty minutes on one card in HHG-006, each just under $500, $1,906.07 in total).&lt;/p&gt;

&lt;p&gt;We chose targeted GSQL pattern queries plus native &lt;code&gt;vectorSearch&lt;/code&gt; over the graph-algorithm&lt;br&gt;
library — no Louvain, no generic community detection. An unsupervised cluster ID is hard to put&lt;br&gt;
in a SAR narrative; "card C13487-K1 used device profile &lt;code&gt;SM-G935F Build/NRD90M | Android 7.0 |&lt;br&gt;
chrome 62.0 for android | 1920x1080&lt;/code&gt; as new, behind an anonymous proxy, like 19 other cards that&lt;br&gt;
window; 44 closed cases, CC-0020 among them, touch its cards" is a sentence a human can verify,&lt;br&gt;
"community 17" is not.&lt;/p&gt;

&lt;p&gt;The last piece is a gradient-boosting model trained on the bank's own closed cases — confirmed&lt;br&gt;
fraud as positive, cleared as negative, and a 15% sample of all unlabelled July–October&lt;br&gt;
transactions as negatives — validated on a strict time split (train on cases opened July–August,&lt;br&gt;
test on September–October, so nothing resembles the November–December exam). That sample&lt;br&gt;
includes transactions on cards that have cases: an earlier version left those cards out, and the&lt;br&gt;
model promptly learned "this card has case history" as a fraud signal, when the closed cases&lt;br&gt;
already hold about all the fraud. Its score is written back as &lt;code&gt;Txn.model_score&lt;/code&gt;, read through&lt;br&gt;
MCP like any other evidence. Here's the first surprise: time-split AUC on confirmed-vs-cleared&lt;br&gt;
came out at 0.886 (0.951 on all test transactions), while the bank's own risk score on the&lt;br&gt;
&lt;em&gt;same&lt;/em&gt; transactions scored 0.052 — not near 0.5, near zero. The bank's alerting model&lt;br&gt;
is anti-correlated with its own analysts' verdicts where those verdicts exist. We weren't&lt;br&gt;
looking for that; it fell out of the validation step, and it's a good demonstration of why the&lt;br&gt;
policy treats a risk score as "a reason to look, never a verdict."&lt;/p&gt;

&lt;p&gt;We measured the whole verdict stage on 1,376 closed September cases that nothing was fitted on.&lt;br&gt;
When the agent commits to fraud or legitimate it is right 99.3% of the time (95.8%&lt;br&gt;
class-balanced), and it commits on 55% of cases. The rest stay uncertain and go to&lt;br&gt;
verification. Only 2 of 1,258 confirmed frauds were called legitimate, its fraud calls are&lt;br&gt;
right 99.6% of the time, and it names the pattern correctly on 92.8% of confirmed fraud.&lt;/p&gt;

&lt;p&gt;It took two fixes, both found by this evaluation, to get there; decided accuracy was 86.0% and&lt;br&gt;
121 frauds were called legitimate before them. Our "recurring charge" defence matched any&lt;br&gt;
same-amount charges that happened to be about a month apart and overrode the verdict to&lt;br&gt;
legitimate. In the bank's history those matches were fraud every time: repeat charges on a stolen&lt;br&gt;
card look "recurring". The second fix was about what may clear a case at all. A low probability&lt;br&gt;
plus "looks like a trip" still hid fraud, while the one defence the cleared cases back is a new&lt;br&gt;
phone. So the agent now clears a case on its own only with that evidence. Otherwise it asks the&lt;br&gt;
customer, which costs one message and never blocks anyone. Both fixes held on the months the&lt;br&gt;
model was fitted on as well as on the holdout.&lt;/p&gt;

&lt;p&gt;Two more pieces of evidence come straight from the graph rather than from the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account history, read from the graph.&lt;/strong&gt; A "card" in this data mixes several underlying&lt;br&gt;
accounts, so the agent reconstructs the flagged transaction's account (card, billing region, and&lt;br&gt;
transaction date minus &lt;code&gt;D1&lt;/code&gt; within a day) and asks, through MCP &lt;code&gt;get_neighbors&lt;/code&gt; on the card's&lt;br&gt;
closed cases, whether any earlier transaction of that account was ever in one. A clean account —&lt;br&gt;
five or more earlier transactions at least a week before the flag, none in any case — caps the&lt;br&gt;
probability at 0.15 (0.17% of in-person transactions on such accounts were fraud, n=18,055); an&lt;br&gt;
account with a transaction in a confirmed case is prosecution evidence and, with a bank risk&lt;br&gt;
score ≥0.5, floors it at 0.9 (97.2% fraud, n=431). HHG-001 is the case this changed: a 0.79&lt;br&gt;
model score in a region away from home, but a clean account in region 444. That brings it down to&lt;br&gt;
0.15, and it is closed legitimate once the customer confirms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rare-device shared origin.&lt;/strong&gt; When the evidence already leans fraud, the agent checks whether&lt;br&gt;
the flagged device profile is rare (20 cards ever or fewer) and whether the same&lt;br&gt;
purchaser/recipient email pair turned up on two or more other cards from a week before the flag&lt;br&gt;
to opening. In August–October, 47.8% of such matches were fraud versus 11.2% without. It doesn't&lt;br&gt;
move the probability; it names connected cards, adds &lt;code&gt;MONITOR_CONNECTED_CARDS&lt;/code&gt;, and triggers a&lt;br&gt;
report — HHG-019 links C06224-K2 and C11309-K1 this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic capabilities
&lt;/h2&gt;

&lt;p&gt;Three things we'd call genuinely agentic, not a scripted pipeline with an LLM sprinkled on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The courtroom.&lt;/strong&gt; For every case, the agent builds a prosecution case and a defence case in&lt;br&gt;
parallel, testing the defence as hard as the prosecution. The bank's own cleared cases gave us&lt;br&gt;
the defence archetypes for free — a trip, a new phone, a recurring charge, a customer who says&lt;br&gt;
they meant to make the purchase. An agent that only looks for fraud finds it everywhere; half of&lt;br&gt;
this exam is legitimate, and the policy scores an agent that blocks everything badly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The decision-flip evidence gate&lt;/strong&gt; is the piece we're most pleased with. Instead of "if&lt;br&gt;
uncertain, ask the customer" as a blanket rule, &lt;code&gt;evidence_gate()&lt;/code&gt; simulates every possible&lt;br&gt;
answer to every evidence type the current recommendation would justify asking for — confirm,&lt;br&gt;
deny, no reply; step-up pass or fail — and re-runs the policy on each simulated outcome. If no&lt;br&gt;
possible answer changes the action set, the agent doesn't ask: the policy's stopping rule treats&lt;br&gt;
"further steps are unlikely to change the decision" as a first-class reason to stop, not an&lt;br&gt;
afterthought. Because replies are simulated, there's a second guard: when the probability sits&lt;br&gt;
between 0.30 and 0.70, a simulated answer would only echo the agent's own guess, so it records&lt;br&gt;
the request and assumes nothing — the case stays open or escalated as &lt;code&gt;uncertain&lt;/code&gt;. Four of the&lt;br&gt;
twenty benchmark cases end that way. Outside the band it simulates the reply the evidence points&lt;br&gt;
to and says so in the answer file.&lt;/p&gt;

&lt;p&gt;Those four open cases are where the console earns its keep. Each trace stores the agent's&lt;br&gt;
findings from before it asked for anything, so an analyst can enter the reply that actually came&lt;br&gt;
back. If the customer denies, confirms or goes silent, the policy engine re-decides then and&lt;br&gt;
there: actions, approval routes, case status, whether a SAR is due. The agent may execute only&lt;br&gt;
the &lt;code&gt;auto&lt;/code&gt; actions. A card block or a SAR waits for a team lead or fraud manager to approve it&lt;br&gt;
in the console. Each sign-off is appended to an audit log, together with the evidence replies it&lt;br&gt;
was based on.&lt;/p&gt;

&lt;p&gt;The customer has a screen too. In the cardholder portal a customer answers "Did you make this&lt;br&gt;
purchase?" for their own open cases, and the analyst console picks up that answer. They can also&lt;br&gt;
report a transaction they don't recognise, which opens a new case that the agent investigates&lt;br&gt;
live. Customers only ever see plain next steps. A SAR is never disclosed to its subject, so the&lt;br&gt;
portal says nothing about one. An analyst can likewise open a case from any transaction ID: the&lt;br&gt;
graph supplies the card, customer, amount and risk score, and the agent does the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case memory that compounds.&lt;/strong&gt; Every closed case becomes an &lt;code&gt;AgentCase&lt;/code&gt; vertex with edges to&lt;br&gt;
the transactions, cards, devices, closed cases, and documents it relied on, plus its own&lt;br&gt;
embedding. The next investigation's &lt;code&gt;similar_cases&lt;/code&gt; query searches both the bank's original&lt;br&gt;
5,565 cases and every case this agent has already written — a device ring found on case 3 is&lt;br&gt;
retrievable evidence by case 4, no batch reprocessing required. Only earlier ones, though:&lt;br&gt;
&lt;code&gt;AgentCase&lt;/code&gt;s created at or after a case's &lt;code&gt;opened_at&lt;/code&gt; are excluded from its retrieval, so memory&lt;br&gt;
never leaks the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;Building the policy engine before touching the LLM was the right call, even though it felt&lt;br&gt;
backwards at first — we wrote &lt;code&gt;policy.py&lt;/code&gt; and its tests against the README's rules using&lt;br&gt;
hand-picked evidence dicts, before the agent or the graph existed. That gave us ground truth to&lt;br&gt;
check the agent's findings-assembly against: if a case felt like it should end in &lt;code&gt;BLOCK_CARD&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;policy.decide()&lt;/code&gt; disagreed, the bug was in evidence gathering, not the rules.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;card_id&lt;/code&gt; derivation and the risk-score inversion taught the same lesson: read the data&lt;br&gt;
before trusting the brief's summary of it. We expected "a risk score, an input not an answer" to&lt;br&gt;
mean "sometimes wrong," not inverted on the transactions where ground truth exists — a finding&lt;br&gt;
worth reporting to the bank on its own. The same goes for our own features: a trip signature&lt;br&gt;
that looked like obvious defence evidence fired on 71% of confirmed out-of-region fraud cases&lt;br&gt;
and only 8% of cleared travel, so we kept it as a stated defence point but took it out of the&lt;br&gt;
calibration fit, which would otherwise have learned "trip" as a fraud signal. We also learned to respect the free tier early: Savanna's&lt;br&gt;
auto-resume adds real latency to a cold first query and 502/503s through several retries before&lt;br&gt;
answering, so the retry loop went into the REST client on day one instead of getting discovered&lt;br&gt;
mid-demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd improve with more time
&lt;/h2&gt;

&lt;p&gt;We had a few days, so we cut things. Here is what we left out, why, and what we would build next.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we cut for time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Real evidence channels.&lt;/strong&gt; The agent decides &lt;em&gt;whether&lt;/em&gt; to ask the customer, the cardholder or an&lt;br&gt;
analyst, but the replies are simulated, as the brief allows. A demo cardholder portal and the&lt;br&gt;
analyst console take real answers, and the policy engine decides again. But the portal's sign-in&lt;br&gt;
is a demo, and nothing sends an SMS, runs a real one-time passcode or waits on an inbox. R4's timers ("no reply within 24 hours", "monitor&lt;br&gt;
for 72 hours") are recorded as actions. They are not running clocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real sign-in and a tamper-proof audit trail.&lt;/strong&gt; Only a person can approve &lt;code&gt;L1&lt;/code&gt; and &lt;code&gt;L2&lt;/code&gt; actions,&lt;br&gt;
but the console's "signed in as" is a dropdown, not a login. Approvals go to a local&lt;br&gt;
append-only file, not into TigerGraph. No hash chain protects it, and an approved &lt;code&gt;BLOCK_CARD&lt;/code&gt;&lt;br&gt;
does not yet move the case's status in the graph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A live pipeline.&lt;/strong&gt; "Investigate live" runs the real agent against TigerGraph on demand, as a&lt;br&gt;
dry run. There is no alert stream feeding it and nothing runs it on a schedule: the 20 cases and&lt;br&gt;
the sentinel sweep are batch runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph algorithms.&lt;/strong&gt; We chose targeted GSQL pattern queries over TigerGraph's algorithm library&lt;br&gt;
because their output can be read and checked in a SAR. We never ran Louvain or connected&lt;br&gt;
components over the device/region/email co-occurrence graph. That run would show whether our&lt;br&gt;
"shared origin" and ring rules miss rings that never cross &lt;code&gt;min_cards&lt;/code&gt; in a single window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better evidence for "legitimate".&lt;/strong&gt; This is the biggest gap in accuracy. The agent now almost&lt;br&gt;
never clears a fraud (2 of 1,258 in the September holdout), but it gets there by asking the&lt;br&gt;
customer in 45% of cases. The frauds it used to clear look like cleared cases on every feature&lt;br&gt;
we have. The trip defence especially is unreliable. To clear more cases on the graph alone we&lt;br&gt;
would need data we did not build: device age, when a phone number, email or address last changed,&lt;br&gt;
travel confirmations, and a merchant ID. The dataset has none. R7's "same merchant" is&lt;br&gt;
approximated by product code plus amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluating the whole loop.&lt;/strong&gt; We measured the verdict stage on 1,376 held-out closed cases, but&lt;br&gt;
those cases do not record which actions were taken. The quality of actions and evidence requests&lt;br&gt;
is therefore checked only by the policy tests and the 20 exam cases, never against history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The knowledge corpus.&lt;/strong&gt; The corpus has 68 passages from a curated subset of the regulatory&lt;br&gt;
sources the README lists. &lt;code&gt;knowledge/SOURCES.md&lt;/code&gt; records what we skipped, mostly FATF documents&lt;br&gt;
further from card fraud. A larger corpus would sharpen SAR narratives for cases on the edge of a&lt;br&gt;
documented pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future scope
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Learn from outcomes, not just retrieve them.&lt;/strong&gt; Every analyst decision and every real customer&lt;br&gt;
reply is a label. Write them back onto the &lt;code&gt;AgentCase&lt;/code&gt; vertex, re-fit the calibration from them&lt;br&gt;
on a schedule, and let &lt;code&gt;similar_cases&lt;/code&gt; weigh how a similar case &lt;em&gt;ended&lt;/em&gt; as well as how it looked.&lt;br&gt;
Today case memory is retrieval. With outcomes it would make the next decision better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The production path the brief sketches.&lt;/strong&gt; Payment events arrive on a stream, get scored in&lt;br&gt;
real time, and the agent investigates. The graph supplies context, the policy and approval&lt;br&gt;
engine decides, action systems execute, and everything lands in an immutable audit log and case&lt;br&gt;
memory. Around that: OAuth/RBAC for analysts, a four-eyes rule for &lt;code&gt;L2&lt;/code&gt; actions, a secrets&lt;br&gt;
manager, and OpenTelemetry traces for every tool call and token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph intelligence.&lt;/strong&gt; Next come community detection, entity resolution that links customers&lt;br&gt;
across emails, devices and addresses (our derived &lt;code&gt;card_id&lt;/code&gt; is a stopgap), and graph embeddings&lt;br&gt;
as model features. Scoring could move into GSQL accumulators so the graph scores at query time&lt;br&gt;
instead of reading a precomputed &lt;code&gt;model_score&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An analyst workbench.&lt;/strong&gt; A case queue with SLA timers, reassignment and workload views. Sentinel&lt;br&gt;
finds would go into a human triage queue instead of a folder. A shadow mode would run the agent&lt;br&gt;
beside live analysts and measure how often they agree before it is trusted with more &lt;code&gt;auto&lt;/code&gt;&lt;br&gt;
actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask why the bank's score is inverted.&lt;/strong&gt; On transactions with ground truth the bank's risk score&lt;br&gt;
has an AUC of about 0.05. That is worth a study of its own, by product code, amount band and&lt;br&gt;
channel, before anyone trusts either model blindly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale.&lt;/strong&gt; A paid Savanna tier, load tests with many concurrent investigations, and a cache for&lt;br&gt;
the device and region neighbourhoods that most alerts share.&lt;/p&gt;




&lt;p&gt;Code: &lt;a href="https://github.com/krishnakanoje207-debug/hhgoa-ids-agent" rel="noopener noreferrer"&gt;https://github.com/krishnakanoje207-debug/hhgoa-ids-agent&lt;/a&gt; · Demo: &lt;a href="https://drive.google.com/file/d/16rMjGdsbA4KBOAKjSAZYw7p_FWWT6k_b/view" rel="noopener noreferrer"&gt;https://drive.google.com/file/d/16rMjGdsbA4KBOAKjSAZYw7p_FWWT6k_b/view&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built for HHGOA (@247pmstudio) on @TigerGraphDB Savanna.&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>ai</category>
      <category>graphdatabase</category>
      <category>tigergraph</category>
    </item>
  </channel>
</rss>
