Inspiration
Scammers don't go after the people who'd spot them. They go after a parent who lives alone: "Grandma, it's me, I'm in trouble, don't tell Mom", a parcel "redelivery fee", a "fraud alert" from the bank with a number to call back. The family usually finds out after the money is gone. Our test family is fictional, but it's the case we built for: Margaret, 74, lives on her own, and her son David, 41, worries about her from another city. Margaret doesn't need an explanation of how phishing works. She needs one clear answer, in big type, that she can trust. David needs to know when something dangerous arrives, and he needs a calm way to talk to her about it.
What it does
For the parent. Margaret pastes a message into one page (on Android, she can share it straight from her messaging app to the installed ScamShield app). The page shows its progress as it goes (reading the message, checking public scam reports, deciding), and a well-known scam script gets an early warning at once. A live check takes about 6â10 seconds and ends in a full-screen verdict:
- đ´ Scam: do not reply, pay or call. If the alert to the family really went out, the screen says "David has been told." If it didn't (not sent, rate-limited or a duplicate), it says "Tell someone in your family." instead.
- đĄ Careful: this might be a scam; don't click, pay or share details; ask family first.
- đ˘ Looks safe: and why, for example because it came from Tom, her grandson, who is in her trusted contacts.
Each verdict lists the red flags in plain sentences, a link to a public report when one exists, and one calm next step. A Read this to me button reads it aloud. Three built-in examples let anyone try it without typing.
For the family. A đ´ result alerts David by email, and on Telegram if he has linked it. The alert has the red flags, the evidence links, a few short notes for phoning his mother (what the message is, the red flag that gives it away, what to tell her), and an I called her button. His dashboard shows:
- recent checks, with the alerts that went out and whether someone called her;
- How ScamShield decided: every step with its Nemotron model, tokens, latency and cost;
- her memory: trusted contacts and known services (bank, pharmacy, clinic, shops), which he can edit;
- alert settings, including Telegram linking by QR code.
Memory that changes the answer. "Grandma, I need bail money" from an unknown number is floored at Scam. The real grandson's number comes back green without a search or an Ultra call. A sender or link that imitates a known service's domain (lakeviewbank-online.test vs. her real bank) is flagged as a look-alike, whatever the model says.
Private by design. Card numbers, SSN-style IDs and passwords are redacted before any model call. Tavily only ever sees a phone number, a domain or a short redacted phrase. Suspicious links are never opened. Safe messages' text is not stored unless the family opts in. The parent has a one-tap "Delete my data" page and a download-a-copy export, and the family can delete everything or a single check.
How we built it
Nemotron, tiered by difficulty, on Nebius Token Factory. Every check runs one pipeline:
normalize â verdict cache â redact PII â memory check â quick check â triage (Nano) + Tavily searches in parallel â verdict (Ultra) â persist + alert
| Step | Model (Token Factory id) | Why this size |
|---|---|---|
| Triage | Nemotron 3 Nano nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B |
fast, cheap signal extraction on every message: urgency, money ask, impersonation, secrecy, links, and the phrase worth searching for |
| Verdict | Nemotron 3 Ultra nvidia/Nemotron-3-Ultra-550b-a55b |
the judgment call: score, red flags tied to evidence, safe action, notes for the family |
The small model sees every message; the largest only sees the ones that need judging. A trusted sender skips search and Ultra entirely. We started with a three-tier design (Nemotron 3 Super planned the searches) but measured that a fixed lookup plan picked the same searches without the extra call and ~2 s, so Super was removed from the check path. Turning Nemotron's reasoning off for both steps (enable_thinking: false) cut triage from 5â9 s to about 1.5â2.5 s and the Ultra verdict from 6â10 s to about 2.5â3 s on our samples, with the same labels and about 60% less model spend per scam check.
Every model step asks Token Factory for response_format: json_schema. If a model rejects that, it falls back to json_object, then gets one repair call. Every step's model id, tokens, latency and cost (from Token Factory's listed prices) is stored with the result and shown in the family's "How ScamShield decided" panel.
Tavily as the evidence desk. Three reusable skills (check_phone, check_url, check_phrase) run real-time Tavily searches, cached for 24 h, with at most 3 searches and one page extract per message. Lookups that need no model (the message's link and phone number) start at once, alongside triage. A result only counts as evidence if it actually mentions the number or domain. The verdict can only cite a URL Tavily returned for this message; any other link is stripped. When nothing turns up, the verdict says "No public reports found" and invents nothing.
Guardrails the model can't talk its way out of. Deterministic floors are applied after the verdict:
- an unknown sender claiming to be family and asking for money or secrecy: at least 75 (Scam);
- any unknown sender asking for money: at least 50;
- a look-alike of a known service: at least 75.
A budget guard runs before every paid call:
- degraded (Nano-only) after daily Ultra caps or 80% of total spend;
- exhausted: no paid calls, a rule-based "ask your family" verdict;
- a kill switch.
Identical messages are answered from a verdict cache, and checks are rate-limited per IP.
Stack. Python 3.12 + FastAPI + SQLite for the API and memory. Next.js 16 static export for the parent PWA and the family dashboard, served by the same app. Resend for email and the Telegram Bot API for alerts. The live demo runs in Docker behind Caddy on a DigitalOcean droplet; all inference goes to Nebius Token Factory. The skills are also exposed as MCP-shaped tools (tools/list / call). The repo has 219 Python tests and 8 web tests, and a 26-message seed set (ten scam patterns, three Japanese variants and safe look-alikes) with an eval script.
Challenges we ran into
- Making evidence honest. Generic searches return lots of unrelated pages, and models like to cite them. We filter hits for relevance, and the verdict may only cite URLs Tavily returned for that message. With no evidence, it has to say so.
- Speed. Our first live run summed to a median of about 24 s per check. Dropping the planning call, turning reasoning off, running triage and searches in parallel and adding an instant preview brought a live check to about 6â10 s.
- Structured output. The catalog doesn't say which models support
json_schema, so we built a fallback chain and a smoke test that probes each tier. Strict JSON fixed the shape, not the judgement, so results are validated and guardrailed in code. - Model ids. Catalog casing is inconsistent (
NVIDIA-Nemotron-3-Nano-30B-A3Bvs.nemotron-3-super-120b-a12b), so ids are matched case-insensitively everywhere, including for the price lookup. - Designing for a 74-year-old. The parent screen uses Atkinson Hyperlegible type at a 22 px base and 76 px tap targets. Colour is never the only signal (icon + word). One button. Read-aloud.
- Alert honesty and safety. The red screen only says the family has been told when an alert really went out. An email "I called her" link opens a confirm button instead of acknowledging on load, so a mail scanner that prefetches links can't mark a call as made. Alerts are de-duplicated per message and capped per contact per hour.
Accomplishments that we're proud of
- A complete two-sided product, not a demo script: the parent's one-button page, the family's alert, and a dashboard that shows why.
- Nano â Ultra routing where the cheapest model sees every message and Ultra only sees the ones that need judgment, with a per-step cost trace a family member can read. We measured our way to it, and cut a model tier when it didn't earn its place.
- Memory beats the model: the real grandson is green, and the impostor is red with a known guardrail.
- Every evidence link in a verdict is a page Tavily actually returned for that message.
- On our own 26-message seed set, a live run on Token Factory got every label right. It's a small set we wrote ourselves, so we read it as "the pipeline works end to end", not as a general accuracy figure.
What we learned
- Most of the value is in what the model is not allowed to do: invent sources, override a trusted contact, or skip a floor.
- Tiering pays off, but measure it: the smallest model was the slowest step until we switched its reasoning off, and a middle tier turned out to be unnecessary.
- The parent needs one sentence; the family needs the reasoning. Splitting them into two screens made both clearer.
What's next
- Forward-to-email intake, so a parent can forward a suspicious email instead of pasting it.
- A weekly family digest of đĄ messages (the long-context Nemotron 3.5 Lightning tier is already configured for it).
- Running the skills as tools for an always-on agent in a sandboxed runtime, with network egress locked to Token Factory and Tavily.
- Voicemail transcription and a safety-classifier pass, once NVIDIA speech and guard models are on Token Factory.
- Verdicts in Japanese for Japanese messages (the seed set already includes ăŞăŹăŞăŹčŠćŹş and delivery-SMS variants).
Built With
- caddy
- digitalocean
- docker
- fastapi
- nebius-token-factory
- next.js
- nvidia-nemotron
- pwa
- python
- react
- resend
- sqlite
- tavily
- telegram-bot-api
- typescript
Log in or sign up for Devpost to join the conversation.