Open touch-grass-murex.vercel.app. On the same Wi-Fi, node server.mjs --lan lets a phone open the card at the door.
I used it on October 13 for a 25-minute garden card in Phoenix. The card said to plant tomatoes, peppers, lettuce, peas, garlic, and brassicas into cool soil, water deeply, and shade the seedlings. It mentioned palo verde still holding a yellow cast, Gambel’s quail near wash vegetation, and soil finally cool enough to work in the morning. The frost line was 63 days until the average first fall frost, December 15. That is the start of the desert vegetable year, not the end of it.
Code
Touch Grass
A field station for the next half hour. It writes one card — a garden job, a walk, or a sit — and then the phone is supposed to go in a pocket.
The card is not a feed. You read it at the door. Pocket mode is a clock and one sentence. When you come back, you keep a single line about what you noticed. That line stays in data/journal.json on this computer.
Why open
The sentence on the card is written by Qwen2.5 1.5B Instruct, an open-weight model (Apache 2.0), running in llama.cpp (MIT) on the laptop. There is no model API in the loop.
That matters for this, specifically:
-
It still works with the network unplugged, after the one-time download.
llama-serveris started with--offline. The page loads from this machine. Frost math, sunset, and the journal never leave it. - Place and…
https://github.com/sonawanelalit574/Touch-Grass
The Qwen weights and the llama.cpp binaries are not in the repo. setup.ps1 downloads them, about 1 GB for the model.
How I Built It
The sentence on the card is written by Qwen2.5 1.5B Instruct, an open-weight model under Apache 2.0, running in llama.cpp (MIT) on the laptop. server.mjs starts llama-server with --offline, a 4096-token context, and the GPU layers forced off, because this machine’s GPU is a 2 GB GeForce MX330. Inference is CPU-only. A card comes back in roughly 10 to 30 seconds once the weights are warm.
The model does not invent the season. lib/facts.mjs builds a brief from bundled climatic averages, a daylight calculation, and a month-by-month phenology file for six climates: continental, subtropical, desert, mediterranean, marine, and tropical. The brief says what may be sown, what must not be sown, and which birds, leaves, and soil cues the card is allowed to mention. Mushrooms in those notes are for looking, not eating. Walks stay on public paths.
Qwen only phrases that brief, and it has to fit a constraint if you wrote one. If the draft is too thin, names the city instead of the job, or turns a walk into a planting list, the station says so and writes the card from the frost calendar instead. The card is labeled either way: “Written on this machine · Qwen2.5 1.5B,” or “Written from the local frost calendar.”
The voice is prompts/ranger.txt. There is an editor on the page. The next card uses the file you saved. To swap models, drop another .gguf in models/, set TOUCH_GRASS_MODEL, and restart.
Why Does Open Innovation Matter?
A closed garden chatbot is a bad fit for this, for three concrete reasons.
It would need the network at the moment you are trying to leave the house. After the weights are on disk, Touch Grass does not. The page, the frost math, the sunset, and the journal all stay on the machine. llama-server is started with --offline. An outing costs nothing.
It would also want the note. “Balcony pots only, and it is windy” is a small fact about a home. I did not want that, or a list of what I noticed on the walk, sitting on a server I do not control. The journal is a JSON file in the project folder.
And a general model flattens the calendar. Ask one what to plant in October and it often says wait for spring. That is wrong in Phoenix, where the vegetable year starts as the soil cools, and wrong in Minneapolis, where the average first frost has already passed and the job is garlic if the soil still crumbles, not tomatoes. Those two briefs are calculated in lib/facts.mjs before the model speaks. I can read them, edit the ranger’s voice, and replace the weights. None of that is a setting inside someone else’s prompt.
My Agent Session
I built this in a Cursor agent session on the laptop. I do not have a DevRelay recording to embed. The repo is the record: the frost checks in scripts/check-facts.mjs, the voice file, and the server that refuses to call out once the model is loaded.
Prize Categories
I am not entering a partner category. The project runs locally on llama.cpp and Qwen2.5, with no hosted runtime, fine-tune, or partner API.
Top comments (0)