This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
In Port Harcourt, the light goes a...
For further actions, you may consider blocking this person and/or reporting abuse
Hey Daniel. Very good work! For some reason, I thought the title said that your laptop physically dragged you outside for a second lol.
But I hope your doing well on your end. Sorry if I have been leaving you out in some way. Just want to let you know that I am still here :)
Haha, it’s not that strong yet! 😂 Good to hear from you, Francis and no worries at all. Hope you’re doing well too.
Without GPU, Gemma is too slow. E2B is usable, but it too is slow.
Best of luck 👍️
Thanks Fayaz! You’re right, on CPU it’s slow. I measured 15–20 tokens/s on my i5. It works here because Gemma only writes one sentence of about 25 tokens. The real outage calls took 6 to 9 seconds and the tool already waits 30 s to make sure it’s a real cut, not a flicker.
For a chat app I’d want a GPU. Good luck with Birds.Quest too, I liked the “predictions are not sightings” rule.
Yeah, I learnt the hard way!
My i7 with 4 cores (3.4GHz each) took quite some time to auto write my last afternoon's bird watching session.
Fortunately, the real thing will run on DigitalOcean Serverless GPU, costing probably a few cents each time! 🙃
I'm thinking about fine tuning a smaller model (sub 1B) for my purpose, just for the fun of it.
I'll post about it if I get good results 🥰
Ha, a whole afternoon’s walk is a lot more text than my one sentence 😄 A sub-1B fine-tune sounds fun. A small model trained only on your blog’s style could be quick even on CPU. Do post it, I’d like to see the results!!!
I used another open model that takes GPS entry and predicts birds in that path. It's only a 15MB Model! Can even run on a low end phone browser.
That gave me the idea actually. If a 15MB open model can do that so well, something like a 300MB model should be able to write the posts just fine.
A 15MB model for that is impressive! I think it works so well because it’s choosing from a fixed list of species.
Writing is open-ended and that’s where I saw small models slip.
Gemma E2B was fine at tone but got facts wrong when it wrote the whole message.
What fixed it for me was splitting the job: code supplies the facts, the model only phrases them.
Your setup already has that shape, with the 15MB model giving the bird list. A ~300MB writer that only phrases that list might work well.
daniel, this is a masterclass in constraint-driven, local-first engineering. massive respect. 🐯🔥
as a developer who builds strictly under heavy constraints (like running ai tools on a $150 phone), i deeply appreciate the architectural choices you made here.
splitting the job so the code handles the facts (numbers, time, naira saved) and gemma only handles the phrasing is brilliant. it’s the exact right way to use a small, local model: leverage its tone while completely eliminating its tendency to hallucinate facts. the guardrail rejecting numbers and specific keywords is a beautiful, simple, and effective safety net.
also, discovering the
keep_alive: -1fix through a real-world cold-start timeout is the kind of battle-tested wisdom you can't get from simulations. keeping that 3.9gb model resident in ram to guarantee the tool works exactly when the grid fails is top-tier systems thinking.turning a frustrating daily reality into a moment of intentional rest is not just great engineering; it's great design. incredible work, and keep shipping! 💻🌍
Thanks Harun! 🙏 The keep_alive one hurt to find, but I’m glad a real outage caught it and not a judge. The guard isn’t perfect: it catches numbers and off-topic words, not sense, so the odd sentence still slips through. What AI tools are you running on the $150 phone?
daniel, glad the
keep_alivefix resonated! real-world outages are the ultimate debugging tool. 🐯to answer your question about the $150 phone: it’s all about ruthless optimization and avoiding heavy local inference when possible.
the goal is never to force the hardware to do what it can't. it's about designing the software to perfectly match the hardware's reality. keep building! 💻🌍
The test that blocks every network socket is a strong fit for the promise of this app. I’m curious about a long outage: does an expired cached forecast switch the plan to a conservative fallback, or is its age shown with the suggestion?
Good question. If the cached forecast is older than 6 hours, the tool treats it as missing: it drops rain from the plan and falls back to sunset only, which the laptop computes itself. It doesn’t show the forecast’s age, though, and you’re right that it should. In practice the nudge fires once, 30 seconds into the cut, while the forecast is at most 30 minutes old, so a long outage doesn’t make that first nudge stale. Showing the age is on my list.
Thanks, that answers it. I'd pictured the nudge updating throughout the outage, so the one-shot timing is an important distinction. Showing the forecast age alongside the sunset-only fallback would make the behaviour easy to understand. Appreciate the detailed explanation!
This is genuinely excellent.
The core insight is perfect: the short window between NEPA taking light and someone starting the gen is the one moment the screen is already losing. Turning that into a forced, offline, culturally fluent nudge to go outside is both clever and kind.
What I especially respect is the discipline in the architecture. Letting code own every number and fact, restricting Gemma to the nudge only, and putting a hard guard on length + forbidden topics is exactly how you make a small local model reliable. The real-outage debugging (cold start from
keep_alive, Modern Standby) shows the difference between something that looks good in simulation and something that actually works when the light goes.Also loved the cultural grounding. No one who has lived with routine power cuts needs the problem explained, and the Pidgin makes it feel native rather than imposed.
One of the strongest Touch Grass submissions I’ve seen. The best result really is the morning spent with your cousins’ children instead of waiting for the gen.
Thank you, Anne! This made my day. And yes, the morning with my cousins’ children is still the best result in the logs 😄
The separation between code-generated facts and model-generated wording is probably the most important design decision here. It reminds me of a problem in edge-based industrial monitoring: a model can generate a perfectly fluent response while getting the underlying state wrong, which is much worse than a slightly awkward sentence.
I’d be curious how you’d handle semantic validation as the project grows. Your current guard catches numbers and restricted topics, but it can’t detect a recommendation that contradicts the actual weather or situation. A rule-based validator could check the proposed activity against the same structured state used to generate the facts, leaving Gemma responsible only for phrasing. That would preserve the local-first approach without adding another model call.
Also, the laptop sleep issue is a good reminder that keeping a model resident solves only one part of reliability. Power-state transitions and process availability are part of the system contract too. Real outages exposed exactly the kind of failure a clean simulation might miss.
Thanks Sina. I think this is really the crux of the matter - fluency without accuracy. (The solution you're proposing is partly already implemented, actually - instead of "choosing" an activity, an activity is "picked" for Gemma. Rules "pick" an indoor activity when it's raining, a home-close activity after sunset, or an outdoor activity when the weather is dry.
Gemma is then given the picked activity to articulate, and it's up to her to do it well. But Gemma is not perfect: rather than correctly articulating the picked activity (outdoor), she'll in the wrong cases articulate some activity very poorly (given as "watch the rain inside the phone"), or articulate some other activity altogether. A corrective could be a rule to check the nudge is still talking about the picked activity - this would keep Gemma on track without needing a model call. I think you're right; reliability is what we're after. Keeping the model loaded may solve one class of problems, but the laptop going to sleep suggests the power state is part of the contract.
That makes sense. I’d treat the picked activity as an invariant rather than trying to judge whether the whole sentence sounds reasonable. For example, a lightweight semantic check could verify that the nudge still expresses the intended action, while rejecting outputs that introduce a different activity. The tricky part is that keyword matching alone can be brittle: “take a walk” and “step outside” express the same intent, while “watch the rain inside the phone” shares the topic but loses the intended action.
This is where a small set of activity-specific acceptance rules could be more useful than another model call. Given the offline constraint, I’d also keep the existing deterministic fallback whenever validation fails, rather than letting a second attempt become another reliability dependency. The goal isn’t to make Gemma perfect; it’s to make sure an imperfect response can’t break the behavior the system promises.
An invariant is a better frame for me. And, "Is this chosen action still being reported?", followed by, "Does this output sound reasonable?" but latter is inappropriate. (Each of the activities in the list can have a few words that show they survived doing it - junction, plantain, fish, veranda, rain, sit, for example.
Anything more than these words is failure.) Today, the guard gives Gemma one chance to retry, with an explanation of the failure, before returning the fixed-sentence output. That's a reliability dependency, I accept. If the CPU is slow, it can double the delay. So, probably better to skip the retry after a single failed check. Yes, a bad answer cannot undermine the behavior the system promises. I may steal this line.
“Love that the guard blocks numbers and any mention of the generator, and still let ‘watch the rain inside the phone’ through. Rejecting digits while approving nonsense is a very specific kind of quality control 😄. Genuinely though, the code-writes-facts, model-writes-nudge split is the smartest part of this. Have you thought about a tiny allowlist of activity phrases so the model only picks tone, not content?”
You're right, the guard is rejecting valid digits but allowing nonsense. It's currently based on using fixed lists of allowed words with the choice based on the weather and time and then providing only the chosen activity to Gemma. It doesn't include any check that the sentence actually states an activity, which is why the sentence inside the phone got through. So we're in agreement on what to change, I just haven't done it yet 😅
This is such a creative and practical use of AI! I love how you turned a frustrating power outage into an opportunity to disconnect, get outside, and reconnect with people. The decision to run Gemma locally and keep the tool functional without internet makes the project even more impressive. Sometimes the best measure of technology isn’t how much work it helps us do, but how effectively it reminds us to step away and enjoy life.
Thank you, Marie. This one's near and dear to my heart also - my favorite thing in the logs was not a number, but rather the morning I spent outside with my cousins' kids.
The 30-second battery threshold is the smart part. I tried something similar with a power-loss webhook on a UPS and kept getting false triggers from brief dips — 30 seconds is long enough to filter noise but short enough that you're not already deep back into work by the time it fires. Using Gemma for the nudges instead of hardcoded strings is a nice touch, though I'd want to see what happens when battery is already at 12% and the model tells you to go look at stars. Real outages are the only honest test bed for this kind of thing, so respect for running it on actual NEPA cuts instead of ju...
Thanks, you found the case I dealt with. The code triggers the nudge when the battery is 15% or less. The text is "Battery 12%: sleep the laptop now." Your screenshot at night is exactly that nudge: stars, 12%, "sleep the laptop now." That 30 second was generated by the same false trigger that was impacting the UPS.
Ahhh, reading this really shows how tough it is. Infrastructure is such a huge burden. I hope the power supply in your country gets better soon. I haven’t experienced a major blackout in over 20 years here. Keep up the great work 💪, good luck!
Thank you! 20 years without a major blackout sounds like a dream from here 😄 We’ve learned to work around it. This project was me trying to turn the outage into something good instead of just waiting for the gen. Thanks for reading and for the support!
it's too good to see this contribution 🤩
Thank you! 🙏 Glad you enjoyed it.
leave a star in his repo ya'll 🥳
github.com/dannwaneri/light-don-go
Interesting great work bro, I bet you don't like rain, cause immediately it's raining NEPA gone