My brain needed external structure, so I built some
I'm autistic and ADHD. A lot of the stuff that's supposed to happen automatically for other people, like drinking water, eating something, remembering the thing, or going to bed at a reasonable hour, doesn't happen for me unless something outside my own head reminds me. So I built the thing that reminds me. Twice, actually: once living in Discord, once living on my desktop. This page is the honest version of both, covering why they exist, what they do, and the real code underneath.
This page is the build story. For the neurotype itself, and the rest of the diagnostic picture, that lives on AuDHD.
Why external structure, specifically
ADHD isn't a willpower problem, whatever the discourse around it sometimes implies. A lot of it comes down to executive function, the part of the brain that's supposed to plan, prioritize, and remember to act on things you already know you should do. Mine is, to put it gently, unreliable. Knowing I should drink water doesn't reliably translate into me drinking water. Knowing I should go to bed doesn't reliably get me into bed. The gap isn't between knowing and caring, it's between knowing and doing, and no amount of caring harder closes it.
The well-documented fix for that isn't "try harder." It's external scaffolding: something outside your own head that does the remembering, the nudging, and the "hey, did you actually do the thing" on your behalf, so your executive function doesn't have to carry the whole load by itself. Alarms, planners, body doubling, and accountability partners are all the same basic idea wearing different clothes. What makes any of them work is that they don't depend on me being in a good state at the moment I need them. I write code, so my version of that scaffolding is something I built myself. The idea isn't clever. It's a sticky note with a database behind it.
๐งฉ Two Hazels, one idea
This trips people up, so here it's up front: there are two separate things called Hazel. They share a philosophy (gentle structure over willpower) but they're completely different codebases, built for different moments of the same bad day.
Hazel on Discord
Lives where I already am all day. It pushes reminders, bedtime checks, and basic-needs nudges into my DMs without me having to remember to open anything. Best at catching me mid-hyperfocus, when I'm least likely to check on myself.
How it works โHazel Care
A standalone desktop app with its own local database and no AI in it at all. It keeps nudging me about water, meds, habits and routine until I have actually dealt with them, and pushes those alerts to my PC and my phone. Entirely offline.
See the app โ๐ฌ Hazel on Discord
The always-on half. It's a Discord bot because Discord is the one app I never close, and meeting my brain where it already is beats building something I'd have to remember to check. A reminder tool you have to remember to open is just a to-do list with extra steps. Full project details โ
Reminders that actually land
A background loop checks for due reminders every few seconds and delivers them straight to my DMs. It isn't a list I have to remember to open, it's a message that simply shows up. If I need something to happen later today, I tell Hazel once and stop thinking about it. That's the actual value: I get to put the thing down.
An actual bedtime checker
Hazel tracks a scheduled bedtime and checks in when it arrives. I can answer yes, later, or with a specific snooze length, all as a normal reply, with no rigid command syntax required. If I say "later," it follows up again gently instead of giving up after one try, which matters because the first nudge is the one I'm most likely to wave off without thinking.
Basic-needs check-ins
A simple, low-pressure prompt covering the things that are easiest for me to lose track of during a hyperfocus spiral: water, food, meds, and movement. It isn't a lecture, just a nudge, and it's built to praise small wins instead of pressuring bigger ones. Anything that made me feel judged for answering honestly would get ignored within a week, and then it would be worse than nothing.
Consistency as the actual feature
The autism half of this shows up differently than the ADHD half. It's less "I forget" and more "once I decide 8:30 is bedtime, I want the exact same message, in the exact same tone, every single night." A human who phrased it differently each time would be worse for me than a slightly dumber bot that never varies, because the sameness is the part my brain relaxes into. Predictability isn't a limitation of the system here, it's the whole feature.
๐ Hazel Care, the desktop half
The other one, and the one that changed the most. It used to be a tracker with an AI coach bolted on. It's now a reminder engine that won't let go of me. The tracking is all still in there, but it's supporting detail now, not the point. It runs as a native window on my own PC, entirely offline, and there's no AI in it anywhere.
๐ What it actually is
Hazel Care is a checklist that remembers, and that keeps nudging me until I have dealt with something. Water, medication, meals, habits, routine, tasks. All of it writes to a single SQLite file that never leaves my machine.
The reframing came out of one question: what do I need this thing to do? The answer, once I wrote it down out loud: strip as much executive function out of the loop as possible, and act as a reminder and checklist that pushes alerts to my PC, and to my phone. Once delivery was the product and not a feature, most of the app's earlier design turned out to be pointed the wrong way.
The AI is gone, on purpose. The previous version had a coach that talked to Gemini. I removed the whole thing: the provider layer, the persona, the context packet, all of it. It was the part of the app I found most interesting to build and the part I used least. That's a fairly reliable signal that it was there for me and not for the problem. What's left does one thing and doesn't need a network to do it.
The XP and levels are gone too, replaced by Hazel Points, which are awarded for real actions and can't be farmed: one award per action per day, and un-ticking a box deletes its award. Toggling a habit twenty times nets three points, not sixty. A number you can inflate by fidgeting isn't a number that means anything.
Why a whole second app instead of more Discord commands? Because Discord is where I talk to other people, and I didn't want my medication tracker living in the same place I'm also trying to have a social life. I also wanted something that still works when Discord, my internet, or the VPS is having a bad day. This one lives entirely on my own PC and doesn't care about any of that, which means the days it's most likely to be needed aren't the days it's most likely to be down.
๐ฅ๏ธ The actual screens
Real screenshots of the real app, taken from the running build and not mocked up. The old ones on this page showed the previous multi-screen AI version and had been wrong for a while.
โฐ The bit I'd keep if I could only keep one
I built the first version so it pre-scheduled a job for every reminder when the app started. That's the obvious way to do it, and it has three failure modes that all look identical from where I sit. All three of them say "Hazel did not remind me":
- A sleeping laptop misses the job, and a one-off job doesn't come back.
- Editing a reminder needed a manual re-plan, which is exactly the sort of thing I forget.
- Medication had no jobs at all. The biggest hole, in an app that tracks doses.
The queue is gone. Now one job runs once a minute and re-derives the entire answer from the database. Nothing is pre-scheduled, so nothing can be lost when the machine sleeps, the clock jumps, a reminder is edited, or the app restarts. I can wake the PC at 3pm and the 9am dose alerts immediately, because the row still says due-at-9 and alerts-sent-zero.
The other half of it is that I keep the fire-or-wait decision a pure function. It takes plain values and returns a string. No database, no clock, no input or output. That's precisely why the awkward parts, quiet hours that wrap past midnight, snoozes, repeat spacing, are testable instead of hopefully-correct.
def decide(*, now, due_at, fired_count, last_fired_at,
snooze_until, repeat_min, max_alerts,
quiet_start, quiet_end) -> str:
"""Should this nudge fire right now? -> 'fire' | 'wait' | 'quiet' | 'spent'
Order matters and encodes the priorities:
1. A snooze is a promise. Nothing overrides it, not even quiet hours ending.
2. Nothing fires before it's due.
3. Quiet hours suppress but never *consume* an alert, so a 3am dose
surfaces at 7:30 rather than vanishing.
4. The first alert always fires the moment it's due.
5. Repeats are spaced by `repeat_min`. `max_alerts` of 0 means keep going
until it's done or the day ends.
"""
if snooze_until is not None and now < snooze_until:
return "wait"
if now < due_at:
return "wait"
if in_quiet_hours(now.time(), quiet_start, quiet_end):
return "quiet"
if fired_count <= 0:
return "fire"
if max_alerts and fired_count >= max_alerts:
return "spent"
if last_fired_at is None:
return "fire"
if (now - last_fired_at).total_seconds() >= repeat_min * 60:
return "fire"
return "wait"
class NtfyChannel:
"""Push to an ntfy topic -> a notification on your phone.
Publishes as JSON rather than with ntfy's header protocol on purpose: HTTP
headers are latin-1 in practice and every title here has an emoji in it.
JSON bodies are UTF-8 and carry the action buttons without escaping games.
"""
name = "ntfy"
def send(self, n: Notification) -> bool:
import httpx
payload: dict = {
"topic": self.cfg.ntfy_topic,
"title": n.title,
"message": n.body or n.title,
"priority": max(1, min(5, n.priority)),
}
if n.actions:
payload["actions"] = [
{"action": "http", "label": a.label, "url": a.url,
"method": "POST", "clear": a.clear}
for a in n.actions[:3] # ntfy renders at most three
]
try:
r = httpx.post(self.cfg.ntfy_server, json=payload, timeout=8.0)
if r.status_code >= 400:
print(f"[{APP_ID}] ntfy refused the push: {r.status_code}")
return False
return True
except Exception as exc: # a dead network is not a crash
print(f"[{APP_ID}] ntfy push failed: {exc}")
return False
send() method. ntfy was the pick because it's free, needs no account, and can be self-hosted later if I want it off someone else's server. It rides on httpx, which was already a dependency, so this added nothing new to install.RULES: dict[str, tuple[int, str, str]] = {
"water_log": (1, "๐ง", "Logged water"),
"water_goal": (5, "๐ฏ", "Hit the water goal"),
"med_taken": (3, "๐", "Took a dose"),
"meal_logged": (2, "๐ฝ๏ธ", "Logged a meal"),
"habit_done": (3, "โจ", "Did a habit"),
"routine_done": (2, "โ
", "Checked off the routine"),
"task_done": (2, "๐", "Finished a task"),
}
๐ฃ Nagging, escalating, honestly
Alerts one and two arrive normally. Three to five go out at high priority. Six and beyond go out as urgent, the level that breaks through Do Not Disturb on Android. Nagging that never gets louder is nagging you learn to sleep through, and nagging that starts loud is nagging you switch off entirely.
An adversarial review of the whole thing turned up ten problems. Two of them mattered a lot:
- Alerts were being consumed when nothing was delivered. The bookkeeping ran and committed before dispatch, and dispatch quietly does nothing when the master switch is off. Turn alerts off for a meeting with "give up after 3" set, and the 9am dose would burn all three alerts into the void, never fire again, and the card would cheerfully report "3 reminders sent". There's now a delivery gate checked before any decision is made.
- Acting on a notification ignored which day it was for. Notifications sit unswiped for days. Tapping last night's 9pm dose at 10am would log today's dose as taken, then cancel today's real reminder, recording adherence for a dose nobody swallowed. Now refused, naming the day it was actually for.
Both are the same class of bug: the app being confidently wrong about something it should have been honest about. In a medication tracker that's the only kind of bug that really counts.
๐ Privacy, by design not by policy
Everything lives in one hazel.db SQLite file on this PC. No telemetry, no analytics, no accounts, no cloud sync, and now no AI provider either. The server binds to 127.0.0.1, so it's not reachable from other machines on my network, let alone the internet.
Two optional things touch the network, both named in Settings and both switchable off: a weather widget, and a currency rate on the second-clock screen. Phone alerts are a third, off by default. When one fires, the reminder's title is the only thing that leaves the machine. No medication history, no water, no points, no notes.
Medication timing is about as personal as data gets, and I'd rather not send it anywhere than trust a privacy policy about it.
Why I built these instead of using an app
I did try the generic apps first. Most ADHD-focused apps assume a level of consistent engagement that's exactly the thing ADHD makes hard: you have to remember to open the app, set up the reminder, and check back later. Every one of those steps is a place where I quietly fall off, and after the second or third time falling off, opening the app starts to feel like being confronted with a list of my own failures. Building my own meant I could make it work the way my actual brain works instead of the way a designer assumed a "typical" user's brain works.
It also means it isn't going anywhere. There's no subscription, no company deciding to pivot, and no feature I depend on getting moved behind a paywall. When I notice I need something, I can add it that night instead of filing a feature request into a void. That ownership sounds like a small thing until you've had a tool you organized your whole week around get discontinued.
This is also the plainest version of "special interest meets real need" that autistic and ADHD people talk about a lot. I hyperfocus on building things whether or not that hyperfocus is useful. Pointing it at a tool that makes my day-to-day easier is one of the better trades I've made with my own brain, because the building is the fun part and the result is something that keeps paying me back long after the novelty wears off.
If you're neurodivergent and thinking about building your own version
Do you need to be able to build a whole Discord bot for this idea to work? No. The principle is small: pick one thing you keep forgetting, and put something outside your own head in charge of reminding you. That could be a recurring phone alarm with a properly written label, a habit-tracking app, a sticky note somewhere you can't avoid looking at, or a person you've asked to check in on you. The tool matters far less than having something there that isn't relying on your memory alone.
Two things helped me more than any specific tool did. The first is starting with one thing instead of ten, because a system that covers your whole life is a system you'll abandon in a fortnight. The second is designing it so that ignoring it once doesn't break it. Anything that punishes you for a missed day is something you'll eventually stop opening instead of facing, and a scaffold you avoid is worse than no scaffold at all.
Being trans didn't cause any of this, and figuring out my own gender is a separate thing from figuring out my own brain. They did happen on roughly the same timeline for me, though, and I don't think that's a coincidence for a lot of us. More on that side of things โ