Inspiration

Every week in Sri Lanka, someone's grandmother or grandfather gets a call like this:

"This is CID. Your ID card was used in a drug case. Move your money to a safe government account now, or police will arrest you today. Don't tell anyone."

Terrified, they open their own bank app on their own phone, at home, and type the OTP themselves. To a bank's security system this looks perfect:

Check Result
Password ✓
OTP ✓
Known device ✓
Known place ✓
Verdict ALLOW

Passwords and OTPs answer one question: "Is this the real owner?" In these scams the answer is yes. The owner is real, but someone else is pulling the strings. We wanted a system that asks a different question: "Is this person acting freely?"

That became Rakshaka (රක්ෂක, "protector"), told through the story of Siriwardena Seeya, 74, a retired teacher in Kandy. He is about to send Rs 4,200,000, 35 years of savings meant for his granddaughter's university fees, to a scammer's "safe account". All people, banks and money in the project are fictional.

What it does

Rakshaka is a network of 11 AI agents in 3 layers, with humans in the loop.

Layer 1: Phone Shield

  • Coercion Sentinel runs a live pressure meter (0–100) while the elder is in the transfer flow. It reads signals such as an active phone call, a pasted account number, long hesitation, going back and forth, and scam words in the payment note (Sinhala, Tamil, English or Singlish).

Layer 2: Bank Council (meets on every request)

  • Place & Time, Routine, Mule Ring, Pattern: specialists that each write a proposal (stance, confidence, one-line reason).
  • Citizen Advocate: a counter-agent whose job is to argue against locking someone out (a hospital bill at 1 AM is unusual, but real).
  • Scam-Breaker: talks to the elder calmly in their own language, by voice and text.
  • Guardian Arbiter: reads all proposals and writes the ruling (ALLOW, STEP-UP, HOLD, BLOCK, or ask a human).

Layer 3: Fraud Network (three fictional banks)

  • Pattern Learner turns a confirmed scam into network immunity.
  • Money Tracer follows stolen money hop by hop across mule accounts.
  • Bank Liaisons (one per bank) agree to or object to cross-bank freezes.

Humans stay in control:

  • the elder uses their phone app;
  • the guardian (a son or daughter) gets a 3-line card with Reject / Approve / Call now;
  • the bank fraud analyst approves cross-bank freezes and breaks deadlocks.

In the demo, the Mule Ring agent finds that two other elders, Mallika and Rasheed, paid the same 9-day-old account in the last 35 minutes. The council puts a locked HOLD on the transfer, the Scam-Breaker speaks to Seeya in Sinhala, and his son Dilshan presses Reject. Rs 4,200,000 is protected. The network then chases Mallika's money and freezes it before the ATM cash-out. When the same scam reaches Kamala in Trincomalee, in Tamil, it is recognised in under a second.

How we built it

Rakshaka is built almost entirely in Jac / Jaseci: backend, agents, seed data and frontend.

  • The graph is the blackboard. Citizens, accounts, devices, requests, proposals, rulings, signals and alerts are all graph nodes. Agents never call each other. Each one reads the graph and writes its own Proposal node with a short English reason, so every decision can be explained in the War Room.
  • Walkers are agents. RunCouncil spawns the specialists on a Request node. The Mule Ring agent literally walks the money edges (Paid), and the War Room animates that walk hop by hop.
  • Two systems of thinking. System 1 (fast, calibrated scores) handles risk readings. System 2 (Jac by llm() with typed outputs) handles reasoning and the text written for people. Every AI call has a plain-code fallback with the same return type, so the demo never freezes.
  • Safety rules are plain code, never an LLM:
    • a confirmed mule account is always blocked;
    • a guardian's Reject always stops the transfer;
    • freezing an account at another bank always needs a human analyst;
    • risk numbers come from code or calibrated models, never invented by an LLM.
  • The pressure meter is a weighted sum of warning signs. In the fallback version:

$$ P = \min!\Big(100,\; 100 \sum_{i} w_i \, s_i\Big), \qquad s_i \in {0,1} $$

with weights such as $w_{\text{on call}} = 0.30$, $w_{\text{remote app}} = 0.25$, $w_{\text{pasted account}} = 0.15$ and $w_{\text{scam words}} = 0.20$. A reading of $P \ge 70$ counts as coached.

  • Frontend in Jac client (.cl.jac): five screens.
    • the elder's phone app;
    • a demo control panel with simulated device signals and QR codes;
    • the guardian's phone;
    • the War Room (trust graph, agent cards revealed one by one, "Normal security vs Rakshaka", bank-to-bank signals);
    • a story/debrief page.
  • Three languages everywhere: Sinhala, Tamil and English. UI text goes through one t(key, lang) helper, fonts are Noto Sans Sinhala and Noto Sans Tamil, and the browser reads the Scam-Breaker's questions aloud in the elder's language.

Three people worked in one repo, with fixed names and data shapes agreed up front. That let us build the graph, the AI functions and the screens in parallel.

Challenges we faced

  • Detecting coercion, not fraud. The hardest design problem was that the "attacker" is the real owner, so every classic signal says fine. We had to rely on behaviour (pressure, routine, the money network) and on people who know the elder.
  • Not becoming the villain. A system that freezes old people's money "for their safety" is its own kind of harm. The Citizen Advocate agent, the deadlock rule (step-up plus guardian instead of a lockout) and the hospital-at-1-AM scenario exist to keep us honest.
  • Jac client surprises. Compiling Jac to the browser taught us a lot:
    • read-only endpoints are cached for 60 seconds, which froze our 1-second live polling until we turned the cache off;
    • a few Python habits don't survive the trip to JavaScript: str.isdigit(), "".join(), min(list), and dict literals with variable keys;
    • debugging meant reading the compiled JS and catching exceptions in headless Edge over the Chrome DevTools protocol.
  • Persistent graph state. Tests and the dev server share a persisted graph, so stale data caused random failures until we learned to start each run from clean data.
  • Language is security. A scam warning the elder can't read protects nobody. Getting Sinhala and Tamil to render well, finding speech voices for them, and handling scam text typed in Singlish all took real work. The Sinhala and Tamil text is marked for review by native speakers.

What we learned

  • Object-Spatial Programming fits multi-agent systems well. When the graph is the shared memory, agents stay small, independent and explainable.
  • Pairing fast calibrated scores for numbers with LLM reasoning for words, and wrapping both in code fallbacks, gives a system that is smart and dependable.
  • The best security for real people mixes AI with humans who care about them: a son's one-tap Reject is stronger than any model.

What's next

  • real bank-app SDK signals in place of our simulated toggles;
  • native-speaker review of every Sinhala and Tamil string;
  • piloting the guardian flow with families;
  • extending the Fraud Network pattern to more banks, so every confirmed scam makes the whole network immune faster.

Built With

  • byllm
  • jac
  • jac-client
  • jac-hammer
  • jaseci
  • jev
Share this project:

Updates

Submission history