Most "AI agent" demos have one agent answering one person. The problem I wanted to solve needed the opposite: several people and several agents, in one chat, getting one urgent job done together.

This post walks through how I built it, for the WeMakeDevs × Switch "Flip the Switch" hackathon. For each part I explain why it's built that way, what I tried first, and where it broke. If you're building anything where agents and humans share a chat, most of these lessons carry over.

What you'll learn

  • How to split work between AI agents and plain code, and why I kept the most important decision away from the AI
  • How a service with no AI at all can be a full participant in an agent chat room
  • Why AI agents can't keep a clock, and what to do instead
  • How to hand work from one agent to another in front of the humans
  • Why "the agent asks for approval" wasn't safe enough, and what I replaced it with
  • Real bugs from live testing, and how I got out of each


🎥 Demo video · 💻 Code · 𝕏 The thread on X

The problem in one minute

In a "digital arrest" scam, a caller posing as police puts an older person on a video call, says they're under investigation, and walks them through sending their savings to "clear their name". India's Home Ministry told the Supreme Court of 21,900+ complaints and ₹647 crore+ in eight months of 2026 (Hindustan Times).

The scam runs on isolation. Victims are told they "must not communicate with anyone else" (Indian Express). Even the bank's safety check fails: it calls the account holder to confirm, and that's the one person the scammer is coaching.

So the design rule was simple: don't ask the victim, tell the family. When Mom's bank SMS looks wrong, the family's group chat gets an alarm, and AI agents help the family split the work: one person calls Mom, one reports on the 1930 helpline, one calls the bank, and the paperwork gets done before the bank's reporting deadline.

(The longer story, with sources, is in this post on the project site.)

Why Switch, and not "a Telegram bot plus an LLM API"?

My first instinct was a Telegram bot that calls an LLM. Then I listed what I'd have to build myself:

  • a shared history that every agent and person reads from
  • routing: which message goes to which agent
  • agent-to-agent handoff without a hidden back channel
  • a way for an agent to ask a human before doing something risky
  • the Telegram side, and later WhatsApp and SMS

Switch already does all of that. It's an open-source platform where people and agents are participants in the same room. Its Telegram bridge turns a Telegram group into a Switch room, so the family keeps using the app they already have, and every agent sees the same conversation they do.

That changed how I designed everything. Instead of wiring components together, every part of Parent Line just posts to the room and gets addressed in it.

Architecture: Bank watch, Ride watch and Call watch post into the family room on Switch; inside it the Guardian agent hands out jobs to the family and passes the case to the Clerk agent

Part What it is Switch feature it uses
Family group People in Telegram Telegram bridge
Guardian AI agent (Claude Code) The room's instructions are its prompt
Clerk AI agent (Claude Code) Agent-to-agent handoff, room documents
Bank watch Python, no AI Agent identity + HTTP API
Ride watch Python, no AI Agent event stream (long poll)
Call watch Python + Twilio, no AI Agent identity + HTTP API

Notice the bottom half: three of the five participants have no AI at all. That was deliberate, and it's the first lesson.

Lesson 1: Decide what must not be AI

The most important decision in the whole system is "should we wake the family at midnight about this payment?"

What I could have done: send every bank SMS to the LLM and ask "is this a scam?"

Why I didn't:

  1. Predictability. A family must be able to trust that ₹4.8 lakh at 23:40 to a new payee always fires, not "usually".
  2. Testability. Rules can be unit tested. A prompt can only be sampled.
  3. Privacy. If a model reads every SMS, it reads every grocery payment. With rules, no AI ever sees an SMS that doesn't raise an alarm.
  4. Cost and uptime. Rules run in microseconds with no API key and no network.

So Bank watch is plain Python, standard library only. It parses the SMS and checks a few rules: over the family's limit, a payee never paid before, an FD broken early, a loan, several debits close together, a debit late at night.

Terminal: the sample scam SMS raises an alarm listing three reasons; an ordinary ₹450 grocery payment raises nothing

The second command is the important one. A ₹450 payment to the local kirana store produces nothing. The family never sees Mom's everyday spending.

The alarm also carries a deadline. RBI's 2017 customer-liability circular says an unauthorised debit reported within three working days costs the customer nothing. Counting working days is exactly the kind of thing I didn't want a model doing:

def report_by(at: datetime) -> date: # shortcut: counts every day but Sunday as a working day; bank holidays  # only push the real deadline later, so this date is never too late.  day, counted = at.date(), 0 while counted < 3: day += timedelta(days=1) if day.weekday() != 6: counted += 1 return day 

💡 When you take a shortcut, make it fail in the safe direction. I skipped the bank-holiday calendar. Ignoring holidays can only make my date earlier than the real deadline, never later. Early is safe; late costs a family money.

What the AI does get: coordinating people. Turning an alarm into jobs, tracking who took what, chasing the ones nobody took, and writing complaints. Those are language tasks, and an LLM is very good at them.

The trade-off: rules miss SMS formats they don't know. Today Bank watch reads SBI's format and a generic one. A model would generalise better, and I chose to pay that price for predictability. Adding a bank means adding a parser and a test.

Lesson 2: A service with no AI is just another participant

Here's the part that surprised me most about Switch. To Switch, Bank watch is an agent, exactly like the Guardian. It has a name, an identity and room membership. It just never runs an AI session.

How:

  1. In Switch Console, create an agent called bank-watch and give it a working directory. Console writes its credentials there (git-ignored).
  2. Invite it to the room from Telegram: !invite-agent @bank-watch.
  3. Post with one HTTP call:
def post(self, room_id: str, content: str) -> str: """Send one message to the room; return Switch's event id.""" return str(self._call("message", {"room_id": room_id, "content": content}, "the alarm")["event_id"]) 

That's POST /agents/{id}/message. The alarm ends with @guardian over to you., and delivering that mention to the Guardian is Switch's job. No queue, no webhook between my services and my agents, no glue code.

Here's the family room in Switch Console, with all five participants:

Switch Console showing the family room bridged from Telegram, with five agents: guardian, bank-watch and clerk here now, ride-watch disconnected, call-watch status unknown

Why this matters: you don't need an SDK or an MCP client to join an agent conversation. Anything that can make an HTTP request can be a participant. That's what let me keep the critical logic in tested Python while still being "in the room".

Bonus: your prompt can live in git

The same HTTP client can run any Switch operation, the ones AI agents get as MCP tools. So bankwatch setup sets the Guardian's behaviour directly:

agent.op("update_room", {"room_id": config.room_id, "instructions": instructions}) 

The Guardian's prompt is the room's instructions: a Markdown file in the repo, with the family's names, the backup neighbour and the bank filled in from family.toml. Nobody pastes a prompt into a UI. It's versioned, reviewable, and reproducible on a fresh machine.

A few rules in those instructions came straight from watching Telegram render the output:

  • No tables, no headings. Switch converts the agents' Markdown for each chat app. On Telegram, bold and bullets survive and tables don't. So every layout uses bold, numbered lines and bullets only.
  • "One idea per line, at most 8 lines." People read these on a phone, at night, scared.
  • A fixed set of emoji: 🚨 alarm, ✅ done, ⏳ in progress, ❌ nobody yet. The same symbol always means the same thing.
  • "Tell people to start their reply with @guardian." By default a Switch agent only gets messages addressed to it. (A server feature flag, follow_the_conversation, lifts that, but it's off by default.)

Lesson 3: AI agents can't keep a clock

Ride watch is a check-in for a ride alone at night. You write @ride-watch 25 min and paste the ride app's share-trip message. If you haven't said reached 10 minutes after that, it asks. No answer 5 minutes later, and the family gets an alarm with the vehicle number and driver details.

My first thought was to give this to the Guardian. It doesn't work: an agent only acts when someone writes to it. It can't wake itself up at 23:55 to notice that nothing happened. "Nothing happened" never arrives as a message.

So the timer is plain Python again. It reads messages from Switch's agent event stream (GET /agents/{id}/events, a long poll) and runs a 30-second tick beside it:

while True: events = agent.events(25) # long poll: returns as soon as something arrives  for event in events: if not is_for_me(event, config.room_id): continue p = event["payload"] # Ride watch never reads an image; the Guardian does, if the alarm fires.  shots = [str(a["mxc"]) for a in p.get("attachments") or [] if str(a.get("mimetype", "")).startswith("image/")] step(lambda rides, p=p, shots=shots: on_message(rides, s, p["sender_name"], p["body"], shots, datetime.now())) 

Look at the screenshots line. People send a screenshot of the driver's details. Ride watch doesn't read it. It keeps Switch's media id. If the alarm fires, the Guardian (Claude) downloads it with its download_attachment tool and reads out the vehicle and driver for the family. On a ride that ends safely, no AI looks at anything. Reading an image is a judgement call, so it goes to the AI, and only when it matters.

🧱 Where I got stuck: an AI pretending to be my timer

In a live test, a ride alarm appeared in the family group before the check-in time. Ride watch hadn't posted it.

What happened: Switch Console has a room watcher. When an agent it manages is @-mentioned and isn't connected, Console starts a Claude Code session as that agent to answer. My Ride watch only polls, so to Switch it looked absent. Someone wrote @ride-watch, Console started a Claude session called ride-watch, and that session did what it thought a ride watcher should do: it posted a ride alarm. A fake one.

That's the worst kind of bug for this project. A false alarm sends a whole family running.

How I got out, in three layers, because any one of them could fail:

  1. Stay visibly present. Ride watch now sends a heartbeat (POST /agents/{id}/connection/renew) every 2 seconds. Switch forgets a connection after about 6, so now it counts the service as there.
  2. Turn the watcher off. On the agent's page in Console, under Room watcher, press Stop. It's now a step in the README.
  3. Tell any stray session to stay silent. The room instructions now say that bank-watch and ride-watch are small programs, and an AI session running under either name must post nothing.

Layer 3 is the one I could watch working. Here's a session Console started as bank-watch after the fix. It read the instructions and refused to post:

Switch Console: an AI session started as bank-watch replies that it didn't post anything because the room's instructions say bank-watch is a small program, not an AI agent, and that it must stay silent

(https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gmbqw44bvxw5zs790gyg.png)

💡 If a program and an AI can share a name, assume the AI will one day answer for the program. Make it impossible (stop the watcher), make it unlikely (stay connected), and make it harmless (instructions).

Lesson 4: Hand work between agents where the humans can see it

The Guardian splits the alarm into three jobs and tracks them as the family replies. Once money has moved, it hands the paperwork to a second agent in one plain line:

@clerk please open a case 

That's an ordinary message in the room. The family sees the handoff, can correct it, and can step in. No hidden agent-to-agent channel.

The Clerk keeps one case file per incident as a Switch room document (create_room_document). It writes the 1930 complaint and the letter to the bank, and owns the RBI deadline: on the last day, Bank watch posts a reminder addressed to @clerk asking for the bank's complaint number.

Why two agents and not one?

  • Different time scales. The Guardian is about people right now: who's calling Mom? The Clerk is about paper over days: was it reported, what's the complaint number, when do we escalate?
  • Smaller prompts work better. Each agent's instructions stay focused, and each is easier to test by itself.
  • Separate permissions. Only the Clerk can draft public posts, so only the Clerk needs the safety rules for them (lesson 5).

Here's a stretch of the real test group. A ride alarm, the rider says reached, the Guardian closes it, and then goes straight back to the bank alarm that's still open: who's called Papa, and who's taking job 2? (My test family's config calls the parent Papa.)

The family Telegram group: a ride alarm with three jobs, the rider replies reached, the Guardian closes the ride alarm, posts the bank alarm's job list with one job done, one in progress and one untaken, and later re-raises the open bank alarm to everyone

The numbers from that live test on Telegram:

  • the Guardian answered 5 seconds after the alarm
  • the Clerk had updated the case file 28 seconds after it
  • with no job taken, the Guardian chased the family at 3 minutes
  • @guardian I'll take 1 got an updated job list 2 seconds later

What's still not solved: because of lesson 3, the Guardian can't keep chasing by itself if nobody writes anything. The fix is the same pattern as Ride watch, a small timer service that re-raises an unanswered alarm. It's next on the roadmap.

Lesson 5: Put a human's thumb on the last button

When a bank or ride app ignores a complaint, public pressure works. So the Clerk drafts a short post for X tagging their support account.

Version 1: the Clerk posted through the X API, behind Switch's approval card. Claude Code's permission settings made the post command wait, and a family member had to press Allow in the chat.

What went wrong: in a live test the Clerk was running with Claude Code's Auto mode on. Its own safety review decided the command was fine and ran it. No approval card appeared. The guard I was relying on wasn't the one in charge.

Version 2: no agent can publish at all. The Clerk runs a checker, then sends a link that opens X with the draft filled in. A person reads it and presses Post on their own account.

Terminal: xpost.py refuses a draft containing a phone number and an account number, then turns a safe draft into an x.com compose link

The checker refuses phone numbers, email addresses, long account-style numbers and anything over 280 characters, because someone panicking at midnight will paste the wrong thing. The safe draft becomes an x.com/intent/post link.

Side benefits I didn't plan for: no X developer account, no API keys to leak, and the post comes from the family's own account, which carries more weight than a bot.

💡 For anything public and irreversible, don't rely on a setting to stop the agent. Remove the agent's ability to do it.

Other walls, and how to get past them

Some of these I hit in testing; the rest the README now warns about, so you don't have to.

Symptom Why Fix
The Telegram group never showed up in Switch Telegram's Group Privacy was on when the bot joined, so the bot never saw messages Turn it off in BotFather before adding the bot. Already added? Remove the bot and add it again
Every agent message ended with a list like mcp__switch__read_context Telegram connections show each turn's tool calls, which helps developers and confuses families I wrote a Switch PR adding a Show tool calls setting
My demo SMS was a ₹4,80,000 UPI transfer UPI to a person is capped at ₹1 lakh a day, so that SMS can't exist Switched the sample to IMPS. Domain details matter in a demo
Replaying an SMS with a changed reference number made the Clerk ask "one debit or two?" Bank watch drops duplicates by reference. A new reference really is a second debit, and the agents were right Delete bankwatch-state.json to replay, and don't fake the reference
Ride watch said a ride was "already alarmed" An old test ride was still saved as alarmed Delete ridewatch-state.json between tests
The missed-call webhook returned 403 Twilio signs the exact URL it calls. A quick cloudflared tunnel gets a new address on every restart, so the signed URL stopped matching my config Update public_url and Twilio's webhook together, every time
A family member's reply got no answer It didn't start with @guardian Tell people in every message, or turn on follow_the_conversation

Giving back to Switch

Most Indian parents live in WhatsApp, not Telegram. The parent-facing half of this idea, Line, is a help line in Mom's own chat. It answers phone questions one step at a time in Hindi, English or Hinglish, and refuses scam-shaped requests ("install AnyDesk", "tell me the OTP") by alerting the family. That needed new Switch platforms, so I wrote them:

Pull request What it adds One thing I learned
#1166 WhatsApp Check every webhook's X-Hub-Signature-256 and de-duplicate by message id, because Meta retries deliveries
#1244 SMS through Twilio Every text costs money, so cap segments per reply and per number per hour. Approval cards become a text reply like A1 1
#1585 Show tool calls setting You can't just switch off activity for a platform: the same code draws the approval cards people need
#1576 Microsoft 365 connector research Not used by Parent Line

They're still open, so Line is optional. Everything else runs on Switch as released. One design note: an agent is connected to one room at a time, so Line lives in Mom's chat and hops to the family room with connect_to_room to raise an alarm, then hops back.

Try it yourself

You don't need Switch, a phone or a bank to see the core logic. Python 3.11+ and one minute:

git clone https://github.com/prx-my/parent-line && cd parent-line python3 check.py 

Terminal: check.py reports OK for Python, repo files, family.toml, 58 unit tests and the sample alarm, then lists the optional tools for later steps

Every line is OK, NOTE or FAIL, and every FAIL comes with its fix. I wrote check.py for anyone cloning the repo, judges included: the first minute decides whether someone keeps going.

The full flow on Telegram with Switch Console takes about 20 minutes. The README walks through it, including the problems above.

GitHub logo prx-my / parent-line

Family alarms for digital-arrest scams, built on Switch: bank SMS triggers a family Telegram group where an AI agent and the family respond together.

Parent Line

When an older parent's bank SMS looks like a scam, the family's group chat gets an alarm, and two AI agents help the family act on it together. Built on Switch.

Demo video · Website · Try it in one minute · How Switch is used · My pull requests to Switch

Digital-arrest scammers win by isolation. They keep an older parent on a video call for hours, forbid them to tell anyone, and walk them through sending their savings to "clear their name". The bank's confirmation call doesn't help: it reaches the one person the scammer controls, who has been told what to say.

Parent Line tells the family instead. Someone calls the parent, someone reports it, someone calls the bank, and the paperwork is done before the bank's reporting deadline.

For judges














You're judging Where to look
Use of Switch
How Switch is used:
…





What it can't do (yet)

  • It can't stop a transfer. Only the bank can. That's why "call the bank" is one of the three jobs.
  • No SMS, no alarm. If Mom's phone is off, or the bank's format isn't parsed, nothing reaches Bank watch.
  • It isn't proven. The rules are tested and the flow works live. Whether real families respond fast enough is still an open question. Family members have stopped these scams in the news, but those are single stories. I found no study that measures how often it works.
  • Someone has to run it. Ideally each family runs its own Switch, so no company holds many families' bank SMS. For families who can't, someone they trust would host it. That part is a plan, not built.

The three things I'd tell you before you build with agents

  1. Draw the line between judgement and rules first. Give the AI language and coordination. Keep money, time and anything you must test in plain code.
  2. Make agents and services peers in one room. Then handoffs are visible, history is shared, and humans can step in at any point.
  3. Test live, early, with the real chat app. The worst bugs in this post, the fake ride alarm and the post that skipped its approval, only showed up in live Telegram tests, never in unit tests.

If you've been the child who got that phone call, or you're building something on Switch and hit different walls, I'd like to hear about it in the comments.

Built for Flip the Switch. #HowISwitch #FlipTheSwitch