Inspiration

First dates, Facebook Marketplace pickups, apartment viewings: we meet strangers all the time, and most safety apps assume you'll open them at the exact moment things go wrong. We wanted something closer to a friend who knows where you are, checks in only when something seems off, and gets help if you go quiet. Everyone already has iMessage open, so Batman lives there. There's no app, just a contact.

What it does

Text Batman your plan ("date at 7 at Lula Cafe"). It asks a couple of quick questions, gets a yes from the friend you chose, and watches your Find My location for that window only. If you leave fast, go dark, or run late, it checks in discreetly. If you don't answer, it alerts your friend with your last location. Two secret code words give you a silent way out: one gets you an excuse to leave, the other quietly alerts your friend.

How we built it

  • iMessage: TypeScript on Node, using Photon's spectrum-ts for messaging and advanced-imessage over gRPC for Find My, Focus status, and read receipts.
  • Plan engine: a deterministic state machine with durable SQLite timers that survive restarts. The escalation ladder is code, not a model.
  • Risk rules: geofences with hysteresis, plus speed checks for leaving by car:

$$ r = \max(r_{\text{base}},\ 2\,a), \qquad \text{outside} \iff d > r + \min(0.5\,r,\ 60\,\text{m}), \qquad v \ge 7\ \text{m/s} $$

where $a$ is the GPS accuracy and $d$ is the haversine distance to the venue. Ladder timers tighten at night and for riskier meetups:

$$ T_k' = T_k \cdot 0.7^{\,\text{night}} \cdot 0.6^{\,\text{vigilance}} $$

  • Language: Claude handles plan extraction and reply classification with structured outputs. Code words are matched deterministically before any model call.
  • Hosting and process: an always-on single machine on Fly.io. We built it with parallel Claude Code subagents (core, conversation, integration, test, and safety review), backed by 1,000+ tests and evals.

What we learned

  • Safety logic should never depend on a model. The LLM reads messages; it never decides to escalate.
  • Evals matter. Our reply classifier hit 98.7% accuracy with zero distress replies labeled "OK". Code words hit 100% recall with 0 false positives across about 2,000 normal messages.
  • Real platform limits shape the design. On a shared pool, every user gets their own number and has to text first. Calls need a dedicated line.

Challenges

  • Platform limits. Outbound calls were blocked (SIP 403) and native location pins weren't supported, so we built honest text fallbacks.
  • A sleeping laptop. Windows Modern Standby silently paused the server mid-test, so we moved Batman to an always-on host.
  • GPS noise. 300 m outliers were faking departures until we added outlier rejection.
  • Tone. Batman has to feel like a friend, not a form, without ever revealing anything on a lock screen during a meetup.

Built With

Share this project:

Updates

Submission history