· 7 min read

Your Reminders Don't Work Because They're Too Predictable

adhd ai architecture

I was undiagnosed with ADHD for over forty years. During that time I tried every reminder system that exists. Phone alarms. Calendar notifications. iOS Reminders. Todoist. Sticky notes. The pattern was always the same: set it up, use it for a week, start swiping away notifications without reading them, abandon it, repeat.

The problem isn’t discipline. The problem is architecture.

Why predictability kills reminders

A reminder that fires at 11:30 AM every day becomes background noise by day four. Your brain learns the pattern. It pre-dismisses the notification before you consciously process it. “Oh, that’s just the daily meds thing” — swipe — gone. You didn’t decide not to take your meds. You didn’t even register the reminder.

This is notification fatigue, and ADHD brains are especially vulnerable to it. Novelty is what sustains attention. Predictable stimuli get filtered out. Every reminder app on the market is designed around exact times and consistent schedules — the exact pattern that ADHD brains are wired to dismiss.

Three design decisions that change everything

So I built nag-bot — a single-user Telegram bot that does three things differently.

Fuzzy scheduling. Reminders don’t fire at 11:30. They fire around 11:30 — plus or minus five minutes, randomized each time. The jitter is small enough that timing still matters, but large enough that your brain can’t predict the exact moment. You can’t pre-dismiss what you can’t anticipate.

Natural language input. You don’t open an app, tap through date pickers, and configure recurrence patterns. You text the bot: “remind me to take my meds every day around 11:30, nag me until I do it.” Claude Haiku parses the intent in a single API call and creates the reminder. The interaction takes five seconds and feels like texting a friend.

Relentless nagging. This is the one that actually works. If you enable nag mode, the bot doesn’t send one notification and hope for the best. It pings you every two minutes until you explicitly type /done MEDS. You can’t swipe it away. You can’t ignore it. The only way to make it stop is to do the thing.

That third one sounds annoying. It is. That’s the point. The notification isn’t something you passively receive — it’s something you actively have to deal with. And for ADHD brains, that active engagement is exactly the mechanism that turns a reminder into an action.

One API call, not a conversation

The AI integration is deliberately minimal. When you send the bot a message, it makes a single call to Claude Haiku with a carefully structured system prompt. No tool-use loop. No multi-turn conversation. No agent framework.

The prompt defines exactly what the bot understands: schedule types (once, recurring, random window), fuzzy vs strict timing, nag intent, timezone handling, and short code generation. Claude returns structured JSON — reminder details, schedule parameters, a mnemonic short code like MEDS or DOGOUT — and the bot stores it in SQLite.

Haiku is fast and cheap. A single parse costs a fraction of a cent and completes in under a second. The entire Claude integration is one file, one function, one API call. No state between messages. No conversation history. No memory. The bot doesn’t need to be smart — it needs to be reliable.

The scheduler runs on a thirty-second tick

Every thirty seconds, the scheduler queries SQLite for due reminders. No setTimeout chains. No in-memory state. Just a simple loop: check the database, fire what’s due, update the records.

This makes the bot restart-safe. Kill the process, restart it, and it picks up exactly where it left off. Every reminder’s state — next fire time, nag count, last fired timestamp — lives in the database. The scheduler is stateless by design.

For recurring reminders, the next fire time is recalculated after each confirmation. The fuzzy jitter is re-randomized each cycle, so tomorrow’s 11:30 reminder might fire at 11:27 or 11:34. Different every time.

The nag loop is a state machine tracked by a single counter. When a nagging reminder fires, its nag_count increments. Every tick, the scheduler checks: is this reminder still nagging? Has the nag interval elapsed since last fire? If yes, fire again. When you type /done MEDS, the counter resets and the next occurrence is calculated. If you never respond, it stops after fifty attempts — a safety cutoff, not a feature.

Timezone math is harder than it should be

All times are stored in UTC. The user’s timezone is stored separately. A reminder set for “11:30” means 11:30 in whatever timezone you’re in — and if you travel, /timezone Asia/Tokyo recalculates every active recurring reminder.

Finding “the next 11:30 AM in Tokyo” from UTC is surprisingly non-trivial in JavaScript. The Date object only knows UTC. The solution uses Intl.DateTimeFormat to reverse-engineer the correct UTC timestamp that produces “11:30” when formatted in the target timezone — including across DST transitions. It’s a handful of pure functions, well-tested, and the kind of problem that sounds simple until you actually try to solve it.

Short codes as interface

Every reminder gets a short code — a 2-8 letter mnemonic generated by Claude from the reminder content. “Take my meds” becomes MEDS. “Let the dog out” becomes DOGOUT. “Renew passport” becomes PASSPORT.

These codes are how you interact with reminders after creation. /done MEDS. /pause DOGOUT. /cancel PASSPORT. No IDs to remember, no scrolling through lists. The codes are human-readable and collision-safe — if MEDS already exists, it becomes MEDS1.

The full command set is intentionally small:

/done <CODE>    — confirm and stop nagging
/list           — show active reminders
/cancel <CODE>  — delete permanently
/pause <CODE>   — temporarily disable
/resume <CODE>  — re-enable
/backup         — send database snapshot via Telegram

Every command is handled natively — no Claude involved, zero latency. The AI parses natural language input. Everything else is deterministic.

The backup is a reminder

One implementation detail I’m fond of: the auto-backup system is built on top of the reminder engine itself. When you type /autobackup 22:55, it creates a recurring reminder with a special sentinel value. When the scheduler fires it, instead of sending a notification, it exports the SQLite database and sends it as a Telegram document.

No separate cron system. No backup infrastructure. The same thirty-second tick that fires your meds reminder also handles your nightly database backup. One engine, multiple purposes.

The stack

LayerChoice
RuntimeNode.js 22, TypeScript, ESM
MessagingTelegram Bot API (polling)
AIClaude Haiku (single API call per message)
DatabaseSQLite (better-sqlite3, WAL mode)
ConfigYAML + environment variables
DeployDocker on Railway (persistent volume)

The whole thing runs on Railway’s free-ish tier. The persistent volume holds the SQLite database. Docker handles deployment. Total monthly cost for a single-user reminder bot: effectively zero.

Why not OpenClaw?

The obvious question. OpenClaw exists, it’s open source, it’s wildly popular, and it can do far more than send reminders. It manages email, controls files, automates workflows — an autonomous agent that runs your entire digital life through a messaging interface. It’s innovative and genuinely impressive.

I’m not using it for this. Two reasons.

Security. OpenClaw’s plugin ecosystem has over a hundred community-contributed AgentSkills, and the vetting process hasn’t kept pace with adoption. Cisco’s AI security team tested a third-party skill and found it performing data exfiltration and prompt injection without user awareness. That’s not a bug in OpenClaw’s design — it’s a consequence of giving an autonomous agent broad system access and letting third parties extend its capabilities. For a tool that manages my medication schedule and has access to my Telegram, that risk profile doesn’t work.

Separation of concerns. I don’t want a single entity running my life. I have separate agents for separate domains — Architect for the website, Compass for life strategy, Actions for workflow automation. Each one has a bounded scope, its own governance files, and no access to the others. nag-bot fits this model: it does one thing, it does it well, and it doesn’t know anything about the rest of my systems.

OpenClaw’s strength is breadth. nag-bot’s strength is constraint. A purpose-built tool with a narrow scope, no plugin system, and no ambition to become a platform. It parses reminders and nags you. That’s it. The attack surface is one Telegram bot token and one Claude API key. There’s nothing else to exploit.

It works because it’s annoying

I’ve been running nag-bot for my daily medication, and the pattern that killed every previous reminder system — set, ignore, abandon — hasn’t happened. The fuzzy timing keeps me from pre-dismissing. The natural language input means I actually create reminders instead of meaning to. And the nag mode means that when the reminder fires, I either take my meds or I deal with a bot that won’t stop texting me.

The architecture is simple. The AI integration is minimal. The scheduling math is the hardest part, and even that is a few pure functions. What makes it work isn’t technical sophistication — it’s three design decisions that align with how ADHD brains actually process notifications instead of how neurotypical productivity culture assumes they should.

nag-bot is still in beta — I’m dogfooding it daily and refining the rough edges as I go. If you’re an ADHD brain who’s burned through every reminder app on the market, or you just want to poke around the code and contribute, take a look: github.com/avanrossum/nag-bot