Inspiration

Cheques feel like a relic, but they refuse to die especially in the Commercial & Business Banking space — and they remain one of the most fraud-prone instruments in banking. Forged signatures, altered amounts, and duplicate presentations still cost the industry billions every year. The challenge we faced was not the fraud itself, but the economics of catching it as every suspicious cheque demands a human analyst to pull the reference signature, eyeball the handwriting, scan transaction history, cross-check watchlists, and — often — phone the customer to ask, "Did you actually write this?"

That's several minutes of skilled labour per cheque. Multiply by the daily volume at any mid-sized bank and the queue itself becomes the attack surface. Fraud slips through not because it's undetectable, but because there aren't enough analysts to look in time.

The core question was: what if the investigation ran itself? Not a single classifier spitting out a risk score, but a team of AI agents that reason, verify, escalate, and even reach out to the customer — the way a real fraud desk works, only in seconds instead of minutes.

What it does

Sentinel is a multi-agent system orchestrated by Google's Agent Development Kit (ADK) with Gemini as the reasoning engine, and MongoDB Atlas as the shared memory, vector store, and audit trail that ties every agent together.

When a cheque is submitted, a Gemini-powered orchestrator decides which specialists to invoke and in what order:

  • 🔍 Extraction Agent — reads the cheque image (payee, amount, account number, sort code, cheque number, signature crop).

  • ⚖️ Risk Agent — the heart of the system. It embeds the presented signature into a vector and runs Atlas Vector Search against the customer's stored reference signature to score forgery likelihood, then layers on statistical checks for amount anomalies and duplicate presentations.

  • 🚨 Watchlist Agent — screens payee and account against known-fraud lists.

  • 📱 Contact Agent — when risk is ambiguous, it doesn't guess. It messages the account holder on Telegram: "Did you write this £4,800 cheque to Supplies Direct?" One tap confirms or reports fraud.

  • 🧠 Orchestrator — collects every signal, resolves the case, and writes the outcome.

Every decision streams into MongoDB as an event, powering a real-time cockpit where you watch the agents think, call tools, and reach verdicts live.

The risk model: The risk agent fuses signals into a single composite score. Sig cosine distance between embedding vectors:

$$ \text{sig_sim} = \frac{\mathbf{v}{\text{presented}} \cdot \mathbf{v}{\text{reference}}}{\lVert \mathbf{v}{\text{presented}} \rVert , \lVert \mathbf{v}{\text{reference}} \rVert} $$

retrieved efficiently via Atlas Vector Search rather than a brute-force scan. The amount anomaly is a normalized deviation from the customer's spending baseline:

$$ \text{anomaly} = \min\left(1,\ \frac{\lvert a - \mu \rvert}{k \cdot \sigma}\right) $$

where $a$ is the cheque amount and $\mu, \sigma$ are derived frn history. The final risk is a weighted blend, with duplicatesshort-circuiting straight to maximum risk:

$$ \text{risk} = \begin{cases} 1.0 & \text{if duplicate cheque detected} [4pt] 0.6 \cdot \text{sig_risk} + 0.4 \cdot \text{anomaly} & \text{otherwise} \end{cases} $$

Why MongoDB Atlas is the backbone

Atlas isn't a passive datastore in Sentinel — it's the substrate the agents reason over:

  • Vector Search powers signature verification directly against stored reference embeddings.
  • Customer profiles & transaction history feed the anomaly and duplicate checks.
  • Agent events stream in continuously, giving live observability and a complete, queryable audit trail — essential for any regulated financial workflow.
  • Case and session state live in Atlas, making the entire agentic pipeline durable and resumable — so when a customer replies on Telegram an hour later, the orchestrator picks the case up exactly where it left off.

One database powers similarity search, agent memory, and compliance-grade observability.

How we built it

Architecture at a glance

Sentinel is a team of independent agents — an orchestrator plus a team of specialists — each deployed as its own service on Google Cloud Run and coordinated through a shared MongoDB Atlas cluster that serves as their common memory.

A Gemini-powered orchestrator agent (built on Google's Agent Development Kit) reasons about each case and delegates to specialist agents by invoking them as tools. Each specialist owns exactly one responsibility and acts autonomously within it; Atlas is the shared substrate they all read from and write to.

The agents and what they own Agent - Kind - Responsibility Orchestrator - LLM ( Gemini + APK) - Reasons about the case, chooses which specialists to invoke and in what order, resolves the final verdict

Extraction - LLM (Gemini Vision) - Reads the cheque image into structured fields — payee, amount, account, sort code, cheque number, signature crop

Risk - Hybrid - Embeds + vector-searches the signature, runs Gemini visual comparison, plus deterministic anomaly & duplicate checks

Watchlist - Deterministic - Screens payee/account against known-fraud lists

Contact - Deterministic - Manages the Telegram human-in-the-loop conversation and case resumption

Why the split matters: we used an LLM only where open-ended judgment is required — interpreting a messy cheque image, sequencing an investigation. Everything that should be predictable, auditable, and fast — list matching, statistical thresholds, messaging — is deterministic code. In a regulated fraud workflow you don't want a language model deciding whether two cheque numbers are equal.

The build, agent by agent

  1. The orchestrator agent — Gemini + Google ADK

Rather than hard-coding a fraud flowchart, we gave a Gemini agent a toolbox and a goal. Using Google's Agent Development Kit (ADK), we wrapped each specialist as a FunctionTool — extract_cheque, get_customer, assess_risk, check_watchlist, contact_customer — and let the model decide the sequence. A system prompt frames it as a fraud investigator; the ADK Runner streams back every reasoning step and tool call.

_runner = Runner( agent=build_agent(), app_name="sentinel", session_service=InMemorySessionService(), )

This is the agent that runs an LLM reasoning loop. It's the brain; the rest are hands.

  1. The extraction agent — Gemini vision

The one specialist that also needs an LLM, because reading a handwritten cheque is genuinely open-ended. Gemini parses the image into a structured record and isolates the signature crop that the risk agent will verify. Its output is structured and validated, so downstream agents get clean, deterministic inputs.

  1. The risk agent — hybrid, and the heart of the system

This agent deliberately mixes ML and deterministic logic:

  • Vector / ML: the signature crop is embedded and matched against the customer's stored reference via Atlas Vector Search; Gemini provides a secondary visual comparison.
  • Deterministic: amount-anomaly scoring against the customer's spending baseline, and a hard duplicate-cheque check that short-circuits to maximum risk.

The composite score is a fixed, explainable formula — not a model's opinion:

composite = round((sig_risk * 0.6) + (anomaly_score * 0.4), 3)

Every blocking call (GCS download, embedding, Gemini compare) is pushed onto a thread executor so it never stalls the async event loop:

loop = asyncio.get_event_loop() presented_bytes = await loop.run_in_executor(None, _download_gcs, crop_url) presented_vec = await loop.run_in_executor(None, embed_image, presented_bytes)

Defensive Pydantic validators coerce messy LLM-supplied inputs (e.g. an amount arriving as "£1,200") into clean floats before any math runs.

  1. The watchlist & contact agents — fully deterministic

The watchlist agent is pure lookup logic against known-fraud records. The contact agent owns the human-in-the-loop conversation: it sends an inline Yes/No Telegram prompt, and its webhook handles the customer's tap — acknowledging instantly, sending a confirmation message, updating the session, and resuming the orchestrator to finalize the case. No LLM is involved, because none of this requires judgment — it requires reliability.

  1. Non-blocking by design

Agent investigations are slow — image downloads, embeddings, vector search, a Gemini vision call. Running that inside a synchronous request guaranteed timeouts. So the orchestrator's /process returns 202 Accepted immediately and hands the work to a FastAPI BackgroundTask:

@app.post("/process", status_code=202) async def process(req, background_tasks): await cheques().insert_one({"case_id": case_id, "status": "processing", ...}) background_tasks.add_task(_process_background, case_id, req.image_url) return {"case_id": case_id, "status": "accepted"}

  1. MongoDB Atlas as the shared substrate

Atlas is the connective tissue the whole fleet reasons over — four jobs at once:

Collection - Role customers - Profiles, transaction history, reference signature embeddings (Vector Search index) cheques - Per-case state machine: processing → complete → closed agent_events - Every tool call & reasoning step — powers the live cockpit + audit trail sessions - In-flight Telegram confirmations, so cases survive and resume

  1. Real-time observability

The hardest-won feature. We propagate the case_id into deeply nested tool wrappers using Python contextvars — no signature rewrites — so every event auto-tags its case:

_current_case_id: ContextVar[str] = ContextVar("current_case_id", default="unknown")

async def _emit(agent, event_type, message): await events().insert_one({ "case_id": _current_case_id.get(), "agent": agent, "event_type": event_type, "message": message, "timestamp": datetime.utcnow(), })

The ADK event stream is also surfaced — event.get_function_calls() becomes tool_call events and text parts become reasoning events — so the cockpit shows the orchestrator thinking, not just its final answer.

  1. The cockpit — Streamlit

A lightweight Streamlit cockpit handles upload → GCS, submission, and a stateless st.rerun() auto-refresh feed over the agent_events stream. We learned the hard way that Streamlit's script thread has no event loop, so the cockpit uses the synchronous PyMongo driver rather than the async one.

  1. Deployment

A deploy.sh script builds each agent's container and ships it to Cloud Run, wiring secrets (MongoDB URI, Telegram token) through GCP Secret Manager via --set-secrets — so no credential ever touches the repo. Gemini model IDs are pinned to verified versions for reproducible deploys.

Challenges we ran into

  • Async all the way down. Our risk agent chained GCS downloads, image embedding, Vector Search, and a Gemini visual comparison — all synchronous, all blocking the event loop, all timing out under a 30-second limit. We had to push every blocking call into thread executors and redesign the orchestrator to return 202 Accepted immediately and process in the background. Long-running agent work simply cannot be a blocking request.
  • State across threads. Propagating a case_id into deeply nested tool wrappers — so every emitted event tagged the right case — without rewriting every function signature led us to Python's contextvars.
  • The Streamlit event-loop trap. Our cockpit used an async Mongo driver inside Streamlit's script thread, which has no event loop — a runtime error until we switched to the synchronous driver. Real-time UIs and async drivers don't mix without care.
  • Closing the loop on Telegram. Getting the customer's tap to dismiss the spinner, send a confirmation message, update the session, and resume the orchestrator — reliably and idempotently — took several iterations to make robust.
  • Tightening the demo under a deadline. Aligning project IDs, regions, pinned Gemini model versions, and secrets across multiple Cloud Run services so the whole fleet deployed cleanly was its own small battle.

Accomplishments that we're proud of

  • We built a true agent fleet, not a monolith. Sentinel isn't one model with a prompt — it's an orchestrator agent that reasons with Gemini and delegates to four independent specialist agents, each its own Cloud Run service. Getting a Gemini orchestrator to plan an investigation and call the right specialist at the right moment — entirely tool-driven, with no hard-coded flowchart — was the moment the system felt genuinely agentic.

  • We made the AI explainable. Every tool call and every reasoning step the orchestrator takes streams live into MongoDB Atlas and onto a real-time cockpit. You don't just see a verdict — you watch the agents think. For a fraud system, "the model said so" is unacceptable, and we're proud that Sentinel can always show its work.

  • We closed the loop with a real human. When risk is ambiguous, Sentinel doesn't guess — it messages the actual account holder on Telegram and waits. One tap confirms or reports fraud, the orchestrator resumes the exact case, and the verdict becomes customer-authorized. Watching a cheque get blocked because a real person tapped "No" — end to end, autonomously — was our proudest demo moment.

  • We made MongoDB Atlas the brain, not just the database. Atlas powers signature verification through Vector Search, holds the customer history the risk model reasons over, streams every agent event for observability, and stores case state that lets a paused investigation resume hours later when the customer finally replies. One cluster doing search, memory, and audit at once.

  • We drew a clean line between judgment and reliability. We used LLMs only where open-ended judgment is required — reading a messy cheque, sequencing an investigation — and kept everything that must be predictable and auditable (duplicate detection, anomaly thresholds, watchlist matching) as deterministic code. That discipline is what makes Sentinel trustworthy enough for a regulated workflow.

  • We shipped it, fully deployed on Google Cloud. Not a notebook demo — a live fleet of containerized agents on Cloud Run, wired through Secret Manager, with pinned Gemini models and a reproducible deploy script. Under a hackathon deadline, we turned a hard architectural idea into a running, end-to-end system that takes a cheque image in and produces an explainable, human-verified fraud decision in seconds.

What we learned

  • Its been an enriching experience. A complete E2E agentic system with multiple agents orchestrated is a key learning with a proof that Agentic systems can handle more traditional challenges with real customer impact
  • Agentic systems live or die on observability. Building solution such that every tool call and every Gemini reasoning step streams into Atlas was the single biggest leap in trustworthiness. For a fraud system, "the AI said so" is not an acceptable answer.
  • Vector search belongs next to your operational data. Keeping embeddings, customer records, and case state in one Atlas cluster removed an entire class of synchronization bugs and latency we'd have inherited from a separate vector DB.
  • Human-in-the-loop is a feature, not a fallback. The Telegram confirmation step turned out to be the most compelling part of the demo — it mirrors how real fraud desks operate and converts an uncertain model output into a definitive, customer-authorized decision.
  • Let the orchestrator orchestrate. We learned to give Gemini tools and goals rather than a hard-coded flowchart, and to trust it to sequence the investigation.

What's next for Sentinel - Agentic Cheque Fraud Identification System

  • Expand to automated voice based communication with the end user with the target to remove
  • Expand Vector Search to cross-customer forgery clustering — detecting the same forged hand across multiple accounts.
  • Auto-generate Suspicious Activity Reports (SARs) from the case audit trail.
  • Extend the agent fleet to other instruments — ACH, wire transfers, and mobile deposits — reusing the same Atlas-backed orchestration core

Built With

Share this project:

Updates