This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Should I go out? is a web app that answers one question and then tells you to put your phone away.
You tap once. The app checks the weather, the air quality, and the parks around you, and Gemma, running on your own computer, suggests one outdoor plan that fits the time you have: a park or landmark, a round-trip route, a few things to do there, and what to wear. Then the screen tells you to put your phone in your pocket.
Apps that send you outside usually still want your attention, with feeds, streaks, and leaderboards. I wanted the screen to be the shortest part of the trip. You get one plan, and "Another place" if you don't like it. Goals and badges only move when you check in at the park with "I'm here", and the check-in compares your location in the browser without sending it anywhere.
On the first visit, four quick questions ask how you like to move, what you enjoy, who comes along, and whether you ride public bikes, or you can let the AI decide. Each suggestion also comes with a music video of about 30 seconds that flies over the real streets to the park in 3D. Once the app has loaded on your phone, check-ins work with no signal. It speaks English and Korean, because I live in Seoul.
It's for anyone who opens their phone on a free Saturday to find something to do and is still scrolling at sunset, and for parents who want a playground 20 minutes away on foot.
Demo
The model runs on your own Mac, so there's no public deployment. Here's the whole flow, from the questionnaire to the check-in:
And the walk preview it makes for every suggestion:
I took it outside
I'm a total homebody and rarely go out. While building the app, I tested it on my phone. It suggested 까치 어린이공원, a small park near home that I didn't even know was there.
I walked over, checked in, and got my first badge. Going somewhere because of an app I built myself felt refreshing.
One thing was off. After I checked in, it suggested "a quick game of soccer at the sports field", but the park has no sports field. The app had counted a field just outside the park as part of it, so I had the agent fix that in the afternoon: only what's inside a park's outline counts now (83df1d5). Using it on my phone had also turned up two bugs earlier, a "Walk there" button that didn't respond and a start point shown as a plus code. Session 6 below covers both.
Code
scs0209 / touch-grass-agent
Should I go out? One tap, one outdoor plan from a local Gemma, then put your phone away.
Should I go out?
One tap, one suggestion, then put your phone away.
It checks the weather, the air, and the parks near you, and Gemma, running on your own computer, suggests one outdoor plan that fits the time you have. Then it tells you to put your phone away.
- One plan, not a feed. A park or landmark, a few things to do there, a round-trip route that fits your time, and what to wear.
- Made for you. Four quick questions on your first visit, or let the AI decide. Don't like the place? Tap "Another place".
- Counts only when you go. "I'm here" checks you in at the place. Goals and badges move only when you actually go somewhere; there are no points and no leaderboard.
- Preview your walk. A music video of about 30 seconds that flies over the real streets to the park, to save or…
It's a pnpm monorepo with apps/server (Hono and Mastra on Node) and apps/web (Vite and React). The README has a short setup, and AGENTS.md lets a coding agent set everything up for you, including pulling the model.
How I Built It
| Piece | What it does here |
|---|---|
| Gemma 3 4B (open weights) | Picks the place, writes the reason and the things to do, picks the outfit, and translates the answer into Korean |
| Ollama (local inference) | Runs Gemma on my Mac through its OpenAI-compatible endpoint |
| Mastra (open-source agent framework) | The agent, and a four-step workflow around it |
| OpenStreetMap data via Nominatim, Overpass, and OSRM | Parks and landmarks, what each park has (playground, water, viewpoint), and real walking and cycling routes |
| Open-Meteo | Weather and air quality |
| MapLibre GL JS and OpenFreeMap | The 3D flyover in the walk preview |
All of these are free and need no API key.
What the server does and what Gemma does
My first version gave Gemma the parks and asked it to plan. It made up walking times, and the card disagreed with the map. Now the server handles everything that can be measured, and Gemma handles the parts that need judgment. Each request is a four-step Mastra workflow:
- Gather conditions from Open-Meteo, Nominatim, and Overpass, and measure the real round trip to every park with one OSRM request. Parks that don't fit your time are dropped.
- Ask Gemma. It sees only the parks that fit, each with its
roundTripMinand features, and answers in JSON: one park by id, a reason, things to do, and an outfit from a fixed catalog. It can't invent coordinates or clothes. - Check the answer. zod validates the shape. Rules drop things to do that name a facility the park doesn't have, and reasons that don't match the weather. If you said "with kids" and Gemma picked a park with no playground while another candidate has one, the server switches parks. If Gemma fails twice, simple rules answer instead.
- Plan the route. Step 1 already measured it, so it comes from the cache.
Korean answers come from a second Gemma call that translates the checked English answer, with place names masked so they come back unchanged.
Tracing with Sentry
With SENTRY_DSN set, each request is one Sentry trace: the four steps, the agent run, every Gemma call with its tokens, and every outgoing API request.
The public Overpass server is often slow, so the app waits at most 3 seconds for it and caches park features for a day. In this trace the route step takes 1 ms, because the route was measured during that wait.
Most of the wait is Gemma. Three requests on an M3 Pro MacBook, with the model already loaded:
| Input tokens | Output tokens | Time | |
|---|---|---|---|
| Gemma picks the place and writes the plan | 2,151 to 2,592 | 218 to 236 | 9.0 to 9.7 s |
| Gemma translates the answer into Korean | 523 | 73 | 3.2 s |
| The whole request | 12.6 to 15.2 s |
That's 75 to 80% of every request, and it costs $0. When Gemma returns invalid JSON, the retry shows up in the same step:
Why Does Open Innovation Matter?
This app knows where you are, when you go out, who you go with, and which parks you visit. With a closed model API, all of that would sit in someone else's logs. Here the prompt never leaves my Mac. The map and weather services get only the coordinates they need, Kakao Map gets the start and the destination when you tap directions in Korea, and check-ins stay in the browser.
Because it's free to run, I kept calls I'd probably have cut with a paid API: "Another place" asks Gemma again, every answer gets a second call for Korean, and broken JSON gets a retry.
The small local model shaped the design too: measured routes, a fixed outfit catalog, rule checks, and a fallback. Swapping models is one environment variable (OLLAMA_MODEL), and the same checks apply to any open model. There's no sign-up and no API key. Clone it, pull the model, and it works anywhere OpenStreetMap, Open-Meteo, and OSRM cover. The catch is that the Mac has to be awake, and the first answer takes 10 to 20 seconds while Gemma loads.
My Agent Session
I built this with Cursor agents, and Entire recorded the sessions and linked each one to its commit: 104 of the 126 commits carry an Entire-Checkpoint: trailer. The records are in a private repository because they also include my work setup.
DEV's parser doesn't read Cursor transcripts yet, so I converted the sessions to the Claude Code format, which is why they show a Claude Code icon. Sessions 1 to 5 come from Entire's records and session 6 from the Cursor chat. Cursor doesn't keep tool results, so you'll see my prompts, the agent's replies, and the commands it ran. My prompts were in Korean, so I added English translations, and I redacted local paths and account names.
1. "The card and the route show different times. Fix that."
The card and the map disagreed on the walk because Gemma was estimating the minutes. The agent moved route measurement in front of Gemma: OSRM measures every candidate's round trip, parks that don't fit are dropped, and Gemma gets each park's roundTripMin. After this I stopped asking a 4B model for numbers I could measure.
Commit: b94354b "keep only parks whose round trip fits"









