This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Most "get outside" software has a design problem: it asks you to stare at a screen so it can tell you to look up.
Grass Pass does the opposite. It's a small command-line tool that writes you a one-page field card for a walk and then gets out of the way. You give it your location, how many minutes you have and how you feel. It hands back a card with three to five steps (each with minutes), things to notice, what to bring, two questions to answer on paper afterwards, and a big Back by 17:28 box.
The card is a single HTML page with no network requests. Print it, or leave it open on your phone, then lock the screen and go. The tool's whole footprint on your day is the minute or two before you leave.
It's for people who sit at a laptop most of the day (me included) and whose plan to "go for a walk" dies at the question: where, and doing what?
Demo
This is a real run on my laptop at 4:43 pm in Vadodara, asking for 45 minutes. This particular card came from the built-in offline quest bank, which takes over when no model is running, and the last line of the card says so.
GRASS PASS #003 THE TIDY LOOP
Afternoon · post-monsoon · 45 min
Back by 17:28 (sunrise 06:31, sunset 18:16)
A short loop that leaves the place a little better than you found it.
[ ] 1. (14 min) Walk a loop near home carrying a bag.
[ ] 2. (18 min) Pick up litter that is safe to touch, aiming for ten pieces. Skip glass and anything sharp.
[ ] 3. (9 min) Sort what you found: what is it, and where does it collect?
[ ] 4. (4 min) Dispose of it properly and wash your hands.
Notice:
- Where litter gathers: corners, drains, fence lines
- The most common kind of rubbish
- Wildlife using the places people neglect
- Greening after the rains, migrant birds arriving, still warm at midday.
Bring: A bag, Gloves if you have them
Afterwards, on paper:
? How many pieces did you collect?
? What was the most common item?
Lock the screen. Put the phone away. The card is all you need.
(made by offline-bank)
With the model running, the same computed facts go into the prompt and Gemma 3 4B writes the card instead. My first real Gemma card (it took 136 seconds) included "Count the number of leaves on three different trees", "Touch a recently rained-on patch of earth" and "Find a patch of moss". I'm not going to pretend that card was perfect: counting the leaves isn't doable, and the rain and moss are things the model had no way to check. A 4B model can't see the weather. It only knows what the prompt tells it, and my season note said "greening after the rains". I come back to this in the open-innovation section.
Code
akashrey09
/
grasspass
Offline-first field cards that get you off the screen and outside. A local open-weight model (Gemma 3 via Ollama) writes the walk; plain code computes sunrise, sunset and your back-by time. Works with no signal. Python stdlib only. open-source-ai, gemma, ollama, local-llm, offline-first, hacktoberfest, python, outdoors, touch-grass
Python standard library only, so there's nothing to install beyond Ollama itself: clone it and run python -m grasspass quest. Fifty tests.
grasspass/
sun.py sunrise/sunset from the NOAA equations
context.py season, time of day, daylight-capped minutes
prompts.py what the model is asked
llm.py OpenAI-compatible client (Ollama, llama.cpp, LM Studio, vLLM)
card.py validation of anything that claims to be a card
fallback.py offline quest bank
generate.py model first, bank as the safety net
render.py HTML / Markdown / terminal
journal.py plain-file log
How I Built It
The open pieces. The model is Gemma 3 (4B), an open-weight model, running locally in Ollama. The client talks to /v1/chat/completions, the protocol that Ollama, llama.cpp's server, LM Studio and vLLM all expose, so changing the model or the whole runtime is two environment variables:
export GRASSPASS_MODEL=qwen3:8b
export GRASSPASS_BASE_URL=http://localhost:8080/v1
Model for ideas, code for facts. The first design decision was deciding what the model is not allowed to do. Small models are good company and bad arithmetic, and "when does the sun set?" is exactly the sentence where being confidently wrong means walking home in the dark. So sunrise and sunset come from the NOAA solar equations, about forty lines of standard-library maths. The season and time of day derive from those, and the Back by time is computed, never generated. Ask for two hours at 5:40 pm and the code shortens it before the model sees the request. The prompt then forbids the model from writing clock times at all. I checked the sun code against London's midsummer sunrise and sunset, the equator at the equinox, and Tromsø's polar day and night.
The model's reply is untrusted input. Small open models rarely follow a schema perfectly. Every reply goes through a validator that parses it, clamps lengths, rescales the step minutes to add up to the time available, and rejects any card whose steps tell you to use a phone, app, camera or search engine, which would defeat the whole point. The core loop:
for _ in range(retries + 1):
try:
raw = chat_json(cfg, system, user)
return validate(raw, ctx.minutes), f"model:{cfg.model}", notes
except LLMError as exc:
notes.append(str(exc))
if not exc.retryable: # server isn't running: don't wait around
break
except CardError as exc:
notes.append(f"model card rejected: {exc}")
card = validate(build_fallback(ctx, seed), ctx.minutes) # never raises
No model? Still a walk. Nine hand-written quests (a sound map, three trees, a golden-hour chase, a dawn chorus and so on) are the safety net. They're chosen by time of day and mood, and they pass the same validator. A stressed mood only gets the calm ones; after dark you only get the quiet, close-to-home ones. So the tool works on a machine that has never heard of Ollama.
What the real model run taught me. I built and tested the model path against a small fake server that speaks the same protocol, then ran it for real on my own laptop. Three things came out of that:
- The first card took 136 seconds. My default timeout was 120 seconds, so a plain run would have silently fallen back to the offline bank and I'd never have known the model was working. I only saw it because I'd raised the timeout by hand. The default is now 300 seconds, and the README says why: the first call has to load the model into memory.
- The validator had nothing to reject in that run. The card passed first time with no retries. That's one run, so I can't say much about the rejection rate.
-
Valid isn't the same as grounded. The validator checks structure and the no-devices rule. It can't know that moss is unlikely near Vadodara in October. Passing
--weatherhelps, but the real fix is better inputs, not a stricter filter.
Testing without a model. The fake server let me drive the client through good replies, chatty replies, malformed JSON, HTTP 500 and 404, a sneaky "open an app" step, and a refused connection. To check the tests had teeth, I deliberately broke the device-word filter and the night-time cap, and each break made tests fail.
Running the real CLI also caught a mistake in my own design. The first version of stats printed "80,000 seconds outside for every second on the screen." That was technically true and completely misleading, because it only counted generation time and ignored the time spent reading and logging. I replaced it with plain numbers.
Why Does Open Innovation Matter?
Four reasons, each tied to something in the build.
A trail has no signal. The design only works if making a card needs nothing but your own laptop. A hosted API ties the walk to a connection at exactly the moment you're about to leave one. With a local model, and an offline bank for when there's no model, the whole pipeline runs on a plane.
The inputs are personal. The prompt holds where you are, when you're leaving and how you feel, and the journal records when you went out and for how long. That's a diary of your movements and mood. Keeping it on your own machine isn't a feature I added; it's what falls out of there being no server.
You control the engine. The validator exists because models are swappable: a 4B model and an 8B model fail differently. With open weights and a standard protocol I can change the model, temperature, prompt and retry policy, and see exactly what each change does.
Retries are free. Rejecting a bad card and asking again costs a few seconds of electricity and nothing else. Against a metered API, a loop that treats the model as untrusted input is a budget decision. Locally it's a for loop.
Where closed would have been better. I'd expect a larger hosted model to write a more grounded card than my 4B model did, and to be far faster than 136 seconds on a laptop CPU, though I didn't test one. A 4B model is a modest author. I accepted that trade on purpose: a plainer card that works offline and keeps your location private is a better walking companion than a more elegant one that needs a signal. The validator and the offline bank are what make the plain-but-reliable choice viable.
Limits. Weather is whatever you pass in --weather; there's no live feed. Sun times ignore terrain and elevation, so the back-by time is a guide, not a guarantee. It writes ideas for walks; it doesn't identify species or judge whether a place is safe.
My Agent Session
I built Grass Pass with an AI coding agent (Claude) in one working session. I supplied the theme and the challenge rules; the agent proposed the architecture, wrote the code and tests, and ran them. I ran everything on my own machine, installed the model, and hit the timeout problem on the first run.
Prize Categories
- Best Use of Gemma: the project's model is Gemma 3 (4B), run locally through Ollama. It wrote the first real card I describe in the Demo section; the sample card shown there is the offline fallback.

Top comments (0)