Shalabh Jaiswal

Personal · 2026

Arnold

A WhatsApp bot that holds six of my friends accountable for going to the gym, charges them fines when they don't, and posts a weekly digest nobody can argue with. It ran live in a real group with real money on the line. That's a very different bar from a demo.

TypeScript · SQLite · Baileys 24,645 LOC Own server, Nuremberg

I built this because gym accountability groups always die the same way, somebody has to be the person who nags, and that person eventually stops. Arnold is that person, and it has no feelings to hurt. It tracks who logged what, assesses fines on a schedule, handles retractions and exemptions in natural language, and sends a Sunday digest.

6 Real users in a live group, not test accounts
~40% Test-to-source ratio
4 Persisted state machines, surviving restarts and host downtime

Under it: four persisted state machines that reconcile themselves on boot and never assume the process stayed alive, AES-256-GCM encryption on stored credentials, systemd timers with explicitly pinned timezones, and a scheduler that catches up correctly if the host was down when a fire time passed. Most of the difficulty in this project turned out to be time, not language.

The incident that set the rule

Early on, Arnold told the group a number that was wrong. Not badly formatted, not stale, invented. The model had been handed the job of summarising the week and it produced a plausible fine total that matched nothing in the database.

What I changed, and why it generalises

Every number is now computed deterministically before the model sees anything. The model's only permitted job is to re-voice a value it was given, and a post-compose audit gate re-checks the outgoing message against the computed figures and fails closed on a mismatch. If the numbers don't match, nothing goes out. The fix is not a better prompt. It's removing the model's ability to produce that class of output at all.

I arrived at the same instinct independently in Forge months later, where Claude is structurally never allowed to emit a timestamp. Two different systems, same conclusion: decide what the model must not be trusted with, then enforce it in architecture, not instructions.

Current status

Paused. The phone holding Arnold's WhatsApp number was stolen from my house. That's the whole reason. It isn't a reliability failure or a product that people stopped using, it worked well for as long as it was live, and restoring it is a pairing problem, not a rebuild. I'm saying that plainly because "paused" usually implies the other story.

← All work