Skip to content

Repository files navigation

HyperGuard

The intervention layer for authorised payment fraud

A scammer doesn't break into the bank. They talk your mother into wiring the money herself. HyperGuard is the AI swarm that picks up the phone in the sixty seconds before she sends it.

LangGraph orchestrates it Twilio calls ElevenLabs speaks Supabase remembers Live API

Detection tells you after. HyperGuard intervenes during, and helps recover after.


The idea in 30 seconds

Every fraud system a bank owns is built to answer one question: is this transaction legitimate? For modern scams, the answer is always yes. The password was right. The device was recognised. The victim tapped Send with her own thumb, because a man claiming to be the police spent forty minutes convincing her she had to.

HyperGuard asks a different question: is this person being manipulated right now?

The moment a transfer looks nothing like the customer's normal behaviour, five agents move at once. One calls her. One listens to her answers and recognises which scam script she's being read from. One wakes up her son. And the money stays in the account until a decision with a written rationale says otherwise.

Our mission

Put a defender in the room at the exact moment someone is about to be robbed.


Why this matters right now

The attack surface stopped being the system and became the person, and the industry's tooling hasn't caught up.

  • Roughly 94% of fraud losses are payments the victim authorised themselves. Nothing was stolen — it was given. Every control that checks credentials, devices, or account takeover signals waves these straight through.
  • The whole scam lives inside a sixty-second window — between "I'll send it" and "sent." No human fraud team can be inside that window for every customer, on every transfer, at 2am.
  • The existing shields report; they don't intervene. Tools like Singapore's ScamShield are excellent at detection and reporting, which happen after the money has moved. Nobody is doing real-time voice intervention, live scam classification, family escalation, and recovery orchestration as one loop.

Blocking the transfer outright isn't the answer either — it fails the customer and never explains why. The victim needs to understand they're being scammed while it's still reversible. That takes a conversation, and a conversation at that scale takes agents.


Watch it work

Seeded scenarios run through the full swarm on a fresh clone with zero API keys configured — every external dependency degrades to a deterministic in-process simulation, so the orchestration is provable before anything is wired up. Two of the four, verbatim:

$ python seed.py

HyperGuard smoke test, capabilities: {'llm': False, 'telephony': False, 'speech': False,
                                      'persistence': False, 'distributed_bus': False,
                                      'demo_mode': True}

──────────────────────────────────────────────────────────────────────────────
  scenario   police_impersonation
  customer   May Tan  ·  SGD 8,000 → Quik Holdings Pte Ltd
  risk       99%  (critical)
  scam       Government / Police Impersonation
  decision   BLOCK
  guardians  1 alerted
  evidence   built
  narrative  Blocked. The SGD 8,000 transfer to Quik Holdings Pte Ltd was halted
             after the call surfaced Government / Police Impersonation; Marcus Tan
             was alerted. The money never left the account, and a recovery evidence
             package was prepared for reporting the beneficiary.
──────────────────────────────────────────────────────────────────────────────
  scenario   legitimate_transfer
  customer   May Tan  ·  SGD 280 → NTUC FairPrice
  risk       7%  (minimal)
  scam       —
  decision   APPROVE
  guardians  0 alerted
  evidence   n/a
  narrative  Approved. The SGD 280 transfer to NTUC FairPrice matched May's
             established behaviour with no scam indicators, released without
             interruption.
──────────────────────────────────────────────────────────────────────────────

The grocery run clears in milliseconds and nobody is ever called. That restraint is the product: an intervention layer that fires on everything is a layer banks switch off.


How it works

Five agents on a LangGraph state machine. Nodes are agents; edges are conditional transitions on risk_score, verification_status and scam_detected. A single shared state object carries the transaction, the customer's behavioural profile, the risk assessment, the live transcript, the scam classification, the verdict and the evidence through the graph.

A transfer is authorised and scored by the Digital Twin. Under 0.58 it is approved instantly. At 0.58 and above the Voice Negotiator calls her, the Educator names the scam script and feeds a counter-line back into the call, the Guardian wakes a trusted contact, and the Arbiter either releases or blocks the transfer. A blocked case goes to the Recovery Coordinator for the evidence pack.

Digital Twin — knows what normal looks like

Every customer carries a behavioural baseline: typical amounts, known payees, active hours, transfer velocity. Each incoming transfer is scored against her history, not a population average, and the signals combine through a logistic link into a 0–1 risk score.

The weights are legible constants, not a black box. A first-ever payee contributes the most evidence (it is the single strongest predictor of authorised-push fraud); an amount several σ above her normal, off-hours activity, a velocity burst, an overseas number, and coercion vocabulary in the transfer note all stack on top. Below 0.58 the transfer clears silently. At 0.88 it is held pending human confirmation regardless of what the call finds.

This is deliberately not a deep model. At the moment of intervention the explanation matters as much as the number — the customer on the phone needs to hear why, and an investigator needs to read it six months later.

Voice Negotiator — makes the call

The instant risk crosses the threshold, an outbound call is placed via Twilio with an ElevenLabs voice. It isn't an IVR reading a warning; it conducts contextual verification — who asked you to send this, what did they say would happen if you didn't — and streams the transcript back into the graph turn by turn. When telephony credentials are live the conversation runs out-of-band over voice webhooks; when they aren't, the same dialogue plays out deterministically in-process.

Educator — names the script she's being read from

Scams are not improvised. They run from a small number of scripts, and each one has a tell. The Educator matches the live transcript against six archetypes — government/police impersonation, bank impersonation, investment/crypto, romance, job/task, and tech support — and feeds the counter-line straight back into the call for the Negotiator to say aloud. For police impersonation, that's the sentence no real agency ever crosses: we will never ask you to move money to a safe account.

Naming the script is what breaks it. A generic "this may be a scam" warning is easy for a victim mid-manipulation to dismiss. Being told exactly what the person on the other line is about to say next is not.

How it actually works. This is an indicator-density classifier, not an LLM — each archetype carries a list of linguistic fingerprints, and confidence scales with how many distinct ones light up. Same reasoning as the Digital Twin: the counter-lines are read aloud to a frightened person and quoted back in an audit trail, so they are authored and deterministic rather than generated. The LLM's judgement enters after the call, in the adjudicator (services/reasoning.py), where it weighs the customer's actual answers and can disagree with the pattern match. Swapping in LLM classification with this matcher as a strictness floor is on the roadmap — see FUNCTIONALITY.md.

Guardian — brings in someone she trusts

Some victims cannot be talked down by a stranger, especially one calling from the bank when a "policeman" has just told them the bank is compromised. The Guardian escalates to pre-authorised trusted contacts with the transaction context and the risk rationale, adding a human verification layer exactly where it's needed most.

Recovery Coordinator — for when it's already too late

When fraud has already been processed, the swarm runs a different path entirely and assembles an evidence package: transaction trail, the risk signals that fired, the full conversation log, and the beneficiary details, formatted for the bank's recovery team and law enforcement. Speed is everything in fund recovery, and this turns a multi-day paperwork exercise into one click.

And the Arbiter decides. It weighs the risk score, the verification status and the scam classification into a single verdict with a written rationale attached. Every decision the swarm makes is explainable and audited — which is the only way a layer like this ever passes a bank's compliance review.


Three surfaces, one swarm

Control centre frontend/ The bank's mission control. Live risk meters, streaming transcripts, the agent relay firing in real time, every case filed with its full decision trail. Next.js, with a bespoke "interdiction console" design system, a React-Three-Fiber hero and GSAP scroll choreography.
Wallet mobile/ The customer's side. A working Expo banking app — balances, payees, transfers, next-of-kin — so the swarm has real transactions to act on. Send money to the hidden scam payee and watch the intervention land on your own phone.
Swarm backend/ FastAPI + LangGraph. REST, WebSocket event stream, wallet and admin APIs, and the five agents. Live on Railway →

Try it yourself

git clone https://github.com/basil-boh/HyperGuard-.git && cd HyperGuard-
cp infra/.env.example .env        # optional — it runs without any of it

Backend — the swarm, and a one-command proof it's wired correctly:

cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

python seed.py                    # run every scenario through the full swarm
pytest                            # risk engine · swarm · wallet
uvicorn app.main:app --reload --port 8000

Control centrecd frontend && npm install && npm run devlocalhost:3000

Wallet — bind the backend to your LAN first (uvicorn app.main:app --host 0.0.0.0 --port 8000), then cd mobile && npm install && npx expo start and open it in Expo Go. The app auto-discovers the backend from the Expo dev host, so there is nothing to configure on the phone. See mobile/README.md for the demo script.

Signing in, and the customers you can sign in as

The wallet opens on a sign-in screen: phone number + 6-digit PIN. The database ships with seven customers, each with a different financial signature — because the risk engine scores against learned behaviour, the only honest way to test it is with accounts that genuinely differ. Tap Demo accounts on the sign-in screen to pick one without typing.

Phone PIN Customer Signature Good for testing
+6580001234 112233 Alex Tan, 67 Retiree, small regular outgoings The default demo — two planted scam payees
+6580000001 445566 May Tan, 72 Retiree, two scams already on file Repeat-target escalation, populated case history
+6580000002 778899 Daniel Lim, 34 Salaried professional, high volume Four-figure transfers that are genuinely fine
+6580000003 102030 Wong Ah Kow, 81 Thin file, tiny amounts Any four-figure transfer reads as critical
+6580000004 135791 Priya Nair, 58 Steady bills and school fees Tech-support interception with a follow-up call
+6580000005 246810 Siti Rahman, 29 Gig worker, many small payouts High velocity, low amounts
+6580000006 909090 Robert Chen, 46 SME owner, five-figure supplier runs Big ≠ suspicious
+6580000010 321321 Marcus Tan, 41 Guardian — son to Alex and May The guardian inbox, with incidents already on file
+6580000013 654654 Linda Wong, 52 Guardian — daughter to Wong A second guardian, one unread incident

The contrast is the point: the same SGD 8,000 transfer is a 99% critical block for Wong and unremarkable for Robert. Each account carries its own balance, saved payees, guardians and 19–31 transactions of history, plus at least one hidden scam payee to transfer to.

Credentials are served by GET /api/auth/demo-accounts and rendered in the app. Set EXPOSE_DEMO_CREDENTIALS=false to hide them, and ALLOW_HEADER_USER_OVERRIDE=false to require a real token (the legacy X-User-Id header is honoured by default so curl demos keep working).

PINs are stored as PBKDF2-HMAC-SHA256 hashes, never in plain text; sessions are stateless HMAC-signed bearer tokens. Repeated failures lock a phone number out for 60 seconds.

The guardian network

The hardest part of protecting someone from a scam is that the person best placed to help — their son, their daughter — finds out afterwards, if at all. The Network tab makes that relationship a first-class object rather than a phone number in a settings screen.

A guardian adds a relative by phone number, and the relative decides: the invitation sits pending until they accept, and nothing about their account is visible until they do. Accepting does three things at once — the guardian appears on the account's alert list so the swarm can reach them mid-intervention, every blocked case already on file is delivered to their inbox, and future ones arrive automatically. Either side can revoke at any time; the link is kept, marked revoked, so past access stays auditable.

The relationship also builds from the other direction: adding a trusted contact whose phone already belongs to a HyperGuard account links the two immediately, because the person whose consent matters is the one doing the adding.

flowchart LR
    G["Guardian<br/>invites by phone"] -->|pending| P["Relative<br/>accepts or declines"]
    P -->|accepted| L["Link active"]
    L --> A["On the swarm's<br/>alert list"]
    L --> H["History<br/>backfilled"]
    B["Transfer blocked"] --> R["Incident report<br/>→ guardian's inbox"]
    R --> F["Alert authorities<br/>(SIMULATED)"]

    classDef base fill:#07080c,stroke:#2a2f26,stroke-width:1px,color:#d7dfc8
    classDef good fill:#0c1408,stroke:#c9f24a,stroke-width:1.5px,color:#c9f24a
    classDef warn fill:#171207,stroke:#ffc24b,stroke-width:1.5px,color:#ffc24b
    class G,P,B base
    class L,A,H,R good
    class F warn
Loading

An incident report is the whole account of what happened — the risk signals that fired, the call transcript, the scam pattern, the guardian actions, the verdict — read live from the case, so it can never drift from the record.

On the authorities filing. The "Alert authorities" action is a simulation, and the code goes out of its way to keep it impossible to mistake for anything else: every reference is prefixed SIM-, every payload carries simulated: true plus a disclaimer naming the real channel (ScamShield, 1799), the status timeline stops at referred and never invents an outcome like funds recovered or an arrest, and services/filing.py has no network access of any kind. HyperGuard is not connected to the police, the National Anti-Scam Centre, or any other body. If you later wire up a real integration, that module is the seam — and the labelling should be the last thing removed, not the first.

Guardian-set transfer limits

A guardian can cap what the person they protect can send in one transfer. It's checked before the swarm runs, because it isn't a risk judgement — it's a standing instruction from someone this customer trusted while they were clear-headed, and the whole point is that it holds when they aren't.

Only the guardian can change it. That asymmetry is the feature: a limit the victim can raise mid-call is a limit a scammer can talk them through raising. The protected person always sees the limit and who set it, on their own balance card and next to that guardian's name — and if they disagree, they can revoke the guardian entirely. That's the escape hatch, and it's deliberately not something you can do in thirty seconds while someone is shouting at you on the phone.

Where several guardians each set one, the lowest wins, so adding a guardian can only ever tighten protection. Revoking a link drops its limit automatically. Nothing is seeded with a limit, so no existing demo path changes until you set one.

Model tiering — paying for the hard cases only

Most transfers never reach a model at all: the Digital Twin's risk score is a deterministic logistic function, so the common path is free, not merely cheap. Of the cases that do reach one, most are ordinary verification calls a small model handles well. The expensive model is reserved for cases that have actually escalated.

Call Tier Why
Negotiator's opening line fast The customer is on the phone. Time-to-first-word is the product.
Negotiator's follow-up turns fast → deep on escalation Exploratory questions are cheap work; once a pattern is confirmed the stakes change.
Educator's guidance line no model The safety wording is authored, not generated.
Post-call assessment deep It decides whether a family member gets woken up. Nobody is waiting.
Incident report deep It's the artefact a human reads.

The promotion rule is one readable predicate in services/model_policy.py: a confirmed scam pattern, or risk at/above the hard-block threshold. Everything else stays on the fast model.

LLM_MODEL_FAST=gpt-5.5-mini     # routine work
LLM_MODEL_DEEP=gpt-5.5          # escalated cases + written outputs (falls back to LLM_MODEL)

Every call is recorded — tier, model, latency, tokens — and the per-case ledger rides along on the intervention poll as model_usage, so the saving is measurable rather than asserted. Set the optional LLM_PRICE_* variables and it also reports dollars, including what the same case would have cost on the deep model throughout. Left unset it reports tokens and latency only: a guessed price is worse than no price.

Seeded so the network is populated on first load: Marcus watches Alex and May with two of May's incidents in his inbox (one unread, one already filed), Linda watches her father, and a pending invitation from Marcus to Wong Ah Kow is waiting so the accept flow can be demonstrated on a single device.

Keys, and what happens without them

Every external dependency is capability-gated. Absent a credential, that subsystem degrades to a deterministic simulation and the swarm still runs end to end — you can see the whole thing work before signing up for anything.

Key What it buys you Needed?
OPENAI_API_KEY LLM dialogue and the risk explainer Recommended. Without it, scripted dialogue and template explanations.
TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_PHONE_NUMBER places a real outbound call Optional. Without it, the conversation runs in-process.
ELEVENLABS_API_KEY the negotiator's voice Optional. Falls back to Twilio's Polly TTS.
SUPABASE_URL / SUPABASE_SERVICE_KEY cases and transcripts survive a restart Optional. Without it, in-memory.
REDIS_URL mirrors every swarm event to a Redis stream, for audit and replay Optional. The bus itself is in-process either way — nothing consumes the stream yet, so this does not make the app multi-worker.
AUTH_SECRET signs customer session tokens Local: no. Deployed: yes, or every restart signs everyone out.
LLM_MODEL_FAST / LLM_MODEL_DEEP cheap model for routine work, powerful one for escalated cases Optional. Both fall back to LLM_MODEL.
LLM_PRICE_*_INPUT / _OUTPUT turns recorded token counts into a cost estimate Optional. Unset, the API reports tokens and latency but never dollars.
Project layout and the developer contract
backend/                       # FastAPI + LangGraph
  app/
    graph.py                   # the swarm orchestrator — nodes, routers, shared state
    config.py                  # capability gating: every integration optional
    agents/                    # digital_twin · negotiator · educator · guardian · recovery · arbiter
    services/                  # risk_engine · scam_taxonomy · dialogue · baselines · llm · auth
    integrations/              # voice (Twilio/ElevenLabs) · notifications · event bus · persistence
    api/                       # routes · ws · auth · wallet (customer) · admin (console) · twilio_voice
    wallet/                    # multi-account bank: accounts, cases, payee registry
    data/seed_data.py          # demo personas & scenarios
  db/schema.sql                # Postgres schema
  tests/                       # risk engine · end-to-end swarm · wallet · auth
  seed.py                      # one-command smoke test

frontend/                      # Next.js — the bank's CONTROL CENTRE
  app/console/                 # overview · users/[id] · cases/[id] · live
  components/                  # landing (Hero3D · ScrollStory) · control · console (RiskMeter · TranscriptStream)

mobile/                        # Expo — the CUSTOMER's banking app
  app/                         # Wallet · Activity · Guardians · transfer · intervention
  components/                  # AgentRelay · RiskGauge · Transcript

infra/                         # .env.example · Supabase schema

The entire swarm is one call. Everything else — the REST API, the WebSocket stream, the wallet — is a caller.

from app.graph import get_orchestrator

outcome = await get_orchestrator().run(customer, txn)

outcome.decision          # Decision.approve | Decision.block
outcome.risk              # score, band, and every signal that fired, with reasons
outcome.classification    # which scam archetype, and how confident
outcome.transcript        # the conversation, turn by turn
outcome.guardian_alerts   # who was contacted, and what they were told
outcome.evidence          # the recovery package, when one was needed
outcome.narrative         # the whole thing in plain English

Where we're taking HyperGuard

Today HyperGuard is a complete, demonstrable swarm running against a simulated bank. The path to production is about depth, not new ideas.

  • Live rails. The telephony, voice and LLM paths are capability-gated and already wired — flipping the keys on places a real call. Next is hardening that path against real conversation: interruptions, dialect, hostility, and the scammer still being on the other line.
  • Durable recovery. Recovery is the one flow that genuinely outlives a process — evidence gathering, bank submission, police reporting, status chasing. That belongs in Temporal, not in memory.
  • A twin that learns. Today's baselines are computed from transaction history with legible weights. The next version updates continuously and learns what an intervention taught us about that customer — including the ones we called and shouldn't have.
  • The layer, not the app. The end state isn't a banking app; it's a drop-in layer between intent and settlement that any bank or wallet can put in front of a transfer, with no rip-and-replace of their stack.

plan.md has the full build plan — data model, phase timeline, and the orchestration decisions with their trade-offs written down. PRESENTATION.md is the live demo script; PITCH.md is the forty-second version.


Disclaimer

HyperGuard is a prototype built for demonstration. It is not a production financial-security system and is not connected to real banking or payment rails. Do not use it to make real fraud-prevention decisions without appropriate review, testing, and compliance sign-off. The seeded personas, transactions and scam payees are fictional.


HyperGuard

Detection tells you after. HyperGuard intervenes during.

Built with LangGraph  ·  Twilio  ·  ElevenLabs  ·  Supabase  ·  FastAPI  ·  Next.js

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages