This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass.
What I Built
My balcony has a little garden: two Indian hibiscus, a money plant, jasmine, a blue aprajita, mint, tulsi and sadabahaar. All in pots. I water them every morning and every evening, without fail.
But a routine doesn't look at the sky. It waters the plants an hour before a downpour. It doesn't know a 38°C afternoon is coming. And it definitely doesn't go looking under leaves for aphids.
I didn't want another app to spend time in, though. The whole point of a garden is to not be on your phone. So I built Tendril, a garden assistant you barely have to open :)
- It plans the week from the weather. Every Sunday at 7 AM it reads the forecast, and a small open model running on my own machine writes a plan: at most two tiny tasks a day, each under six words.
- It checks the sky every morning. If rain shows up overnight, or a heat spike, it quietly rewrites just today's tasks.
- The plan is one picture. It's made to fit a phone's lock screen, with the top third left empty for the clock. One glance and you're back outside.
- Tell it what you did. Tap Done, or hold the mic and say something like "Watered the chillies, there are aphids under the tulsi leaves." It ticks off the watering, and since aphids are serious, it re-plans the rest of the week with neem oil first. It's set up for English and Hindi.
- It only nudges once. After 6 PM, if something's still pending, the page shows one gentle line. That's it. No streaks, no guilt.
- No accounts. Set your city, add your plants, scan a QR code, add it to your home screen. Done.
An app that wants you to close it as fast as you can sounds a bit odd, I know. But that's the whole idea :)
Demo
Searching for your city
Adding a plant
The morning plan
Telling Tendril what you did
Code
GitHub: https://github.com/pyarchana/tendril
How I Built It
Nothing fancy, and that's on purpose. Everything that thinks runs on the same machine:
- Python and FastAPI for the app, with SQLite for the garden's memory.
- Qwen 2.5 (3B), an open-weight model, running locally through Ollama. It writes the weekly plan and reads my notes.
- faster-whisper, an open speech model, turning voice notes into text right on the CPU.
- Open-Meteo for the forecast and city search. Free, and no API key.
- Pillow to draw the plan picture. Every icon in it, the sun, the clouds, the little leaf, is drawn in code.
- Docker Compose to run it all with one command.
A small model with big habits
A 3B model is tiny. That's what lets it run on a laptop, but it comes with some habits. My first real run taught me more than all my tests did:
- It copied my instructions into its answers. I asked for a short reason with each task, and got back things like "At most 2 tasks per day; fewer is fine on quiet days." Thanks, very helpful :)
- Then it copied my example onto every day. I showed it one sample day with "Water deeply at the roots". Next plan: deep watering, seven days in a row, for both plants.
- It was slow. The first plan took 242 seconds.
The fix wasn't a bigger model. It was splitting the work. The model handles what it's good at: varied, plant-specific ideas, and making sense of messy notes. Plain code handles what it can't be trusted with. The weather rules (skip watering above 60% rain, shade above 35°C, a deep soak after three dry days) are applied last, over whatever the model said. Repeats get trimmed. Copied reasons get swapped for a real weather note.
Asking for compact JSON helped with speed too. All that indentation was costing about a third of the tokens. A week plan now takes about 100 seconds, which is fine for something that runs quietly at 7 AM.
Keeping the model honest
Every plan has to come back as proper JSON: at most two tasks a day, actions under six words, only plants I actually own, and every date filled in. The server checks each reply with Pydantic. If something's off, it asks once more and says exactly what was wrong ("unknown plant 'Rose'"). If that fails too, a simple rule-based planner steps in. So a plan always shows up, model or no model.
Voice notes got a small safety net as well. In one test the model filed "I watered the chillies… aphids on the tulsi" entirely under tulsi and forgot the watering. Now a tiny keyword check runs alongside it and catches the obvious things like "watered", matched to the plant named in the same sentence.
Making it feel like a lock screen
I wanted the plan to feel calm, like a wallpaper rather than an app. Dark forest green, two big cards, a weather pill that says something like "29° · Rain at 4 PM", and a strip of the next six days at the bottom. Headings are in Bricolage Grotesque, everything else in Figtree.
Why Does Open Innovation Matter?
- My garden stays at home. My location, my voice and my plants' history live in one small file on a machine I control. Voice notes are turned into text on that same machine. The only thing that leaves is the garden's coordinates, to fetch the weather.
- It costs nothing to run every day. A planner that wakes up every morning, forever, is exactly the kind of thing where paying per request adds up. With an open model, there's no bill that grows.
- I could see the model's mistakes, and work around them. Every odd habit above showed up in the raw output, and I could fix it on my side the same day. No waiting on anyone.
-
Swapping models is one line. If a better small model comes out next month, I change
OLLAMA_MODELand pull it. Everything else stays.
A bigger closed model might have written prettier reasons. But a small open one that lives on my own machine, costs nothing and does the job is more than enough for a few pots on a balcony.
Trying It on My Balcony
The first plan for my balcony landed on a rainy week. The forecast showed rain on four days, with up to a 94% chance on one of them. So Tendril's advice for most of the week was simply: skip watering today.
For someone who waters twice a day no matter what, that was the most useful thing it could say :)
It also left the watering alone on the dry days, because my notes already say I water every morning and evening. It suggested pest checks and pinching off yellow leaves instead.
Turns out the best thing a garden assistant can say is sometimes "don't". 🌱




Top comments (23)
This is such an inspiring example of "as small as it needs to be, and no smaller". Two tasks, six words, on the lock screen. And after publishing it costs almost nothing, for you or for the user. That's the part I keep thinking about.
"An app that wants you to close it as fast as you can" made me smile. Most of us build the opposite.
I recognized your model/code split too. I ended up with the same rule in my own small on-device project: the model brings the variety, deterministic code decides what's true. It's reassuring to see it work for a 3B model as well.
And "sometimes the best thing a garden assistant can say is 'don't'" is a great line about product design in general.
Curious: how does the wallpaper reach the phone every morning? Is it a Shortcuts automation?
Thank you, Valentina! "Most of us build the opposite" made me smile too :) And it's lovely to hear you ended up with the same split in your own project, model for variety, code for what's true.
Good question! The plan doesn't need the app open to be seen. Every morning Tendril makes a lock-screen version of the plan, shaped for phones and always at the same private link. So any phone automation can grab it after 7 and set it as the wallpaper: Shortcuts on an iPhone, or an automation app on Android. The setup page has a copy button for the link and the steps: github.com/pyarchana/tendril#your-...
If you try it with Shortcuts, I'd love to hear how it goes!
And what's the on-device project you mentioned? I'd love to see it.
My project dev.to/yahhi/mi-barrio-my-kids-pra...
And it wasn’t so successful as yours. It needs much more work to setup reliable listening. Now it is difficult to make it understand some words from Argentinian Spanish
I loved reading this! Doña Rosa and the medialunas 😄 And "a child should never lose a star because a model had a bad moment" is exactly the same idea as Tendril's rules, just with much cuter stakes :)
Honestly, it sounds like it worked where it mattered: your kids finished all three shops and immediately asked for more. That's the best review an app can get!
Yes. Seems like I have to continue working on the idea because of this :)
Do you have plans for future development of the app?
Yes, a lot actually :) Tendril is still small, and I have quite a few features planned for it. The comments here have already shaped it too. A few fixes went in this week just because people read the code and spotted things.
Nice to hear the comments turned into real fixes. Reading your code is the best review a small project can get.
I came back to this one and spent some time reading through the actual repository, because the article made me curious enough to see how the idea translated into code. 😄
And honestly, I like the project even more after reading it.
What stood out to me wasn’t Qwen, Ollama, Whisper, or even the local-first approach. It was the amount of effort you’ve put into making the model not be the authority.
ask_validated() gives it two chances and then gets out of the way. enforce_rules() puts deterministic weather logic back on top. _limit_repeats() deals with the little model getting repetitive. _replace_copied_reasons() even catches it copying the example back at you.
That last one made me laugh a little. We’ve apparently reached the stage of AI engineering where part of our job is politely telling the model, “yes, I know I wrote that, please stop quoting me.” 😂
I also really liked the small details around failure. Keeping completed tasks during a re-plan, doing the re-plan in a separate DB session after a serious check-in, pre-rendering the morning image, and falling back to a completely rule-based plan all fit the philosophy of the project very well.
While reading, though, I found a few corners I’d be tempted to experiment with.
One is _merge() in rules.py. Since a model task can mark a rule kind as already “covered”, and the final list is capped at two tasks, I wonder if there are combinations where a deterministic task could lose its place. A day with a recent health issue + heat + a dry streak would be an interesting test case. I think an explicit priority between health, weather and routine tasks might make that contract even stronger.
Another interesting one is matches_task() in the check-in path. The content-word intersection is beautifully simple, but simplicity can occasionally be a little too enthusiastic 😄. Two actions sharing something like “soil” could potentially be considered equivalent even when the gardener did something different. A tiny action taxonomy might eventually be safer than matching free-form action text.
And health_tasks() made me stop for a second too: when the plant from a check-in can’t be matched, falling back to the thirstiest plant keeps the planner moving, but for treatment advice I might prefer “unknown plant” over confidently treating the wrong one.
None of these made me think “this architecture is wrong.” Quite the opposite. They are the kind of edge cases that become visible because the architecture is small enough to actually reason about.
That’s probably my favourite thing about Tendril.
There isn’t an agent framework trying to negotiate with another agent framework while seventeen abstractions discuss whether the hibiscus needs water. 😄
It’s a small model, a database, some deterministic rules, validation, and a fallback.
And when the AI has a bad day, the plants still get a plan.
That’s a very good property for software to have. 🌱
Really thoughtful project, Archana. Thanks for making the repository open too — reading the implementation was even more interesting than reading about it.
Mustafa, thank you for actually reading the code! That's the best kind of feedback :)
You were right on all three, and the first one was sneakier than it looked: if the model wrote its own shade task, the rule stepped aside for it, and then the two-task limit cut the model's version. So on a 38°C day with pests and a dry spell, there was no shade advice at all 😅
I fixed all three:
Each one now has a test with your exact case: github.com/pyarchana/tendril/commi...
And "politely telling the model, please stop quoting me" is going on a sticky note 😂 Thanks again!
Archana, I actually went through the commit, and now I’m smiling at my screen. 😂🌱
I expected a few small adjustments. Instead, I found changes across eight files, a proper action taxonomy, explicit rule priorities, and regression tests covering the exact scenarios we discussed.
You really didn’t waste any time! 😄
I particularly liked how you handled _merge(). Letting a model-generated task occupy the corresponding rule’s priority slot is a much cleaner solution than simply removing duplicates.
And the check-in changes are thoughtful too. Distinguishing between checking soil and loosening it sounds obvious until you remember that an LLM can confidently treat them as the same job because both contain the word “soil.” 😂
My favourite change, though, is the unknown-plant handling.
Instead of confidently prescribing treatment for the wrong plant, Tendril now essentially says:
“Okay, we have a problem. But before I start spraying innocent plants, could you tell me which one?” 🌿😂
That’s exactly the kind of restraint I like seeing in software.
One small thing I noticed: _SAME_JOB still treats regular watering and deep watering as equivalent. That seems like a reasonable simplification for now, although it might be worth distinguishing them someday if watering depth becomes important.
But that’s a thought for another day, not another homework assignment. I promise! 😂
And seriously, thank you for taking the feedback so constructively.
I only pointed out a few possible edge cases. You did the actual engineering, found the real failure paths, designed the fixes, and added the tests.
That’s your work, and you deserve the credit.
This little exchange is a lovely reminder of why open source is worth the effort.
Sometimes all it takes is one curious person reading the code and another person willing to listen.
And somewhere out there, a hibiscus just got better software support than half the enterprise applications I’ve encountered. 🤣🌺
Keep building, Archana. I’m genuinely looking forward to seeing where Tendril goes next! ☕🌱
Haha Mustafa, "before I start spraying innocent plants" made my day 😂🌿
And yes, that one I let slide on purpose: most people just say "I watered it", so I'd rather tick off the deep watering than make someone repeat themselves in exactly the right words. But you're right that watering depth might matter someday, so I'll keep it in mind (not as homework, promise 😄).
Thank you for reading the code so carefully. It genuinely made Tendril better, and the hibiscus is very happy with its enterprise-grade support 🌺
Oh, and one more thing: if you ever feel like it, contributions are very welcome! The repo is open and the tests run with one command, so the watering-depth idea (or anything else you spotted) would make a lovely PR. No pressure at all, just wanted you to know the door's open 🌱
github.com/pyarchana/tendril
Oh no. You should not have told me the tests run with one command. 😂
That’s dangerously close to an invitation for me to clone the repo again and turn “I’ll just look at this for five minutes” into an evening of reading garden software. 🌱
Seriously though, thank you for the invitation. I might actually take you up on it.
And I agree with your decision on deep watering. From the user’s perspective, interpreting “I watered it” as completing the watering task is probably the right UX today. Making the taxonomy technically perfect at the cost of making people talk to their garden assistant like they’re filling out a government form would be worse. 😄
If I do send something, I’ll try to find a contribution that fits Tendril’s philosophy rather than adding complexity just because we can.
The dangerous part is that now I know PRs are welcome.
Your hibiscus may eventually receive unsolicited infrastructure improvements. 😂🌺
Running a small open model locally to compile weather API data into a simple image asset is the ultimate tech-bro flex. You get automated planning without sending your balcony's data to a cloud database or paying for a bloated subscription.
Haha, not sure my hibiscus sees it as a flex :) but yes, that was the whole idea...no subscription, and my balcony's data stays at home. thanks for reading tho!
archana, this is a masterclass in both product philosophy and practical ai engineering. "an app that wants you to close it as fast as you can" is a brilliant design principle.
but the real gem here is your realization: "the fix wasn't a bigger model. it was splitting the work." this is the exact truth of building reliable ai. instead of burning tokens (or crashing a mobile browser) trying to force a 3b model to be perfectly deterministic, you let it do what it's good at (generating varied ideas) and use plain, deterministic code to enforce the rules (trimming repeats, validating json, applying weather logic).
that pydantic validation with a rule-based fallback is exactly how you build resilient systems under constraint. whether it's a local 3b model or a lightweight api call from a $150 phone, graceful degradation is the name of the game.
also, "the best thing a garden assistant can say is sometimes 'don't'" is a fantastic takeaway. beautiful build, and thanks for sharing the raw lessons from your first real run! 🐯🌱
Thank you, Harun! That line took a few very broken plans to learn :) I really wanted the 3B model to just behave, and everything got easier once I stopped asking it to and let plain code handle the rules. A plan showing up even when the model has a bad day mattered more to me than a perfect one. Glad the "don't" line landed! 🌱
archana, "a plan showing up even when the model has a bad day mattered more to me than a perfect one" is the most profound engineering truth i've heard all week.
graceful degradation and deterministic fallbacks will always beat brittle, forced perfection. it’s exactly the mindset needed to build reliable systems, especially under constraint.
thanks for sharing the real, behind-the-scenes reality of building it, and for the constant inspiration. happy gardening! 🌱🐯
Haha, thank you so much, Harun! glad the behind-the-scenes part was useful. happy building to you too 🌱
The constraint I like most is the six-word task cap with at most two a day, since most assistant projects fail by generating a wall of suggestions nobody acts on. Curious how you handle a bad forecast: if the overnight rain call is wrong, does the re-plan trigger on the actual morning weather or just the forecast it saw at 7AM?
Thank you, Kartik! Yes, that was the whole reason for the cap. Two tiny tasks actually get done, a list of ten doesn’t :)
Good question! Tendril makes the week’s plan on Sunday. Then at 7 every morning it pulls a fresh forecast and compares today with what the plan expected. If the rain has disappeared (or shown up, or there’s a heat spike), it rewrites just today’s tasks. So if the overnight rain call is gone by morning, “skip watering” goes away too.
But it’s still the forecast, not the actual weather, and it only checks once. If the 7 AM forecast says rain at 4 PM and it never comes, Tendril won’t notice until the next morning. Honestly, that’s a gap right now, and a second look later in the day would be a good fix :)
Loved this project!
Thank you so much, Ritik! :)
Thấy approach "barely have to open" rất thú vị — thay vì kéo user vào app, nó đẩy insight đến nơi user đã ở (notification, widget, watch complication). Với garden care thì timing quan trọng hơn engagement: biết lúc cần tưới/bón/che nắng đúng giờ tốt hơn là mở app check hàng ngày.
Một vài góc kỹ thuật mình hay thấy team bỏ qua ở loại app này:
Hacktoberfest contribution idea: PR thêm "plant personality" — mỗi loài có voice/notification style riêng (cây xương rồng: "chill, đừng tưới nhiều", lan: "tôi khát nước nè 🌿") — vừa vui vừa giảm notification fatigue.
Curious: backend dùng gì cho scheduling? Cron job hay event-driven? — found it via LabAgent, site: labagent .tech