Some people plant four lettuces and water them whenever they remember, and then there’s Javier de la Torre, founder of CARTO, who plants four lettuces and ends up building a lakehouse, eight moisture probes, a camera, a weather station and an AI agent that decides how much to water every morning. He tells the story in I gave an agent a lakehouse and control of my garden, and admits the project was already “approaching the €60-lettuce phase”.
Beyond the fun (and it is fun), it’s one of the most complete and honest examples I’ve read of an agent with its hands in the physical world: real data, a real actuator, and the real problems that show up when you put all that together.
The setup
The garden is a raised bed of about two square metres with broccoli, cabbage, lettuce and spinach, plus the lawn around it. To measure it he uses Ecowitt sensors (about €300): eight soil moisture probes spread across the bed and the lawn, a weather station, a leaf wetness sensor and the gateway itself. Add a Reolink camera pointed at the bed and a Rain Bird irrigation controller with two zones, the lawn sprinklers and the bed’s drip line.
Everything ends up in LakehouseBox, a lakehouse on Apache Iceberg tables in Parquet, hosted on European infrastructure:
flowchart LR
S["Ecowitt sensors · every 60 s"] -->|HTTP POST| L[("Lakehouse · Iceberg")]
C["Reolink camera"] -->|FTPS| L
M["Open-Meteo · AEMET"] -->|scheduled DuckDB| L
L --> A["Agent · 08:45"]
A -->|email| J["Javier"]
A -->|minimal API| P["Old Android · Rain Bird app"]
P --> R["Irrigation"]I really like one of his lessons: devices are simple and you have to take them as they come. The gateway does an HTTP POST every minute in its own format, the camera uploads photos over FTP like it’s twenty years ago, and instead of forcing them to speak something modern, the platform adapts to them. The weather forecast comes from a DuckDB query scheduled at 5:30 against Open-Meteo, and data from an official AEMET station serves as reference. Since September 18 it has collected over 4 million readings in about 62 MB across seven tables (the photos do weigh something: about 3 GB). Parquet and Iceberg doing what they do best, something I covered with Iceberg v3 and geospatial data.
The agent
Every morning at 8:45, right after watering, an agent checks the history and the forecast, decides whether anything needs changing and sends an email with four blocks: what happened, what changes, why, and what caught its attention. “I don’t want a report. I want a message,” says Javier. It has tools to run SQL against the catalogue, persistent memory and its own decision log (agent_log), where it records what it decided, what it expects to happen and when to check back.
The prompt works as a job description, and that’s where the watering limits live:
| Limit | Value |
|---|---|
| Changes per zone and run | 1 at most |
| Change per adjustment | 20% at most |
| Minutes per zone | between 1 and 15 |
| Turning watering off | only with 5 mm or more of rain forecast in 24 h |
And the actuator is the part that made me laugh the most: the Rain Bird controller uses Bluetooth with its own protocol, so the solution was an old Android phone next to the valve that drives the official app the way a person would: open the schedule, change the minutes, save and wait for it to sync. Javier calls it “absurd”, and it is, but it works. The agent uses it through a minimal API with a key it never gets to see.
What worked (and what didn’t)
The interesting part is that the agent finds things. It noticed a corner of the bed was dry (21%) and asked for extra drip there, saw that one lawn zone dried out faster than the rest and, when an old note in its memory had the wrong watering time, trusted the data over the note. It also warns about low batteries in three sensors… and has been doing so for 29 days. In its log it now writes in capitals that the frost date is getting closer. The batteries, of course, are still unchanged (and I recognise myself quite a bit there).
What it didn’t see was the caterpillars. The holes in the leaves were a few pixels in the camera’s frame, and only showed up in close-ups taken with a phone. What it did catch was the neighbour’s cat using the bed as a toilet at night.
Then there are the open problems, which Javier doesn’t hide: the gateway doesn’t retry uploads, so a minute without Internet is a gap in the data, and the tunnel to the phone goes through a service outside Europe.
Limits belong in the valve
Of the whole article, the sentence I keep is this one: limits belong in the thing that opens the valve, not in the instructions of the thing that decides. Today those limits live in the prompt, and Javier himself lists it as the next thing he wants to change, with a “deliberately boring” tool (zone, minutes, stop) that the agent can’t get around.
It’s exactly what I mean when I say AI is an intern: you give it clear tasks and supervise it, but you don’t hand over the keys to the machine room just because you asked nicely in its instructions. A prompt is a request, not a guarantee. If the watering goes wrong in a vegetable bed, the cost is a drowned lettuce; in a production system, the equivalent can be quite a bit more expensive.
The rest of his conclusions also ring true to me: the agent replaces the classic IoT rules engine because it can weigh many variables at once and explain what it does, it needs history and not just the latest value, and open formats matter even more when the one reading the data is an agent. What used to take a data team now takes a few afternoons and a few prompts, and that opens this kind of project up to things as small as a raised bed. The code is on GitHub, in case anyone else wants their own €60 lettuce.




