DEV Community

Aleksandra Fedak
Aleksandra Fedak

Posted on

I Built an AI That Wants You to Stop Using It — Meet SIDEQUEST 🌿

Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass Submission 🌿

An offline-first AI quest machine designed to become a physical device that prints real-world adventures.

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass

What I Built

I'm a software developer, so most of my day happens in front of a screen. And after work? More screens — learning new technologies, building side projects, or scrolling through social media.

Here's the funny part: throughout the summer, I was hiking in the Tatras almost every weekend, climbing mountains over 2,000 meters high. But as autumn arrived, I got so caught up in work and tech projects that some days I struggled to convince myself to even go for a short walk.

And most solutions to excessive screen time involve... another app.

So I started wondering: What if AI could help us disconnect instead of giving us another reason to stay online?

That's how SIDEQUEST was born.

Not another app. A future physical device.

SIDEQUEST is a software prototype for a physical, offline-first AI quest machine.

Imagine a small device on your desk with a tiny display, directional buttons, a big PRINT button, and a thermal receipt printer.

You choose your preferences, press PRINT, and a locally running AI model generates a personalized real-world adventure. A paper receipt comes out with three objectives.

Take the receipt. Leave the screen. Touch grass.

No phone needed during the quest. No notifications. No endless feed.

The current version is a browser-based simulation of this future device, built to test the AI quest engine and gameplay before developing the hardware.

How SIDEQUEST works

Choose a duration (15, 30, or 60 minutes), an environment (City, Park, Nature, or Anywhere), and a mode (Calm, Move, Explore, or Surprise).

Gemma 3 4B, running locally through Ollama, generates a quest with exactly three real-world objectives, displayed as a retro-style receipt.

After completing a quest, you can return to earn XP, unlock badges, and build a daily streak.

There's also Doomscroll Escape — a shortcut that generates a 15-minute surprise quest when you catch yourself scrolling without a purpose.

The gamification is deliberately lightweight: it should motivate you to go outside, not keep you interacting with the machine.

The best SIDEQUEST session is the one where you spend almost no time using SIDEQUEST.

Demo

Here's the working web prototype:

The demo shows the current browser-based prototype, including local AI quest generation and the receipt-printing animation. The long-term vision is to bring this experience to a standalone physical device.

Code

SIDEQUEST is open source:

GitHub logo fedachkaa / sidequest

AI-generated side quests for the world outside your screen.

🌿 SIDEQUEST

Print a quest. Leave the screen. Touch grass.

A screenless-first AI quest machine, designed for the physical world.

SIDEQUEST is the software prototype of a future physical AI-powered quest dispenser — a small desk device that generates personalized real-world adventures and prints them on thermal paper.

The vision is simple: press a button, receive a quest, and walk away from your screen. No endless feeds, no notifications, no app demanding your attention.

The current implementation is a browser-based simulation of that device, powered by a locally running open-weight AI model. It brings together the quest generation engine, receipt-style interface, and gamification system — with the long-term goal of moving the experience into dedicated hardware.

AI that gets you offline, not AI that keeps you online.

Built for the Hacktoberfest 2026 — Touch Grass Challenge.

Demo

sd-hq.mp4

Preview

SIDEQUEST Receipt Machine

Your next adventure, printed

Generated SIDEQUEST receipt

The idea

We have apps for…

The repository includes the backend, AI integration, frontend, persistent storage, automated tests, and local setup instructions.

You can run the current prototype on your own computer using Ollama and Gemma 3 4B, without a cloud AI API key.

How I Built It

SIDEQUEST uses FastAPI, Gemma 3 4B, Ollama, Pydantic, SQLite, and vanilla JavaScript.

SIDEQUEST architecture diagram showing the flow from quest preferences through FastAPI, Ollama with Gemma 3, and Pydantic validation to the generated quest receipt, with SQLite storing player progress.

The user selects their quest preferences, which are sent to a locally running Gemma 3 model through Ollama. The generated response is validated with Pydantic before being displayed as a printable-style receipt.

SQLite stores quests and player progress, while the frontend handles the receipt animation, XP, badges, and streaks.

The architecture is designed to evolve into a physical device with a small display, directional buttons, and a thermal printer. The long-term goal is to move AI inference onto the device itself.

Challenges and Lessons Learned

Challenge What happened Solution
LLM output validation Gemma generated a 12-minute quest instead of the requested 15 minutes. The response passed Pydantic validation but violated a business rule. Introduced QuestService to validate request-dependent constraints separately from schema validation.
Local inference latency Running Gemma 3 4B locally caused an httpx.ReadTimeout during testing. Added a configurable timeout to OllamaClient to accommodate slower local inference.

Why Does Open Innovation Matter?

Open innovation is central to SIDEQUEST because local AI is a requirement of the product vision, not just a choice of technology.

A device designed to help people disconnect shouldn't need to contact a cloud AI service every time someone wants to go for a walk.

With an open-weight model like Gemma, I can run inference on my own hardware, experiment with the generation pipeline, and explore what it would take to move that intelligence into a dedicated physical machine.

That changes what's possible for a small independent project.

Benefit Why it matters for SIDEQUEST
Cloud independence Gemma 3 runs locally through Ollama, without a hosted LLM API. The future device aims to work entirely offline.
Privacy Quest preferences and progress stay on local hardware, without being sent to an external AI provider.
Freedom to experiment Open-weight models allow me to explore quantization, smaller models, and edge AI hardware without depending on a cloud provider.

There are still challenges — especially hardware limitations, inference speed, and model optimization — but that's what makes building SIDEQUEST exciting.

We're surrounded by technology designed to capture our attention.

What if we built more technology designed to give it back?

My Agent Session

I used Codex as a coding assistant while building SIDEQUEST, focusing my own work on architecture, AI behavior, product decisions, and reviewing the implementation.

Here's a curated session showing part of the development process:

Building SIDEQUEST's Local Gemma Quest-Generation Boundary
You

We are implementing the quest generation milestone for SIDEQUEST.

Current state:
- FastAPI application exists
- QuestRequest and Quest Pydantic models exist
- Tests for the models exist and pass
- Ollama is installed locally
- gemma3:4b is installed and working
- Ollama API is available locally
- httpx is already a project dependency

Implement the first Ollama integration.

Requirements:

  1. Create an AI layer for communicating with Ollama.
  2. Use httpx directly — do not add the Ollama Python SDK.
  3. Use gemma3:4b as the default model.
  4. Keep Ollama-specific code separate from business logic.
  5. Add a prompt builder for generating SIDEQUEST quests.

The prompt must instruct the model to:
- return only structured JSON
- generate exactly 3 tasks
- respect the requested duration, environment, and mode
- use difficulty 1–3
- not require a phone
- not require purchases
- not require entering private property
- avoid dangerous activities
- not assume that specific landmarks, animals, facilities, trails,
bodies of water, or other environmental features exist
- make every task achievable in a typical environment of the selected type

  1. Parse and validate the LLM response using the existing Quest Pydantic model.
  2. Add appropriate tests. Mock Ollama in tests — tests must not require a running local model.
  3. Do NOT create the FastAPI generation endpoint yet.
  4. Do NOT add retries, fallback models, databases, auth, RAG, or other unrelated infrastructure yet.

Keep the implementation small and easy to understand.

Before changing files, briefly tell me:
- which files you plan to create/change
- the responsibility of each file

Then implement it.

Agent

Planned changes:

  • app/ai/__init__.py — marks the new AI integration package.
  • app/ai/prompts.py — builds the SIDEQUEST-specific prompt from a QuestRequest; it owns all generation and safety constraints.
  • app/ai/ollama_client.py — contains the Ollama HTTP boundary, defaults to gemma3:4b, calls /api/generate with httpx, extracts the generated JSON, and validates it as Quest.
  • tests/test_prompts.py — verifies requested inputs and every required constraint appear in the prompt.
  • tests/test_ollama_client.py — uses httpx.MockTransport, so it verifies the request and response validation without contacting a running Ollama instance.

I won’t change the FastAPI app or add an endpoint. I’ll make the client injectable enough for clean tests, while keeping its public API small.

Agent

The AI boundary and prompt builder are now in place. I’m running the full test suite next; I’ll also inspect the diff for accidental scope creep and formatting issues before wrapping up.

You

Important product context:

SIDEQUEST is not primarily a conventional web application.

It is a software prototype/reference implementation for a future
screenless physical device with physical controls and a thermal receipt
printer.

The intended interaction is:

select time/environment/mode
→ generate one mystery quest
→ print it as a physical receipt
→ take the receipt
→ leave the screen

The current browser version will simulate that physical device.

Keep this hardware-first architecture in mind:
- the LLM generates structured quest data, never presentation/HTML
- quest generation must not depend on the browser UI
- receipt rendering must eventually be independent from quest generation
- a future thermal-printer output adapter should be possible without
changing the AI layer
- avoid architecture that assumes SIDEQUEST will always be a web app

Do not implement printer support yet.

Agent

Understood. The implementation already keeps the AI layer transport- and presentation-agnostic: it accepts a typed request and returns a validated Quest, with no FastAPI, browser, HTML, or receipt concerns. That boundary can later feed either a browser receipt renderer or a thermal-printer adapter unchanged.

I’ll keep printer support out of this milestone and finish verification of the current change.

Agent

I isolated the stall to this sandbox’s Python event-loop startup (asyncio.run(asyncio.sleep(0)) itself hangs), not to the integration. Since this milestone does not require an async client, I’m simplifying the boundary to synchronous httpx.Client. FastAPI supports calling synchronous application code safely, and this avoids unnecessary async complexity in the first integration.

You

The real Ollama integration smoke test exposed an issue:

httpx.ReadTimeout: timed out

gemma3:4b runs locally on relatively modest hardware and inference can
take longer than httpx's default timeout.

Update OllamaClient to use an explicit configurable timeout suitable for
local LLM inference.

Requirements:
- default timeout: 60 seconds
- make it configurable through OllamaClient.init
- pass it to the real httpx.Client
- do not change the existing injected test client behavior
- add/update tests to verify the configured timeout where appropriate
- do not add retries or other error handling yet

Keep the change minimal.

Agent

I’ll make one focused production change in app/ai/ollama_client.py: add a timeout constructor argument defaulting to 60 seconds and pass it only when creating the real httpx.Client. I’ll update the client tests to verify both the default and an override, while leaving injected clients untouched.

Agent

Updated OllamaClient with:

  • Default timeout of 60 seconds.
  • Configurable timeout constructor argument.
  • Timeout passed only to the internally created httpx.Client.
  • No changes to injected test-client behavior.
  • Tests covering the default and a 120-second override.

Verification: 10 tests passed.

You

We found a real integration issue during testing.

QuestRequest requested:
duration_minutes=15

but Gemma returned:
duration_minutes=12

The response still passed Pydantic validation because Quest.duration_minutes
is intentionally an int. We want to keep it that way because Quest should not
be coupled to the currently supported UI duration options.

We now need validation for constraints that depend on both the QuestRequest
and the generated Quest.

Implement this as a small business-logic layer.

Requirements:

  1. Introduce a QuestService responsible for quest generation business logic.

  2. QuestService should:

    • accept a QuestRequest
    • use OllamaClient to generate a Quest
    • validate request-dependent constraints
    • return the validated Quest
  3. For now, the only request-dependent constraint is:
    quest.duration_minutes must exactly equal request.duration_minutes.

  4. If the generated duration does not match the requested duration,
    raise a clear domain-specific exception.

  5. Keep responsibilities separated:

    • OllamaClient = communication with Ollama + parsing/schema validation
    • PromptBuilder = prompt construction
    • QuestService = business rules involving request vs generated quest
    • Quest Pydantic model = structural validation
  6. Make OllamaClient injectable into QuestService so the service can be
    tested without running Ollama.

  7. Add tests for:

    • matching duration -> Quest is returned
    • mismatching duration -> domain exception is raised
  8. Do not:

    • change Quest.duration_minutes to Literal
    • add retries or automatic regeneration yet
    • add the FastAPI endpoint
    • add fallback models
    • change the prompt
    • add unrelated abstractions or infrastructure

Keep the implementation minimal.

Before changing files, briefly explain:
- which files you will create/change
- why QuestService is the appropriate layer for this validation

Then implement it.

Agent

I’ll create:

  • app/services/__init__.py — declares the business-service package.
  • app/services/quest_service.py — adds QuestService and a domain-specific QuestDurationMismatchError.
  • tests/test_quest_service.py — tests matching and mismatching generated durations using an injected mock OllamaClient.

No existing production files need changing. QuestService is the right layer because duration equality is not a structural property of a Quest; it is a business invariant that only exists when comparing generated output with the originating request. This keeps Pydantic reusable and Ollama transport-focused.

Agent

The service now owns the cross-object invariant and returns the exact validated Quest instance when it passes. I’m running the service tests alongside the existing model, prompt, and Ollama-client tests, then checking the diff for formatting problems.

Prize Categories

  • Best Use of Gemma — SIDEQUEST uses Gemma 3 4B for local AI-powered quest generation.

Top comments (0)