This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
What I Built
Be honest: when did you last drink water while working?
I can sit at my laptop for four hours straight, and the only thing that moves is my cursor. My eyes burn, my back hurts, and the water bottle next to me is exactly as full as it was in the morning.
I've tried reminder apps. They all fail the same way: they say the same boring sentence every time, and within a day my brain learns to skip it. A notification I've read 50 times is not a notification anymore, it's wallpaper.
So I built Break Buddy π¦Έ
Break Buddy is a Windows app that runs quietly in the background. When it's time for a break, a pixel-art hero pops up in the corner of my screen, points at an RPG-style dialogue box, and types out a message that is different every single time, in whatever personality I picked. Here's one it wrote for me:
"Are your eyelids auditioning for an endurance sport?"
I didn't write that. I never will see the same reminder twice. And honestly, it works. I laughed, then I actually rested my eyes.
Here's what it does:
- Setup is one typed sentence. I typed "remind me to drink water every 45 min, rest my eyes every 20, stretch every hour, nothing before 9am or after 8pm, sarcastic friend tone" and a local model turned it into a config file. No settings screen, no sliders, no dropdowns.
- Any personality you like. Sarcastic friend, calm coach, pirate, angry gym bro. Change one word and the whole app talks differently.
- It knows when not to bother you. It reads my active window title, labels what I'm doing (coding, video, meeting, documents...), and holds reminders back during a Zoom or Google Meet call. When I leave the call, I get one combined popup instead of three stacked ones.
- Long-break nudges. After 2 hours of continuous screen time, it tells me to take a proper break and mentions what I've mostly been doing.
- Snooze, pause and quit from the popup and a system tray icon.
It starts with Windows and has no console window. I set it up once and never touched it again. That's the whole point. π¦₯
Demo
Code
https://github.com/kumudasrip/Break-Buddy
The README has the full setup. You need Windows, Python and Ollama, and it's about five minutes from clone to first popup.
How I Built It
I know Python well, but I had never used an open-weight model before this week. I had Ollama installed and a few models pulled, and that was it. No fine-tuning, no GPU tricks, just calling a local model from Python and seeing what happens.
The stack
- Ollama runs the models on my machine. I call it from Python like any other library.
- gemma4:e4b is the default model. It's small enough to stay loaded all day.
- Pydantic + Ollama structured output forces the setup step to return valid JSON, which is validated and retried up to 3 times.
-
pygetwindow reads the active window title, and the Windows
GetLastInputInfocall measures idle time. - tkinter draws the popups and pystray draws the tray icon.
- The hero is drawn entirely by Python (
make_sprite.py). There are no image files to download. It's an original hooded, caped guardian with a crescent-moon emblem.
Where the AI actually earns its place
A plain "every 45 minutes" timer needs no AI at all, and I didn't want the model to be decoration. So it has three real jobs:
- Understand me. Turn a typed sentence into a structured config.
- Understand my screen. Label what I'm doing from the window title, so reminders can wait during meetings.
- Write the message. A fresh line for every reminder, in my chosen tone. It only receives a label like "coding" and a number of minutes, never the real window title.
What went wrong (the part I actually learned from)
1. The model ignored my tone.
I typed "sarcastic friend" and the config kept saving "friendly". Again and again. The cause was my own schema: I had pre-filled "friendly" as a default value, and the small model copied the default instead of reading my sentence. The fix was to make the field required with no default, and to add one example ("pirate voice" becomes "pirate"). After that it worked every time. Small models follow defaults over instructions, and now I know.
2. The popups overlapped.
I had guessed the popup height, and on my high-DPI screen the real windows were taller. Popups piled on top of each other and hid the one below. I switched to measuring each popup after its text is laid out, and turned on DPI awareness.
3. Meeting detection was too trusting.
I tested it with a file named Zoom meeting.txt, and the model correctly said it was not a meeting. But that test made me realise something uncomfortable: if the model ever misreads a real call, the app interrupts me in the middle of it, and that's the worst possible failure. So I added fixed patterns for Zoom, Meet, Teams and Webex that run first, with the model as the backup. In my logs the model caught titles my patterns missed (like Kumuda Sri Padmanabhuni's Zoom Meeting), so the two layers cover each other.
4. Limits I know about.
Zoom's home screen ("Zoom Workplace") also holds reminders back, even when I'm not in a call. And a "Connectingβ¦" window at the start of a call can let a reminder slip through for a few seconds. I'd rather tell you than hide it.
Slow and steady: which model should an always-on app use?
I wanted to know if I even needed a big model, so I wrote benchmark.py. It imports the app's own prompts and runs the same tasks through three models: 10 typed setup sentences, 36 window titles (including my real ones), and reminder messages in five tones. Setup and labeling ran at temperature 0 with one attempt per case. The real app retries setup up to 3 times, so real-world results are a little better.
| gemma4:e4b | gemma4:26b | qwen3.6:35b | |
|---|---|---|---|
| Setup: perfect parses | 9/10 | 10/10 | 10/10 |
| Window labels (patterns + model) | 97% | 97% | 92% |
| Meeting recall | 7/7 | 7/7 | 7/7 |
| Non-meetings called meetings | 0 | 0 | 0 |
| Messages that follow the rules | 100% | 100% | 80% |
| Cold start | 5.8 s | 64.7 s | 25.7 s |
| Median time per message | 0.2 s | 0.4 s | 0.5 s |
| Tokens per second | 118 | 56 | 61 |
| Memory while loaded | 3.3 GB | 18.3 GB | 23.3 GB |
| Share running on my GPU | 100% | 51% | 41% |
What the numbers told me:
- The small model was enough. The bigger models got exactly one more setup sentence right (e4b dropped the "walk every 90 minutes" reminder from "take a walk every 90 minutes, no reminders before noon, motivational speaker"). On labeling and message quality, e4b matched or beat them.
- Going big costs a lot. gemma4:26b needs 18 GB and took over a minute to cold-start. On my laptop it only half fit on the GPU. For something that sits in memory all day, a 3.3 GB model running fully on the GPU is the obvious choice. That's the sloth way: do the job with the least energy.
- The biggest model wasn't the best one. qwen3.6:35b labeled my Copilot window as "coding", and one sarcastic message wasn't about water at all: "I suppose someone has to remind you to stay alive today." Funny, but not a water reminder. π
- Safety rules beat model size. All three models caught every meeting and none raised a false alarm, partly thanks to the fixed patterns.
- Honest caveats. This is a small test (10 sentences, 36 titles), I wrote the correct labels myself, and some "mistakes" are debatable. A Chrome tab I'd call "other" was labeled "browsing" by all three models.
Why Does Open Innovation Matter?
I'll say it plainly: ChatGPT or Claude could write a nicer reminder than my local model, and would probably label odd window titles better. If this were a one-off task, I'd use one.
But Break Buddy isn't a one-off task. It's a tool that watches my screen and runs every day for the rest of my life. For that, open and local wasn't a nice-to-have. It's the only version I'd actually be willing to run.
1. It keeps my screen off someone else's server.
Break Buddy reads my active window title every few seconds. That's every tab I open, every file I touch, every chat window, every job search, every bank page. I would never stream that to a server I don't control, and I'd be uncomfortable even with a promise not to store it. Because the model runs on my laptop, I could build the one feature that makes the app smart, knowing what I'm doing, with zero privacy cost. With a closed API, that feature would have been the part I couldn't ship.
2. An always-on tool should cost nothing to leave on.
This app makes model calls all day, every day. On a metered API, "set it once and forget it" turns into a bill that never stops. Locally it's free, and gemma4:e4b idles at 3.3 GB without me noticing. Nothing in the app needs the internet once the model is downloaded.
3. I could measure instead of guess.
Changing one line let me put three models through the same tests and compare accuracy, speed and memory. That's how I discovered that the small model was enough, and that the biggest one wasn't better. With a closed API, I'd be using whatever model the provider picks, and I couldn't have measured the memory, the GPU share or the cold start at all.
4. I could control and debug the model's behavior.
When the model kept saving "friendly" instead of "sarcastic friend", I could look at my own schema, set the temperature to 0, switch thinking off, force the output into JSON and find the real cause. When the vision model failed, I could test it from the command line to prove the problem wasn't my code. Having every layer in my hands is what turned "it doesn't work" into "here's why".
5. Nobody can change it under me.
The app behaves the same way tomorrow as it does today. A provider can't quietly update a model, change its pricing, or retire the endpoint my daily tool depends on.
The honest trade-off is quality on unusual cases. A big cloud model would probably win there. But for this job, a small model on my own machine did everything I needed, and that's the point. Open models let me pick the right size for the problem instead of renting the biggest one by default.
The Lazy Payoff
I set Break Buddy up once, with a single sentence. Now it starts when I turn on my laptop, and since I switched to real mode the reminders show up on schedule without me opening a settings screen or touching a config file.
And the best part: it actually changed what I did. I really did drink the water, rest my eyes and stand up when the hero told me to. A normal timer I would have dismissed without reading. A pixel hero that roasts me in a fresh sentence every time is much harder to ignore. π
In my Meet and Zoom tests, reminders were held back for the whole call, and I got a single combined popup afterwards instead of a pile of them. So the app doesn't just remind me. It knows when to stay quiet, which is what a lazy person really wants from an assistant.
Effort I save: no remembering, no setup screens, no switching apps, no dismissing the same sentence 50 times. Effort I spend: one typed sentence.
Prize Categories
-
Best Use of Gemma. The default model is
gemma4:e4b, and I benchmarked it againstgemma4:26bfor setup parsing, window labeling, message quality, speed and memory. The small Gemma turned out to be the right model for an always-on tool.
Look away from the screen. Drink some water. The hero is watching. ππ


















