[KongchangAI]
· 4 min read· 2,059 words

No Vector Store, No Cypher: How This Anti-Fraud Agent Solves Cases with Graph Memory

No Vector Store, No Cypher: How This Anti-Fraud Agent Solves Cases with Graph Memory

LedgerLens anti-fraud Agent uses a single FalkorDB graph to hold all memory and audit, enabling explainable investigations via fixed tools and evidence declarations.

LedgerLens is an anti-fraud AI Agent whose core innovation is abandoning vector databases and hand-written Cypher, storing all data, memory, SOPs, and audit trails in a FalkorDB graph database. The Agent can only use 26 predefined tools, and every answer must declare one of five evidence shapes (e.g., with_path, from_memory), ensuring explainability. A key bug fix taught the Agent to persist 'correct rejection' decisions, avoiding repeated investigation of innocent alerts. Measured results show rejecting an alert takes only 11 tool calls, dismantling a gang 37–62, with sub-millisecond memory retrieval. Graph algorithms (WCC, pageRank, etc.) are integrated as tools for gang detection and money-flow analysis.

No Vector Store, No Cypher: How This Anti-Fraud Agent Solves Cases with Graph Memory

While most AI Agents still build memory with vector databases + RAG, the LedgerLens anti-fraud Agent takes a completely different path: no vector store, no hand-written Cypher queries—all memory, data, SOPs, and audit trails are packed into a single FalkorDB graph database. Within three days of launch, it could already recognize "this isn't a new gang, it's a repeat of an old case"—all through a single full-text search, completing cross-session recall in milliseconds.

LedgerLens project screenshot

Why Anti-Fraud Needs Graph Memory, Not Vector Retrieval

Anti-fraud problems are rarely simple lookups. "Which accounts are related to this suspicious transaction?" is essentially a walk across shared devices, addresses, owners, and money flows. Text retrieval relies on fragments to guess relevance, whereas a graph advances along actually recorded relationships. When the Agent freezes an account, the path it walked is the explanation handed to the compliance department.

According to the author, the entire context layer is the graph itself: data, memory, standard operating procedures, and audit trail. A single FalkorDB instance hosts one graph per bank, with isolation achieved through graph keys—every handle is select_graph("bank_<slug>"), the slug is validated against a strict schema, and queries cannot cross tenants because there is simply no way to name another tenant.

FalkorDB and Graph Database Basics

FalkorDB is an open-source graph database built on a Redis-based sparse matrix engine, supporting the property graph model and the openCypher query language. Unlike traditional relational databases, graph databases model entities as nodes and relationships as edges, and allow nodes and edges to carry properties. This enables multi-hop relationship queries (e.g., "account A is linked to account B via device D, and B transfers to C") without complex JOIN operations—instead, you walk directly along edges, and query latency typically depends on traversal depth rather than total data volume. A key feature of FalkorDB is its built-in graph algorithm library (such as community detection, centrality, path finding) and support for multi-graph isolation—each graph has its own key space, providing a natural isolation boundary for multi-tenant scenarios. In AI Agent scenarios, graph databases can not only store structured relationships but also carry unstructured memory (such as session messages and findings), making them a unified context layer.

Three Rules That Keep the Agent Honest

Rule One: The Model Never Writes Cypher

If the Agent writes its own queries, its answers depend on code no one has reviewed. So it gets a fixed menu of 26 tools, each with human-written queries inside. CYPHER.md is generated from the live tool registry, and every query the product relies on is publicly available for review.

IDs are validated before execution: validate_entities checks parameters against the graph, and a bad ID returns unknown_entity rather than a confident guess. Reads cannot write: every read tool executes through GRAPH.RO_QUERY. Writes leave a trace: freeze_account and escalate_case create an Action node recording the operator, reason, and timestamp, linked to the case.

Cypher Query Language and GRAPH.RO_QUERY

Cypher is a declarative query language widely adopted in the graph database world, with syntax similar to SQL but designed specifically for graph pattern matching, e.g., MATCH (a:Account)-[:TRANSFER]->(b:Account) RETURN a, b. However, letting an LLM directly generate Cypher carries risks: the model may produce syntax errors, logic bugs, or unauthorized queries (such as cross-tenant reads). LedgerLens completely avoids this problem through a fixed tool menu—all queries are pre-written by humans and encapsulated as tool functions. GRAPH.RO_QUERY is a read-only query interface provided by FalkorDB that enforces read-only execution on the server side, unable to write or modify the graph structure. This provides a hard security boundary for read tools: even if the internal logic of a tool is flawed, it cannot accidentally tamper with data. Write operations are executed through dedicated tools (such as freeze_account) and are forced to record an Action node, forming a non-repudiable audit chain.

Rule Two: Every Answer Declares Its Evidence Type

Every session ends with a JSON verdict naming one of five answer shapes, and the tool layer rejects any other form:

  • with_path: the graph holds the relationship, and here are the hop counts
  • from_memory: a past session established this, and here is the timestamp
  • not_in_graph: the entity exists, but is not connected in the way the question assumes
  • unknown_entity: the ID is not in the graph, so no query was executed
  • action: a deliberate, audited write

not_in_graph blocks the cop-out of "I couldn't find a link, so it should be fine." In anti-fraud work, a missing relationship is itself a finding. unknown_entity blocks fabricated IDs: if the ID isn't in the graph, no query is executed. These shapes don't guarantee correct reasoning, but they make every claim state what evidence it stands on, so humans know what to check first.

Rule Three: The Account That Must Not Be Touched

Every seed bank includes a high-value, legitimate, fully isolated account—new device, new address, no gang links. This is exactly the kind of target an over-eager Agent might freeze just to look productive. The investigator should check it, find nothing, and return no_case. Live LLM tests assert that it remains untouched in every run.

The Bug That Taught the Agent to "Remember the Right Rejection"

While preparing the demo, the author discovered a hole in the memory design. Every case the Agent opened was persisted as graph memory: findings, links, timestamps. But when the investigator correctly declined to act on the negative control (a legitimate 12,400 transfer), that decision left no graph trace. The graph only remembered the cases we opened, never the cases we correctly rejected. The next session would re-investigate the same innocent alert from scratch.

Discipline is not memory. An Agent that has to re-prove an account's innocence every time will eventually slip up once.

The fix (commit e93909f): clearance results are now persisted as Finding nodes, linked to the session that produced them and the relevant accounts. That's 38 lines of code in agents.py plus a 27-line regression test. A fresh session can now recall the rejection decision with 0.9 confidence, complete with finding ID and timestamp—the very verdict shown above.

Algorithms Are Just More Tools

FalkorDB's graph algorithms sit on the same menu: algo.WCC for gang components, algo.pageRank and algo.betweenness for hubs and brokers within a component, algo.SPpaths for up to three shortest money paths (with per-segment amounts), and algo.maxFlow for funnel capacity between two accounts.

These algorithms require direct node-to-node edges, but the author models each transfer as a Transaction node because the audit trail matters. So the graph keeps two projections: TRANSFER aggregates transactions by ordered account pair (summing amounts, retaining counts), with one edge per ordered pair that also satisfies algo.maxFlow's simple adjacency requirement; LINKED_TO records that two accounts share a device, address, or owner.

Gangs come from LINKED_TO plus internal money density, because a household sharing a laptop is also a connected component. Scale and internal flow distinguish a gang from a household.

A v6 pitfall: the algorithm API is not uniform. Most procedures accept a configuration map, but algo.pageRank still accepts positional arguments. The author mapped signatures against the running server rather than trusting the docs, and the usable forms are recorded in CYPHER.md.

What Graph Algorithms Mean in Anti-Fraud

  • WCC (Weakly Connected Components): partitions nodes connected by any edge into the same component, used to discover gangs—as long as accounts are connected through transfers, shared devices, or any relationship, they belong to the same component. But households sharing devices also form connected components, so internal money density is needed to distinguish further.
  • PageRank: measures a node's importance in the global network, often used to identify core accounts in a gang (such as fund aggregation points).
  • Betweenness (betweenness centrality): measures how much a node acts as a "bridge," used to find broker accounts connecting different subgraphs.
  • SPpaths (shortest paths): finds the shortest path of money flow between two accounts, supports returning amount segments along the path, used to trace money flows.
  • maxFlow (maximum flow): calculates the maximum money throughput capacity of the network between two accounts, used to assess the money-laundering scale of funnel accounts.

These algorithms are all exposed in Cypher as procedures, e.g., CALL algo.pageRank(...). LedgerLens makes them part of the tool menu, invoked by the Agent on demand rather than run automatically.

Memory Is Just More Graph

All three kinds of memory are nodes and edges. Episodic memory: Session nodes with a persisted Message chain (SAID_IN, NEXT), with each case having its own session. Semantic memory: Finding nodes with confidence and timestamp, linked to the entities they describe, full-text indexed together with case titles and alert summaries—search_memory is the shared mechanism for the opening scene and clearance recall. Procedural memory: Playbook and PlaybookStep nodes hold the SOP retrieved before acting on each alert type. Closing a case can call learn_lesson, appending a step to the playbook with provenance pointing to the case that taught it. Every subsequent investigation follows it, and every change is on paper.

Recalling a previous session is just a query—there's no second store to keep in sync.

Full-Text Index and the search_memory Mechanism

LedgerLens's semantic memory relies on FalkorDB's full-text indexing feature. Finding nodes and their associated entities (case titles, alert summaries) are included in the full-text index, and the search_memory tool quickly locates relevant memories through full-text search. Unlike vector retrieval, full-text search is based on keyword matching (supporting stemming, stop-word filtering, etc.) and does not depend on embedding models, so latency is extremely low (median 0.37 ms measured). This design sacrifices semantic generalization but gains determinism and interpretability—every retrieval result can be traced to a specific keyword match. In anti-fraud scenarios, alert descriptions and case titles usually contain highly structured terms (such as account IDs, amounts, behavior types), so keyword search is sufficient for most recall needs. For scenarios requiring semantic understanding, the Agent can still supplement related information through graph traversal.

Handoff as an Edge

Two Agents run the same loop with different permissions. The intake analyst triages, reads anything, and opens cases, but cannot freeze. The case investigator can freeze, escalate, and close, but only after handoff. open_case triggers it by writing a :HANDS_OFF edge from the analyst's session to the new investigator session. Coordination becomes something you can query, rather than a function call that disappears.

Measured Data: Rejection Takes 11 Tool Calls, Bringing Down a Gang Takes 37–62

The author ran four investigations in parallel against the live stack:

AlertVerdictTool CallsWall-Clock Time
AUR-ALR-001no_case (rejected)11120.3s
NMB-ALR-001frozed_and_escalated37221.4s
NMB-ALR-003frozed_and_escalated54265.1s
AUR-ALR-003frozed_and_escalated62408.6s

Wall-clock time includes LLM inference. So a rejection takes about 11 tool calls, and bringing down a gang takes 37 to 62.

Query latency (median of 25 runs on live FalkorDB): search_memory full-text search is just 0.37 ms (p95 0.52 ms), 3-hop money path traversal 0.53 ms, 1-hop association link 0.58 ms, algo.pageRank 1.94 ms, algo.maxFlow 46.9 ms, algo.WCC full graph 397.5 ms (p95 498.2 ms).

Memory recall is a sub-millisecond query, so it can run at every step of the loop at near-zero cost. The model is the bottleneck, not the graph. WCC is the heaviest because it labels the entire association projection, so it runs once per investigation rather than at every step.

Testing and Next Steps

44 pytest tests pass, including 3 live LLM Agent tests and 9 authorization tests. Each bank has planted ground truth in tests/fixtures/planted_<tenant>.json, and tests assert that tools find exactly what was planted and no more. On concurrency, the suite runs 24 parallel reads plus concurrent case and finding writes, alongside 4-way parallel investigations. Zero cross-interference, with no application-level locks used. The MCP server exposes the same tool registry over stdio, and any MCP client can drive it.

Next steps: temporal edges, because recurrence requires time and not just patterns, and feeding algorithm output back into alert prioritization.

The code is open-sourced on GitHub: github.com/Mihir-Rabari/ledgerlens. Clone it, start FalkorDB, run python seed.py --wipe, and the console will show the alert flow from triage to verdict, with the hop counts behind every conclusion displayed on screen.

#anti-fraud AI Agent#FalkorDB graph database#graph memory#Cypher queries#vector database RAG#graph algorithms#money laundering detection
Share:

Related articles