DEV Community

Cover image for Tendril: a garden assistant you barely have to open

Tendril: a garden assistant you barely have to open

Archana on October 08, 2026

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass. What I Built My balcony has a little garden: t...
Collapse
 
yahhi profile image
Valentina Koniukhova •

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?

Collapse
 
chanadev profile image
Archana •

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.

Collapse
 
yahhi profile image
Valentina Koniukhova •

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

Thread Thread
 
chanadev profile image
Archana •

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!

Thread Thread
 
yahhi profile image
Valentina Koniukhova •

Yes. Seems like I have to continue working on the idea because of this :)
Do you have plans for future development of the app?

Thread Thread
 
chanadev profile image
Archana •

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.

Collapse
 
merbayerp profile image
Mustafa ERBAY •

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.

Collapse
 
chanadev profile image
Archana •

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:

  • The rules now have an explicit order: treatment first, then skip watering, then shade, then deep watering. A model task that covers a rule takes that rule's slot, so it can't get cut.
  • Voice notes only tick off the same kind of job, so "loosened the soil" no longer ticks "Check soil moisture".
  • If Tendril can't tell which plant has pests, it asks instead of treating the wrong one.

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!

Collapse
 
merbayerp profile image
Mustafa ERBAY •

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! ☕🌱

Thread Thread
 
chanadev profile image
Archana • • Edited

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

Thread Thread
 
merbayerp profile image
Mustafa ERBAY •

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. 😂🌺

Collapse
 
alexcodebytes profile image
Oleksandr •

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.

Collapse
 
chanadev profile image
Archana •

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!

Collapse
 
koda2026 profile image
Harun - solo dev •

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! 🐯🌱

Collapse
 
chanadev profile image
Archana •

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! 🌱

Collapse
 
koda2026 profile image
Harun - solo dev •

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! 🌱🐯

Thread Thread
 
chanadev profile image
Archana •

Haha, thank you so much, Harun! glad the behind-the-scenes part was useful. happy building to you too 🌱

Collapse
 
kartik-nvjk profile image
Kartik N V J K •

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?

Collapse
 
chanadev profile image
Archana •

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 :)

Collapse
 
ritik_verma_a6c3b5117cfd5 profile image
Ritik Verma •

Loved this project!

Collapse
 
chanadev profile image
Archana •

Thank you so much, Ritik! :)

Collapse
 
ai_adam profile image
ai_adam •

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:

  • Local-first sync: garden data nhẹ, offline-first trên device rồi sync khi có network tránh mất dữ liệu khi ra vườn không sóng
  • Sensor fusion: kết hợp weather API + device sensors (light, pressure) + user log để giảm false positive notification
  • Seasonal model: không hardcode rule, mà learn từ lịch sử user + region để adjust threshold theo mùa

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