{"slug": "graphsentinel-agentic-fraud-investigation", "title": "GraphSentinel- Agentic fraud investigation", "summary": "A developer built GraphSentinel, an agentic fraud-investigation and next-best-action system for the TigerGraph Agentic Fraud Investigation challenge. The system combines a graph store for structured evidence, a risk model for fraud probability, a policy engine for permissions and approvals, and a LangGraph state machine that selects follow-up queries using value-of-information scoring and records a next-best action before and after requesting additional evidence. It supports local graph, TigerGraph REST, and TigerGraph MCP backends under a shared query contract.", "body_md": "Fraud investigation is not only a classification problem.\n\nAn analyst needs to understand **why** a transaction is risky, gather the right evidence, follow policy, choose an action, document approvals, and preserve the investigation for the next case.\n\nGraphSentinel was built to explore that complete workflow.\n\nIt is an **agentic fraud-investigation and next-best-action system** for the TigerGraph Agentic Fraud Investigation challenge. The system starts from a risk alert, customer report, or analyst request and produces a traceable investigation record containing graph evidence, model belief, policy citations, evidence requests, actions, approvals, SAR decisions, and case memory.\n\nThe central idea is:\n\n**Use the graph to gather structured evidence, use a model to estimate risk, use policy to constrain actions, and use an agent to decide what investigation should happen next.**\n\nGraphSentinel combines five main ideas:\n\nThe interesting part is that the workflow records a **next-best action before requesting additional evidence**.\n\nIf the case is still uncertain, the system chooses an allowed evidence request using value-of-information scoring. After the response, it updates its belief and records another next-best action.\n\nThis makes the effect of evidence visible instead of hiding everything inside a final classification.\n\nThe application supports:\n\n```\nLocal graph store\n       │\n       ├── Offline development\n       │\n       ├── TigerGraph REST\n       │\n       └── TigerGraph MCP\n```\n\nThe local graph implementation follows the same query contract as the TigerGraph backends, allowing the investigation workflow to be tested without requiring a live TigerGraph instance.\n\nAt a high level, an investigation follows this path:\n\n```\nTrigger\n  │\n  ▼\nIntake\n  │\n  ▼\nBaseline Graph Evidence\n  │\n  ▼\nAgent-selected Follow-up Queries\n  │\n  ├───────────────┐\n  ▼               ▼\nSimilar Cases    GraphRAG\n  │               │\n  └───────┬───────┘\n          ▼\nRisk Model + Fraud Classification\n          │\n          ▼\nPolicy Decision\n          │\n          ▼\nNBA Before Evidence\n          │\n          ▼\n   Is the case uncertain?\n       /          \\\n     No            Yes\n     │              │\n     │              ▼\n     │        Select Evidence\n     │              │\n     │              ▼\n     │        Apply Response\n     │              │\n     │              ▼\n     │        Update Belief\n     │              │\n     │              ▼\n     │        NBA After Evidence\n     │              │\n     └──────┬───────┘\n            ▼\n Actions / Approvals / SAR\n            │\n            ▼\n      Explanation\n            │\n            ▼\n      Graph Write-back\n```\n\nThe main runtime is assembled by `services/runtime.py`. It loads the dataset, graph store, policy configuration, pattern library, risk model, likelihood tables, case repository, evidence provider, and optional CrewAI client.\n\nThe `agent/workflow.py` module builds the LangGraph state machine with explicit nodes for:\n\n```\nintake\nbaseline evidence\nfollow-ups\nmemory / RAG\nassessment\ndecision\nevidence\nfinalization\n```\n\nThis explicit state-machine approach makes the investigation path easier to test and reason about than putting the entire workflow inside a single agent prompt.\n\nOne of the most important architectural decisions was deliberately separating responsibilities.\n\n```\n┌───────────────────────────┐\n│        TigerGraph         │\n│                           │\n│ Evidence + Relationships  │\n└─────────────┬─────────────┘\n              │\n              ▼\n┌───────────────────────────┐\n│        Risk Model         │\n│                           │\n│ Fraud probability         │\n└─────────────┬─────────────┘\n              │\n              ▼\n┌───────────────────────────┐\n│       Policy Engine       │\n│                           │\n│ Permissions + Approvals   │\n└─────────────┬─────────────┘\n              │\n              ▼\n┌───────────────────────────┐\n│       Agent / LLM         │\n│                           │\n│ Follow-up + Explanation   │\n└─────────────┬─────────────┘\n              │\n              ▼\n┌───────────────────────────┐\n│       Action Gateway      │\n│                           │\n│ Approved actions only     │\n└───────────────────────────┘\n```\n\nThe graph supplies evidence.\n\nThe risk model estimates fraud probability.\n\nThe policy engine determines permissions and approval routes.\n\nThe LLM proposes optional follow-up work and generates language.\n\nThe action gateway executes only policy-approved actions.\n\nThe LLM is **never allowed to authorize or execute a protective action**.\n\nIts JSON output is validated, unknown tool names are discarded, citations are filtered against retrieved policy clauses, and failures fall back to deterministic templates.\n\nEvery investigation begins with a focal transaction.\n\nThe system gathers a bounded set of temporal graph queries:\n\n```\nTransaction details + owner\n        │\n        ├── Customer history\n        ├── Card activity\n        ├── Shared devices\n        ├── Address peers\n        ├── Customer case history\n        └── Linked cases\n```\n\nRepresentative queries include:\n\n```\ngs_txn_detail\ngs_customer_history\ngs_device_customers\ngs_address_peers\ngs_linked_cases\n```\n\nThese queries are transformed into signals such as:\n\n```\nsignals = {\n    \"amount_ratio\": amount_ratio,\n    \"device_novelty\": device_novelty,\n    \"account_age\": account_age,\n    \"card_velocity\": card_velocity,\n    \"email_mismatch\": email_mismatch,\n    \"linked_confirmed_cases\": linked_confirmed_cases,\n    \"graph_fraud_proximity\": fraud_proximity,\n    \"address_cluster_size\": address_cluster_size,\n}\n```\n\nA critical constraint is that behavioral queries are evaluated strictly **before the focal event**.\n\nIf a transaction occurred on January 10, information that only became available on January 20 cannot influence the January 10 decision.\n\nA simplified interface therefore looks like:\n\n``` python\ndef get_customer_history(\n    customer_id: str,\n    as_of: datetime,\n):\n    return graph.run_query(\n        \"gs_customer_history\",\n        {\n            \"customer_id\": customer_id,\n            \"as_of\": as_of.isoformat(),\n        },\n    )\n```\n\nThis `as_of` boundary is part of the graph-access layer rather than an assumption made by the analyst.\n\nBaseline evidence is not always enough.\n\nThe agent can select additional graph investigations, for example:\n\n```\nDevice ring expansion\nCard activity\nAddress-cluster transactions\nCommunity statistics\nSpending trajectory\n```\n\nThe agent can select up to three additional tools.\n\nCrewAI acts as a bounded investigation assistant for this stage.\n\nIt receives the available signals and a menu of installed tools and returns structured output such as:\n\n```\n{\n  \"tools\": [\n    {\n      \"name\": \"device_ring\",\n      \"reason\": \"Device is shared with multiple high-risk customers.\"\n    },\n    {\n      \"name\": \"address_cluster\",\n      \"reason\": \"Several recently created accounts share this address.\"\n    }\n  ]\n}\n```\n\nThe important part is that the model doesn't receive arbitrary database access.\n\nThe returned tool names are validated against the installed tool registry:\n\n```\nTOOLS = {\n    \"device_ring\": investigate_device_ring,\n    \"card_activity\": investigate_card_activity,\n    \"address_cluster\": investigate_address_cluster,\n    \"community_stats\": investigate_community,\n    \"spending_trajectory\": investigate_spending,\n}\n\ndef validate_tools(requested):\n    return [\n        tool\n        for tool in requested\n        if tool[\"name\"] in TOOLS\n    ]\n```\n\nUnknown tools are discarded.\n\nIf the model produces malformed output or fails completely, the workflow falls back to deterministic planning.\n\nThis creates a controlled boundary:\n\n```\nLLM\n │\n │ proposes\n ▼\nTool Registry\n │\n │ validates\n ▼\nInstalled Graph Queries\n```\n\nrather than:\n\n```\nLLM ─────────────► arbitrary database access\n```\n\nFraud investigation requires more than transaction data.\n\nThe agent may need to understand:\n\nGraphSentinel therefore uses GraphRAG.\n\nThe case is embedded and compared with previous cases using both vector similarity and structural relationships such as shared devices and cards.\n\nPolicy documents are parsed into a document graph containing:\n\n```\nDocumentChunk\n     │\n     ├── PolicyClause\n     ├── Pattern\n     └── Regulation\n```\n\nRetrieval combines vector search with graph expansion:\n\n```\nUser / Case Question\n        │\n        ▼\nVector Search\n        │\n        ▼\nRelevant Document Chunks\n        │\n        ▼\nGraph Expansion\n        │\n        ▼\nPolicy / Pattern / Regulation Context\n        │\n        ▼\nCited Investigation Context\ngs_similar_cases_vec\ngs_doc_search_vec\ngs_policy_context\n```\n\nThis allows the system to attach policy citations to evidence requests, actions, explanations, and SAR decisions.\n\nVector similarity alone is not enough for fraud investigations.\n\nTwo cases may have similar descriptions but completely different graph structures.\n\nTherefore case retrieval combines:\n\n```\nVector similarity\n       +\nShared devices\n       +\nShared cards\n       +\nStructural relationships\n       +\nPrevious case outcomes\n```\n\nConceptually:\n\n```\nsimilar_cases = retrieve_similar_cases(\n    embedding=current_case_embedding,\n    graph_links=[\n        customer_id,\n        *device_ids,\n        *card_ids,\n    ],\n)\n```\n\nThis allows the risk model to use historical cases without reducing the investigation to a simple nearest-neighbor search.\n\nAfter graph evidence, follow-up investigation, and memory retrieval, the system estimates fraud probability.\n\nThe risk model is a regularized logistic model.\n\n```\nfeatures = [\n    bank_risk_score,\n    amount_ratio,\n    device_novelty,\n    account_age,\n    card_velocity,\n    graph_fraud_proximity,\n    confirmed_case_links,\n    similar_case_outcomes,\n    pattern_strength,\n    trigger_type,\n]\n\nfraud_probability = model.predict_proba(\n    [features]\n)[0, 1]\n```\n\nThe resulting belief is separated into fraud hypotheses:\n\n```\nLegitimate\nThird-party fraud\nFirst-party fraud\n```\n\nEvidence can later update those hypotheses.\n\nFor example, the supplied likelihood tables can make a failed step-up authentication increase the probability of third-party fraud, while a passed authentication shifts belief in the other direction.\n\nThis is one of the most important parts of the architecture.\n\nThe policy engine applies configurable thresholds.\n\n```\nif probability < CLEAR_THRESHOLD:\n    decision = \"clear\"\n\nelif probability > ACTION_THRESHOLD:\n    decision = \"action\"\n\nelse:\n    decision = \"uncertain\"\n```\n\nFor uncertain cases, the system evaluates potential evidence requests.\n\nBut **policy is applied before optimization**.\n\nSuppose we have:\n\n```\nCustomer validation\nStep-up authentication\nAnalyst review\nDevice investigation\n```\n\nA naive implementation might calculate information gain first.\n\nGraphSentinel instead does:\n\n```\nCandidate Evidence\n       │\n       ▼\nPolicy Filter\n       │\n       ▼\nAllowed Evidence\n       │\n       ▼\nValue of Information\n       │\n       ▼\nSelected Evidence\nallowed = []\n\nfor request in evidence_requests:\n\n    policy_result = policy.check(\n        case=case,\n        request=request,\n    )\n\n    if policy_result.allowed:\n        allowed.append(request)\n\nbest_request = max(\n    allowed,\n    key=lambda request:\n        expected_information_gain(request)\n        - request_cost(request)\n)\n```\n\nThis matters because a request can be statistically useful while still being impermissible.\n\nFor example:\n\nThe optimizer never sees those prohibited requests.\n\nBefore any additional evidence is requested, the system records the current next-best action.\n\n```\nnba_before = {\n    \"decision\": \"escalate\",\n    \"probability\": 0.71,\n    \"risk_level\": \"high\",\n    \"fraud_class\": \"third_party\",\n    \"action\": \"hold_transaction\",\n    \"permission\": \"approval\",\n    \"approval_route\": \"fraud_analyst\",\n    \"policy_clause\": \"POL-2.1\",\n}\n```\n\nThis creates an important property:\n\nWe know what the system would have done before seeing the additional evidence.\n\nEach NBA records information such as:\n\n```\ndecision\nprobability\nrisk level\nfraud class\nselected evidence request\nexcluded requests\npolicy reasons\nactions\npermission type\napproval route\npolicy clause\nreceived evidence\n```\n\nIf the case remains uncertain, the selected evidence request is executed.\n\n```\nevidence = {\n    \"type\": \"step_up_auth\",\n    \"result\": \"failed\",\n}\n```\n\nThe result updates the fraud hypotheses.\n\n```\nposterior = bayesian_update(\n    prior=belief,\n    evidence=evidence,\n    likelihoods=likelihood_tables,\n)\n```\n\nThe important architectural property is that evidence is represented as an explicit transition:\n\n```\nBelief Before\n     │\n     ▼\nNBA Before\n     │\n     ▼\nEvidence Request\n     │\n     ▼\nEvidence Response\n     │\n     ▼\nBelief Update\n     │\n     ▼\nNBA After\n```\n\nThis makes it possible to inspect exactly how new evidence changed the investigation.\n\nAfter updating the belief, the system records a second next-best action.\n\n```\nnba_after = {\n    \"decision\": \"protect\",\n    \"probability\": posterior.fraud_probability,\n    \"fraud_class\": posterior.fraud_class,\n    \"action\": \"block_transaction\",\n    \"permission\": \"approval\",\n    \"approval_route\": \"fraud_analyst\",\n    \"policy_clause\": \"POL-2.1\",\n}\n```\n\nThe case now contains a complete decision timeline:\n\n```\nInitial Evidence\n      │\n      ▼\nInitial Belief\n      │\n      ▼\nNBA Before Evidence\n      │\n      ▼\nEvidence Request\n      │\n      ▼\nEvidence Response\n      │\n      ▼\nUpdated Belief\n      │\n      ▼\nNBA After Evidence\n```\n\nThis is more informative than simply returning:\n\n```\n{\n  \"fraud\": true\n}\n```\n\nThe complete investigation is represented as a state machine.\n\nA simplified version looks like:\n\n``` python\nfrom langgraph.graph import StateGraph, END\n\nworkflow = StateGraph(InvestigationState)\n\nworkflow.add_node(\"intake\", intake)\nworkflow.add_node(\"baseline\", baseline_evidence)\nworkflow.add_node(\"followups\", followup_planning)\nworkflow.add_node(\"memory\", retrieve_memory)\nworkflow.add_node(\"assessment\", assess_risk)\nworkflow.add_node(\"decision\", policy_decision)\nworkflow.add_node(\"evidence\", request_evidence)\nworkflow.add_node(\"finalize\", finalize_case)\n\nworkflow.set_entry_point(\"intake\")\n\nworkflow.add_edge(\"intake\", \"baseline\")\nworkflow.add_edge(\"baseline\", \"followups\")\nworkflow.add_edge(\"followups\", \"memory\")\nworkflow.add_edge(\"memory\", \"assessment\")\nworkflow.add_edge(\"assessment\", \"decision\")\n\nworkflow.add_conditional_edges(\n    \"decision\",\n    route_after_decision,\n    {\n        \"evidence\": \"evidence\",\n        \"finalize\": \"finalize\",\n    },\n)\n\nworkflow.add_edge(\"evidence\", \"assessment\")\nworkflow.add_edge(\"finalize\", END)\n\napp = workflow.compile()\n```\n\nThe important property is that an evidence response can send the case back through assessment.\n\nThe system is therefore:\n\n```\nTrigger\n   ↓\nInvestigate\n   ↓\nAssess\n   ↓\nDecide\n   ↓\nNeed evidence?\n   ├── No ───────► Finalize\n   │\n   └── Yes\n        ↓\n     Evidence\n        ↓\n     Reassess\n        ↓\n     Decide\n```\n\nThis is the core agentic loop.\n\nTigerGraph is the graph system of record for investigation evidence and case memory.\n\nThe graph contains vertices for:\n\n```\nTransaction\nCustomer\nDevice\nCard\nAddress\nFraudCase\nFinding\nAction\nDocumentChunk\nPolicyClause\nPattern\nRegulation\n```\n\nEdges represent relationships such as:\n\n```\nCustomer ──owns──────► Card\nCustomer ──uses──────► Device\nCustomer ──lives_at──► Address\nTransaction ──belongs_to──► Customer\nCase ──has_finding──► Finding\nCase ──has_action────► Action\nCase ──similar_to────► Case\nDocument ──references─► PolicyClause\n```\n\nThis turns the fraud investigation into a connected evidence problem rather than a flat feature table.\n\nEvery graph read is a named installed query.\n\nThe contract in `graph/contract.py` defines query names, parameters, and result parsing.\n\n```\nQUERY_CONTRACT = {\n    \"gs_txn_detail\": [\n        \"txn_id\",\n    ],\n\n    \"gs_customer_history\": [\n        \"cust\",\n        \"as_of\",\n        \"max_rows\",\n    ],\n\n    \"gs_address_peers\": [\n        \"cust\",\n        \"window_sec\",\n        \"as_of\",\n        \"min_first_seen\",\n    ],\n\n    \"gs_similar_cases_vec\": [\n        \"query_vec\",\n        \"k\",\n    ],\n}\n```\n\nThe local graph store, TigerGraph REST store, and MCP store all implement the same interface.\n\nThis gives us two major advantages:\n\nRepresentative installed queries include:\n\n```\ngs_txn_detail\ngs_customer_history\ngs_device_customers\ngs_address_peers\ngs_linked_cases\ngs_similar_cases_vec\ngs_doc_search_vec\ngs_policy_context\n```\n\nThe repository also includes:\n\n```\nWeakly Connected Components\nLouvain Communities\nPersonalized PageRank\n```\n\nPersonalized PageRank is seeded from confirmed-fraud customers to create a graph-based fraud-proximity feature.\n\nThe setup also computes:\n\n```\ndevice degrees\ncustomer links\nconnected components\ncommunities\nfraud proximity\n```\n\nHighly connected hub devices are excluded from useful proximity signals because shared corporate or public devices can otherwise create misleading fraud relationships.\n\nThe agent-plane MCP adapter exposes four operations:\n\n```\nrun_installed_query\nadd_nodes\nadd_edges\nget_node\n```\n\nThe architecture becomes:\n\n```\nLangGraph Agent\n      │\n      ▼\nTigerGraph MCP\n      │\n      ├── run_installed_query\n      ├── get_node\n      ├── add_nodes\n      └── add_edges\n      │\n      ▼\nTigerGraph\n```\n\nThe MCP server is launched over stdio using the same TigerGraph configuration.\n\nThe repository also contains an MCP emulator backed by the local graph store.\n\nThis made it possible to test the complete MCP path without requiring a live TigerGraph deployment.\n\nBefore investigations run, graph-derived features are precomputed.\n\n```\ngraph.compute_device_degrees()\ngraph.compute_customer_links()\ngraph.compute_wcc()\ngraph.compute_louvain()\ngraph.compute_fraud_pagerank()\n```\n\nThese values can then be used during investigation instead of repeatedly traversing the entire graph.\n\nOne of the less obvious challenges was preventing future information from leaking into the investigation.\n\nConsider:\n\n```\nJanuary 10\n   │\n   └── suspicious transaction\n\nJanuary 15\n   │\n   └── investigation starts\n\nJanuary 20\n   │\n   └── case confirmed as fraud\n```\n\nThe January 20 outcome must not become a feature for the January 10 transaction.\n\nTherefore graph queries use:\n\n```\nas_of = case_opened_at\n```\n\nand only retrieve information available before the relevant event.\n\nThe same principle applies to customer history, account age, devices, cards, and linked cases.\n\nLeft-censored accounts also need special handling. If the available dataset starts after the account was created, we shouldn't automatically classify that account as \"new.\"\n\nThese rules belong in the graph access layer and tests, not only in analyst convention.\n\nThe investigation does not disappear after the final API response.\n\nThe case is written back to graph memory as a `FraudCase`.\n\n```\ngraph.add_node(\n    \"FraudCase\",\n    {\n        \"id\": case.id,\n        \"status\": case.status,\n        \"embedding\": case.embedding,\n    },\n)\n```\n\nFindings and actions are then connected:\n\n```\ngraph.add_node(\n    \"Finding\",\n    {\n        \"id\": finding.id,\n        \"type\": finding.type,\n        \"confidence\": finding.confidence,\n    },\n)\n\ngraph.add_edge(\n    \"HAS_FINDING\",\n    case.id,\n    finding.id,\n)\n```\n\nThe case can be connected to:\n\n```\nCustomer\nFocal Transaction\nRelated Transactions\nFindings\nActions\nSimilar Cases\n```\n\nThis creates a continuous memory loop:\n\n```\nPast Investigations\n        │\n        ▼\n    Case Memory\n        │\n        ▼\n New Investigation\n        │\n        ▼\nNew Findings\n        │\n        ▼\nUpdated Memory\n```\n\nBut agent outcomes and analyst-confirmed outcomes are deliberately kept separate.\n\nAn agent prediction should not automatically become training ground truth.\n\nOnly analyst-confirmed outcomes should become authoritative learning data.\n\nSAR eligibility is evaluated by policy.\n\nThe system considers factors such as:\n\n```\nPosterior probability\nAggregate amount\nSuspect identification\nMoney-laundering indicators\nApplicable thresholds\n```\n\nWhen a SAR is required, the agent can draft the narrative:\n\n```\nif policy.requires_sar(case):\n\n    sar = draft_sar(\n        case=case,\n        evidence=evidence,\n        citations=policy_context,\n    )\n\n    approval_queue.submit(\n        sar,\n        role=\"bsa_officer\",\n    )\n```\n\nThe LLM can help write the narrative, but it does not independently authorize the SAR.\n\nThe policy engine controls eligibility and the required approval route.\n\nThe system is agentic in a constrained, auditable sense.\n\nIt can:\n\nThe important design choice is that agency is bounded by contracts and policy.\n\nThe agent can explore and explain.\n\nIt cannot silently bypass an approval route or turn an uncertain case into an automatic protective action.\n\nGraphSentinel also contains a discovery loop for closed investigations.\n\nThe idea is:\n\n```\nClosed Cases\n     │\n     ▼\nResidual Analysis\n     │\n     ▼\nUnexpected Pattern\n     │\n     ▼\nCandidate Rule\n     │\n     ▼\nPolicy Review\n```\n\nFor example, the discovery system might identify an unexplained cluster of newly created accounts sharing the same address.\n\nThe important part is that discovery does **not** automatically become policy.\n\nThe candidate is flagged for review:\n\n```\ncandidate_pattern = {\n    \"pattern\": pattern,\n    \"documented\": False,\n    \"requires_policy_review\": True,\n}\n```\n\nThis keeps pattern discovery separate from authorization.\n\nOnce the investigation is complete:\n\n```\nAutomatic actions\n        │\n        ▼\nMock Action Gateway\n\nApproval actions\n        │\n        ▼\nRequired Approval Route\n\nSAR required\n        │\n        ▼\nBSA Officer Approval\n\nAll paths\n        │\n        ▼\nExplanation\n        │\n        ▼\nGraph Memory\n```\n\nAutomatic actions are sent to the mock gateway.\n\nApproval actions are queued for the required role.\n\nSAR eligibility is evaluated using the policy rules.\n\nFinally, the complete case is written back to graph memory.\n\nThe project can run without TigerGraph for the initial development loop.\n\n```\npip install -e \".[dev]\"\n\ngraphsentinel synth\n\ngraphsentinel build\n\ngraphsentinel run-benchmark\n\ngraphsentinel eval\n\ngraphsentinel serve\n\npytest -q\n```\n\nThe local graph store follows the same query contract as the TigerGraph implementation.\n\nFor TigerGraph:\n\n```\ncp .env.example .env\n\n# Configure:\n# GS_MODE=tigergraph\n# TG_* variables\n\ngraphsentinel tg-setup\n\ngraphsentinel tg-check\n\ngraphsentinel serve\n```\n\nThe default agent access path is MCP:\n\n```\nGS_TG_ACCESS=mcp\n```\n\nREST access is also supported:\n\n```\nGS_TG_ACCESS=rest\n```\n\nAdministrative operations such as DDL, bulk loading, and algorithm setup use the REST path.\n\nThe application also supports the actual HHGOA/IEEE-style dataset through configurable column mappings.\n\nThe dataset directory is configured with:\n\n```\nGS_DATA_DIR=/path/to/dataset\n```\n\nThe loader resolves file and column names using:\n\n```\nconfig/dataset_mapping.yaml\n```\n\nFor the provided benchmark case pack:\n\n```\ncase_pack.csv\n```\n\nis placed alongside the dataset files.\n\nThe benchmark can then be run with:\n\n```\nGS_DATA_DIR=/path/to/case-pack \\\ngraphsentinel run-benchmark\n```\n\nThe generated cases are written as:\n\n```\ncases/\n├── HHG-001.json\n├── HHG-002.json\n├── ...\n└── HHG-020.json\n```\n\nEach investigation record contains the investigation evidence, findings, decisions, actions, graph write-back status, SAR details where applicable, and the next-best action before and after evidence.\n\nThe project contains two different evaluation modes.\n\nThe first is the generated benchmark.\n\nThe second is temporal replay.\n\nThis distinction is important because the benchmark uses synthetic data where the generator deliberately plants patterns.\n\nThe benchmark therefore demonstrates that the system behaves correctly against the generated ground truth.\n\nIt should not be interpreted as a production fraud-detection accuracy estimate.\n\nThe temporal replay is a more realistic test because the model trains on earlier cases and investigates later cases.\n\nThe repository reports:\n\n```\nTraining:\nMonths 1–3\n69 closed cases\n\nReplay:\n37 later cases\n```\n\nThe replay produced:\n\n```\n21 cases decided directly\n20 correct decisions\n16 escalations\nPrecision: 1.0\n```\n\nThe important limitation is that many escalations came from evidence requests for which no response was recorded in the closed-case data.\n\nThis means the system sometimes correctly identifies uncertainty but does not have enough historical evidence to resolve it automatically.\n\nThat is an important difference between:\n\n```\n\"I don't know\"\n```\n\nand:\n\n```\n\"I am confident this is legitimate.\"\n```\n\nA production system should preserve that distinction.\n\nA large graph is not automatically useful.\n\nDevice, card, and address relationships matter when they:\n\nThe query contract helped keep graph investigation focused on decisions rather than graph traversal for its own sake.\n\nFraud data contains future outcomes, later cases, and accounts that may predate the available dataset.\n\nEvery query therefore needs an `as_of` boundary.\n\nLeft-censored accounts must also be handled correctly.\n\nOtherwise a seemingly good model can quietly learn from information that would not have been available at decision time.\n\nA request can be statistically informative and still be impermissible.\n\nTherefore:\n\n```\nEvidence candidates\n       ↓\nPolicy constraints\n       ↓\nAllowed candidates\n       ↓\nValue-of-information\n       ↓\nSelected evidence\n```\n\nThis ordering prevents an optimizer from selecting a request that creates customer-contact or tipping-off risk.\n\nAgent-confirmed and agent-cleared cases are useful memory.\n\nBut they are not automatically analyst ground truth.\n\nKeeping those outcomes separate prevents feedback loops where the model starts training on its own previous decisions.\n\nLLM calls can fail because of:\n\n```\nCredentials\nRate limits\nProvider changes\nMalformed JSON\nNetwork failures\n```\n\nThe investigation should still continue.\n\nThat's why GraphSentinel has deterministic planning and explanation fallbacks.\n\nThe LLM is an optional reasoning and language layer, not a single point of failure.\n\nExecute every GSQL query against a real TigerGraph Savanna or Community Edition deployment and add stronger deployment/version checks.\n\nReplace the mock action gateway with authenticated banking, notification, evidence-provider, and e-filing integrations.\n\nReplace the development `X-Role` header with an identity-provider integration and enforce role claims at a trusted proxy boundary.\n\nTrain and calibrate thresholds on real closed investigations, monitor drift, and add confidence intervals and champion/challenger evaluation.\n\nConnect real authentication, customer-validation, and analyst-review systems with asynchronous response handling.\n\nRun the complete `HHG-001` through `HHG-020` case pack and compare decisions with independent review.\n\nThe current console is a lightweight static analyst UI. A production version would use richer graph interactions, accessibility improvements, and durable event streaming.\n\nAdd structured traces for:\n\n```\nGraph latency\nModel versions\nLLM calls\nPolicy decisions\nApproval turnaround\nAction outcomes\n```\n\nThese would be essential for production monitoring.\n\nThe entire system can ultimately be reduced to this:\n\n```\n                  ┌───────────────────┐\n                  │      TRIGGER      │\n                  │                   │\n                  │ Risk alert        │\n                  │ Customer report   │\n                  │ Analyst request   │\n                  └─────────┬─────────┘\n                            │\n                            ▼\n                  ┌───────────────────┐\n                  │    LANGGRAPH      │\n                  │      AGENT        │\n                  └─────────┬─────────┘\n                            │\n              ┌─────────────┼─────────────┐\n              │             │             │\n              ▼             ▼             ▼\n        ┌──────────┐  ┌──────────┐  ┌───────────┐\n        │TigerGraph│  │ GraphRAG │  │   Case    │\n        │          │  │          │  │  Memory   │\n        │ Evidence │  │ Policies │  │  Similar  │\n        │ Relations│  │ Patterns │  │  Cases    │\n        └────┬─────┘  └────┬─────┘  └─────┬─────┘\n             │             │              │\n             └─────────────┼──────────────┘\n                           ▼\n                  ┌───────────────────┐\n                  │    RISK MODEL     │\n                  │                   │\n                  │ Probability       │\n                  │ Fraud class       │\n                  └─────────┬─────────┘\n                            │\n                            ▼\n                  ┌───────────────────┐\n                  │   POLICY ENGINE   │\n                  │                   │\n                  │ Permissions       │\n                  │ Approval routes   │\n                  │ SAR rules         │\n                  └─────────┬─────────┘\n                            │\n                            ▼\n                  ┌───────────────────┐\n                  │ NBA BEFORE        │\n                  │ EVIDENCE          │\n                  └─────────┬─────────┘\n                            │\n                      uncertain?\n                       /          \\\n                     no            yes\n                     │              │\n                     │              ▼\n                     │       ┌──────────────┐\n                     │       │   EVIDENCE   │\n                     │       │   SELECTION  │\n                     │       └──────┬───────┘\n                     │              │\n                     │              ▼\n                     │       ┌──────────────┐\n                     │       │ BELIEF UPDATE│\n                     │       └──────┬───────┘\n                     │              │\n                     │              ▼\n                     │       ┌──────────────┐\n                     │       │ NBA AFTER    │\n                     │       │ EVIDENCE     │\n                     │       └──────┬───────┘\n                     │              │\n                     └──────┬───────┘\n                            ▼\n                  ┌───────────────────┐\n                  │ ACTION / APPROVAL │\n                  │ SAR / EXPLANATION│\n                  └─────────┬─────────┘\n                            │\n                            ▼\n                  ┌───────────────────┐\n                  │   GRAPH MEMORY    │\n                  │                   │\n                  │ Case              │\n                  │ Findings          │\n                  │ Actions           │\n                  │ Evidence          │\n                  └───────────────────┘\n```\n\nThe final system is not an unconstrained chatbot making banking decisions.\n\nIt is a **traceable investigation workflow** in which:\n\n```\nGraph\n  → provides evidence\n\nRisk Model\n  → estimates risk\n\nPolicy\n  → controls permissions\n\nAgent\n  → chooses useful investigation work\n\nHuman\n  → provides required approvals\n\nAction Gateway\n  → executes approved actions\n\nGraph Memory\n  → preserves the investigation\n```\n\nThat separation is the central design principle behind GraphSentinel.\n\nThe goal is not simply to predict fraud.\n\nThe goal is to build an investigation system where **evidence, reasoning, policy, actions, approvals, and outcomes remain connected and auditable**.", "url": "https://wpnews.pro/news/graphsentinel-agentic-fraud-investigation", "canonical_source": "https://dev.to/abhishekyadav26/graphsentinel-agentic-fraud-investigation-47mj", "published_at": "2026-09-24 22:09:51+00:00", "updated_at": "2026-09-24 22:29:04.529251+00:00", "lang": "en", "topics": ["ai-agents", "artificial-intelligence", "ai-tools", "agent-protocols"], "entities": ["GraphSentinel", "TigerGraph", "LangGraph", "CrewAI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/graphsentinel-agentic-fraud-investigation", "markdown": "https://wpnews.pro/news/graphsentinel-agentic-fraud-investigation.md", "text": "https://wpnews.pro/news/graphsentinel-agentic-fraud-investigation.txt", "jsonld": "https://wpnews.pro/news/graphsentinel-agentic-fraud-investigation.jsonld"}}