This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
I live in a little suburban town, and for a long time I thought I lived in the most boring place in Canada. I thought had seen everything there was and that there was nothing new or interesting to do. But then a friend introduced me to Jet Lag (check out their youtube channel if you have time, they have some really good content) and more specifically their Hide and Seek Game, which is just normal hide and seek but in an entire city. After playing the game, trying to find the other team and researching spots to hide myself, I gained a deeper understanding of where I lived and just how many things I had missed.
I built Sidequest for that exact reason, it shouldn't take your friend convincing you to play a children's game to help you gain a deeper understanding of your surroundings. Sometimes all it takes is a short, silly little task, a sidequest.
Sidequest uses Gemma, location data from SerpAPI, and user inputs to generate personalized quests based on each user's interests and preferences. After completing a quest, users can give feedback that shapes future recommendations, creating a continuous personalization loop. I use Sentry to monitor errors and track failed generations, making it easier to identify and fix problems, while Render hosts the backend so the system can run without my computer.
Demo
Code
sidequest
Most days we walk straight past the little things - moss growing in a crack in the wall, the way the light hits the street late in the afternoon, a bird that's been singing outside the window all week, a shop sign we've never actually read Sidequest is a small nudge to go outside and notice them.
Sidequests are quick 5-30 minute things to do outside, right now - small low-friction prompts that get people out of the house and paying attention to what's already around them. They're not about going somewhere special; they're about seeing the ordinary places you pass every day a bit more closely. The app tailors each one to the user's interests, how much time they have, their energy and accessibility needs - and to where they actually are: the live weather, time of day and real places nearby are looked up from their location…
How I Built It
One rule I set from the beginning was that every Sidequest had to be doable the moment a user received it. No extra gear, no bookings, and nothing too far away. I wanted it to be a simple way to get outside and explore the place I live in.
I wanted Sidequest to use AI that anyone could inspect, run, and build on, so I chose open models:
- Gemma 4 (Google’s open-weight model) generates every quest.
- WordLlama (open-source embeddings) runs offline on my server and helps retrieve relevant quests.
My LLM client supports any OpenAI-compatible /chat/completions endpoint. I currently call Gemma through Google AI Studio, but switching to a self-hosted model through Ollama, vLLM, or llama.cpp only requires changing one line of configuration.
I started by building a simple proof of concept to test the personalization loop. It used a FastAPI backend and a Streamlit interface.
The first quests Gemma generated were pretty generic. They didn't always reflect the user's interests, and some didn't even involve going outside. I changed the pipeline so that every generation considers three things:
- User preferences: Interests, energy levels, difficulty, accessibility needs, crowd tolerance, and how far someone is willing to travel.
- Similar quests: The three closest examples from my quest library, retrieved using WordLlama embeddings and cosine similarity.
- Past experiences: The user's last 10 quests, grouped into Loved, Disliked, Rated OK, and Gave Up On. Quests a user disliked are treated as things to avoid repeating.
This helped make the quests more personal, but the locations were still a problem. Gemma would suggest visiting a park or walking along a trail without telling the user where to actually go.
I integrated SerpAPI to find specific places. Its Google Maps engine searches for parks, trails, landmarks, and viewpoints within the user's travel radius, prioritizing places that match their interests.
Each result includes its rating, review count, and opening hours. I filter out closed and indoor-only locations, combine the results with OpenStreetMap data, and rank the remaining options by distance and rating. Gemma then receives the exact place names and is instructed not to invent locations.
SerpAPI also converts typed locations into coordinates, so users can still use Sidequest without GPS. I cache results for 24 hours and limit each quest to two searches to keep the process reasonably fast and affordable.
I also added live weather data from Open-Meteo, air quality, and sunset times to the generation process. These act as constraints, preventing quests during thunderstorms, extreme heat, high UV, poor air quality, or after dark. If sunset is approaching, Gemma can suggest a quest based on watching it.
Once the generation loop was working, I started building the Flutter app. I wanted users to be able to complete quests and keep track of their experiences, rather than just generate a quest and forget about it.
The app includes step-by-step quests with timers, live routes displayed on an OpenStreetMap map, photo albums, and a journal where users can record what they noticed. I also added streaks, freezes, milestones, points, and badges to encourage people to keep using it. For friends, there's a group quest feature where a single Gemma call generates a quest for everyone.
I designed the app to work offline wherever possible because mobile signal isn't always reliable when exploring unfamiliar places.
As I added features, quest generation became slower and harder to debug. When a quest failed, I often couldn't tell which part of the process caused the problem. I integrated Sentry to make this easier to investigate.
-
Tracing: Each generation is recorded as a span, with individual Gemma attempts tracked as
gen_ai.chatspans. I can inspect latency, token counts, reasoning tokens, and the reasons quests were rejected. - Metrics: Tracking rejection reasons helped me identify which rules Gemma repeatedly ignored. I used that information to adjust the prompt so more quests would pass on the first attempt.
- Error tracking: Failed generations are grouped into issues with their attempt histories, making recurring problems easier to investigate.
-
Mobile monitoring: The Flutter app reports timeouts and server errors through
sentry_flutter.
Since prompts can contain user locations, I keep that information out of Sentry unless location logging is explicitly enabled.
The last step was deploying Sidequest so it could run without my laptop. I used Render, with a render.yaml Blueprint, health checks, environment variables for secrets, and deployments connected to GitHub.
Render's free tier doesn't include persistent disk storage, so I paired it with Litestream, an open-source tool that backs up SQLite databases to Backblaze B2. It streams database changes as they happen and restores the databases whenever the server starts. If restoration fails, the server won't start, which prevents it from running with empty databases instead of existing user data.
This means quest histories, photos, and streaks can survive server restarts and redeployments, including on a free instance.
After more than 200 backend tests, I had a working version of Sidequest that brought the main pieces together. Gemma generates personalized quests, SerpAPI finds real places to visit, Sentry helps me track down bugs, and Render hosts the backend.
While there are several improvements I could make, the core idea I wanted to implement was implemented. Users can get a quest, head outside, and explore somewhere nearby without much planning.
Why Does Open Innovation Matter?
Open innovation is a really important for Sidequest because I want other developers and communities to build on my work instead of starting from scratch.
Since the code is public and the system uses open models and tools, developers can replace Gemma, improve quest retrieval, or experiment with different ways of personalizing quests without rebuilding the entire app. For example, a school could fork Sidequest to help students explore local history, discover community spaces, or find activities after class. A city could adapt it to highlight lesser-known parks, local restaurants, public art, and walking trails, while a small town could use it to generate date night ideas and recommend nearby businesses. Smaller communities could even host their own versions on inexpensive servers using their own copies of Gemma.
Another benefit is that developers can improve the quest generation system for specific needs that a general-purpose app might overlook. Someone could build better accessibility filters for wheelchair users, for example, or change the generation logic to account for a student's limited lunch break. Those improvements could then be shared with other communities facing the same challenges to better everybody's experience with the product.
My Agent Session
Prize Categories
Best Use of Gemma
Best Use of SerpApi
Best Use of Render
Best Use of Sentry Agent Tracing
Top comments (0)