# Project export: Roominate

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Ever stuck procrastinating on important tasks? Roominate breaks down your tasks into smaller steps, and incentivizes completing them by allowing you to decorate a home just for you!
- Devpost: https://devpost.com/software/roominate-vp1aqu
- GitHub: https://github.com/XitoAliferis/calhacks12
- Video: https://www.youtube.com/embed/0LMzgVUCBIg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([MLH] Best .Tech Domain Name)
- Team: 5 GitHub contributor(s) — Ash (32 commits), xaliferi (22 commits), Ethan Pan (14 commits), Xristopher Aliferis (2 commits), aurorajxshen (2 commits)

## Devpost submission (written by the team)

### Inspiration

Studies have shown gamification has psychological benefits in one's ability to complete tasks. Your to-do's can feel daunting, and the "reward" feels too far away, especially for people with ADHD or chronic procrastination. We conducted research on spatial memory, discovering visualizing and attaching progress tracking to object formation helps users develop engagement and desire to complete tasks, decreasing the chance of dropping out and giving up tasks. Breaking tasks into smaller steps decreases the mental barrier of starting tasks and bringing in rewards with gamification drives it further.

### What it does

Roominate gamifies and offers incentives for completing your tasks by rewarding completion with the ability to decorate a cozy home. Place Your Task: Instead of "adding a task" to an endless list, you are provided with a personal space and "place a task" in a room. This turns an abstract chore ("Write Essay") into a concrete "ghosted" object (like a Bookshelf or a plant) in a specific spot where you could categorize and recall it based on spatial memory. AI Task Breakdown: Have trouble breaking down your complex task into steps? Our AI assistant breaks that intimidating task into small, non-threatening, actionable subtasks (e.g., "Research topic," "Write outline," "Write first draft"). The "blank page" problem is gone. Create Your Space: Completing a task solidifies the “Bookshelf” as a permanent, beautiful object. As you complete tasks, you unlock new rooms and more opportunities for different furniture, building a home you’re genuinely attached to. Get Motivated: The friendly cat inside your home gives you advice and motivation to complete your tasks. psst Crater... there may be a secret cow as well, but I wouldn't follow its advice!

### How we built it

We built Roominate in Godot 4.5 focusing on seamless UI/UX design. The global logic is managed through signals to synchronize updates across all scenes. Each major system; task creation, to-do management, room layout, all communicate through these signals to ensure smooth, event-driven updates without manual dependencies. Our AI system, powered by ASI:One implemented in cat.gd and cow.gd as well as OpenRouter with Claude shown in our task step generator. In terms of the assistants, they receive updates about the latest edited task, sent through HTTP requests, and respond with a short and personality-driven feedback. The dialog appears dynamically in-game and an automatic timer, creating natural conversational flow without blocking gameplay. The room system ties task progress directly to world building. Completed tasks instantiate corresponding furniture objects within room_layout.gd, while furniture.gd manages placement. Tasks are tracked globally enabling players to unlock rooms and better interactions with the cow and cat assistants.

### Challenges we ran into

One of the biggest challenges we faced was standing out in a space where productivity tools and gamified tasks have already been explored. We didn’t want to make something that simply looked different, we wanted it to feel different, with real emotional engagement and long-term growth potential. Finding that balance between familiar and innovate required a lot of iteration and design restraint. Integrating AI added another layer of complexity. We wanted the AI assistants (like our cat and cow personalities) to feel dynamic and responsive, but not intrusive or repetitive. Tuning their tone, behavior, and timing so they were motivating (well, at least the cat is) rather than distracting was a careful balancing act. We also faced the usual hackathon time pressure, merging design, code, and polish under tight deadlines while keeping our vision cohesive. The biggest challenge wasn’t just technical; it was staying focused on our core idea: making productivity something players would want to return to, again and again.

### What's next

We believe Roominate is an innovative new tool for productivity. Our roadmap for continuation includes: Multiplayer & Social Rooms: We did some user interviews and concept testing, and found that social aspects are also incentives for many people to accomplish tasks. Imagine visiting a friend's "Sweet Home" to see the objects they've built, or even tackling a "group project" that builds a shared object in a community space. Deeper Customization: Select from an option of themes such as pirate, farm, and sci-fi to make every space truly unique and personalized. Application Integration: Launch the programs or open the web pages needed for your task directly from Roominate.

## README (from the GitHub repository)

# Roominate - Gamified Task Tracker

enter same stuff from devpost here (like the elevator pitch)

This repo houses the Godot game plus Python services that:
- expose the quest-planning REST API (`uv run uvicorn app.main:app --reload`)
- expose the same capabilities over MCP (`uv run python backends/app/mcp_server.py --transport stdio`)
- add a lightweight agent-relay server that routes prompts to Fetch.ai, JanitorAI, Wordware, or Letta (`uv run uvicorn app.agent_server:app --port 8300 --reload`)

All details live in `backends/README.md`, `docs/API.md`, and `docs/MCPGuide.md`.


## Detected evidence (automated analysis)

Indexed codebase: 42 recognized source files, 111 KB.
- FastAPI (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (120 of 646)

```
.github/workflows/backend.yml
.gitignore
assets/.DS_Store
assets/.gitkeep
assets/buttons/Blue_Buttons_Pixel.png.import
assets/buttons/Brown_Buttons_Pixel.png.import
assets/buttons/Gray_Buttons_Pixel.png.import
assets/buttons/Green_Buttons_Pixel.png.import
assets/buttons/lock.png.import
assets/buttons/lock.png~
assets/buttons/Purple_Buttons_Pixel.png.import
assets/buttons/Red_Buttons_Pixel.png.import
assets/buttons/task marker.png.import
assets/buttons/task marker.png~
assets/buttons/unlock.png.import
assets/buttons/Yellow_Buttons_Pixel.png.import
assets/cat 3 (64х64).png.import
assets/chatbox.png.import
assets/chatbox.png~
assets/furniture/bathroom.png.import
assets/furniture/bedroom  furniture.png.import
assets/furniture/kitchen furniture.png.import
assets/furniture/livingroom furniture.png.import
assets/furniture/Slice 1.png.import
assets/furniture/Slice 10.png.import
assets/furniture/Slice 100.png.import
assets/furniture/Slice 101.png.import
assets/furniture/Slice 102.png.import
assets/furniture/Slice 103.png.import
assets/furniture/Slice 104.png.import
assets/furniture/Slice 105.png.import
assets/furniture/Slice 106.png.import
assets/furniture/Slice 107.png.import
assets/furniture/Slice 108.png.import
assets/furniture/Slice 109.png.import
assets/furniture/Slice 11.png.import
assets/furniture/Slice 110.png.import
assets/furniture/Slice 111.png.import
assets/furniture/Slice 112.png.import
assets/furniture/Slice 113.png.import
assets/furniture/Slice 114.png.import
assets/furniture/Slice 115.png.import
assets/furniture/Slice 116.png.import
assets/furniture/Slice 117.png.import
assets/furniture/Slice 118.png.import
assets/furniture/Slice 119.png.import
assets/furniture/Slice 12.png.import
assets/furniture/Slice 120.png.import
assets/furniture/Slice 121.png.import
assets/furniture/Slice 122.png.import
assets/furniture/Slice 123.png.import
assets/furniture/Slice 124.png.import
assets/furniture/Slice 125.png.import
assets/furniture/Slice 126.png.import
assets/furniture/Slice 127.png.import
assets/furniture/Slice 128.png.import
assets/furniture/Slice 129.png.import
assets/furniture/Slice 13.png.import
assets/furniture/Slice 130.png.import
assets/furniture/Slice 131.png.import
assets/furniture/Slice 132.png.import
assets/furniture/Slice 133.png.import
assets/furniture/Slice 134.png.import
assets/furniture/Slice 135.png.import
assets/furniture/Slice 136.png.import
assets/furniture/Slice 137.png.import
assets/furniture/Slice 138.png.import
assets/furniture/Slice 139.png.import
assets/furniture/Slice 14.png.import
assets/furniture/Slice 140.png.import
assets/furniture/Slice 141.png.import
assets/furniture/Slice 142.png.import
assets/furniture/Slice 143.png.import
assets/furniture/Slice 144.png.import
assets/furniture/Slice 145.png.import
assets/furniture/Slice 146.png.import
assets/furniture/Slice 147.png.import
assets/furniture/Slice 148.png.import
assets/furniture/Slice 149.png.import
assets/furniture/Slice 15.png.import
assets/furniture/Slice 150.png.import
assets/furniture/Slice 151.png.import
assets/furniture/Slice 152.png.import
assets/furniture/Slice 153.png.import
assets/furniture/Slice 154.png.import
assets/furniture/Slice 155.png.import
assets/furniture/Slice 156.png.import
assets/furniture/Slice 157.png.import
assets/furniture/Slice 158.png.import
assets/furniture/Slice 159.png.import
assets/furniture/Slice 16.png.import
assets/furniture/Slice 160.png.import
assets/furniture/Slice 161.png.import
assets/furniture/Slice 162.png.import
assets/furniture/Slice 163.png.import
assets/furniture/Slice 164.png.import
assets/furniture/Slice 165.png.import
assets/furniture/Slice 166.png.import
assets/furniture/Slice 167.png.import
assets/furniture/Slice 168.png.import
assets/furniture/Slice 169.png.import
assets/furniture/Slice 17.png.import
assets/furniture/Slice 170.png.import
assets/furniture/Slice 171.png.import
assets/furniture/Slice 172.png.import
assets/furniture/Slice 173.png.import
assets/furniture/Slice 174.png.import
assets/furniture/Slice 175.png.import
assets/furniture/Slice 176.png.import
assets/furniture/Slice 177.png.import
assets/furniture/Slice 178.png.import
assets/furniture/Slice 179.png.import
assets/furniture/Slice 18.png.import
assets/furniture/Slice 180.png.import
assets/furniture/Slice 181.png.import
assets/furniture/Slice 182.png.import
assets/furniture/Slice 183.png.import
assets/furniture/Slice 184.png.import
assets/furniture/Slice 185.png.import
assets/furniture/Slice 186.png.import
[526 more files omitted for size]
```

### Dependencies

- backends/pyproject.toml: alembic@>=1.13.0, chromadb@>=0.5.5, fastapi@>=0.115.0, fastmcp@>=0.4.0, httpx@>=0.27.0, httpx@>=0.27.0, mypy@>=1.10.0, openai@>=1.50.0, pydantic-settings@>=2.4.0, pytest@>=8.3.0, python-dotenv@>=1.0.1, ruff@>=0.5.0, sentence-transformers@>=3.0.0, sqlmodel@>=0.0.21, tenacity@>=9.0.0, uvicorn@>=0.30.0

### Recent commits (newest first)

- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- changes
- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- added APIS
- Add files via upload
- cow
- tracker in the corner for # completed tasks
- Update README.md
- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- .
- small fix for furniture
- cat?
- moo
- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- finish setup for the demo
- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- api updates
- Merge branch 'main' of https://github.com/XitoAliferis/calhacks12
- switched model and fixed AI bugs

## Key source files (fetched from GitHub, selected and truncated for size)

### docs/GodotIntegration.md

```markdown
# Godot Integration Notes

Use `HTTPRequest` (or `HTTPClient`) to communicate with the FastAPI backend running at `http://127.0.0.1:8000`.

## Endpoints Recap
- `POST /ai/generate` — convert player text into hierarchical todos (see `docs/API.md`). Set `save=false` to preview quests without mutating backend state.
- `GET /todos/tree` — fetch nested quest trees for rendering.
- `POST /memory/search` — semantic recall for contextual suggestions.
- `GET /health`, `GET /ready` — lightweight health/readiness checks for boot gating.

## Example: Fetch Todo Tree
```gdscript
@onready var http := $HTTPRequest

func _ready():
    http.request_completed.connect(_on_tree)
    http.request("http://127.0.0.1:8000/todos/tree")

func _on_tree(result, response_code, _headers, body):
    if response_code != 200:
        push_error("Backend error %s" % response_code)
        return
    var payload = JSON.parse_string(body.get_string_from_utf8())
    render_tree(payload["todos"])
```

## Offline / Mock Workflow
- Run the real backend with `MOCK_AI_RESPONSES_FILE` set (default points to `backends/mocks/ai_generate_sample.json`). This lets `/ai/generate` respond without calling OpenRouter.
- Alternatively, start the lightweight mock server: `uv run python scripts/run_mock_server.py` (serves on port 8001).
- Load demo data via `uv run python scripts/load_demo_data.py data/demo_tasks.json --reset` to ensure the tree endpoint returns useful content.

## Error Handling Tips
- Non-200 responses will include `{ "detail": ... }`; surface these in-game for debugging.
- Use the `/ready` endpoint during Godot startup to ensure the backend has finished booting/migrations before sending gameplay-critical requests.

```

### docs/sponsors.md

```markdown

# spons-cofoundry
# spons-composio
# spons-letta
# spons-postman
# spons-rox
# spons-y-combinator
# announcements
# ask-directors
# ask-mentors
# ask-mih
# cohost-annapurna-labs
# cohost-anthropic
# cohost-applovin
# cohost-fetchai
# first-time-hackers
# general-and-memes
# hardware
# spons-a37
# spons-arize
# spons-baseten
# spons-bright-data
# spons-camfer
# spons-chroma
# spons-citadel
# spons-coderabbit
# spons-comet
# spons-conversion
# spons-conway
# spons-crater
# spons-cread
# spons-davidai
# spons-de-shaw
# spons-decagon
# spons-deepgram
# spons-elastic
# spons-ethereum-foundation
# spons-fish-audio
# spons-fung-institute
# spons-gemini
# spons-groa
# spons-heavybit
# spons-interaction-co
# spons-janitorai
# spons-labelbox
# spons-lava
# spons-listenlabs
# spons-livekit
# spons-mastra
# spons-mercor
# spons-meter
# spons-mintlify
# spons-promise
# spons-regeneron
# spons-reka
# spons-ripple
# spons-scale-ai# spons-de-shaw
# spons-decagon
# spons-deepgram
# spons-elastic
# spons-ethereum-foundation
# spons-fish-audio
# spons-fung-institute
# spons-gemini
# spons-groa
# spons-heavybit
# spons-interaction-co
# spons-janitorai
# spons-labelbox
# spons-lava
# spons-listenlabs
# spons-livekit
# spons-mastra
# spons-mercor
# spons-meter
# spons-mintlify
# spons-promise
# spons-regeneron
# spons-reka
# spons-ripple
# spons-scale-ai# spons-de-shaw
# spons-decagon
# spons-deepgram
# spons-elastic
# spons-ethereum-foundation
# spons-fish-audio
# spons-fung-institute
# spons-gemini
# spons-groa
# spons-heavybit
# spons-interaction-co
# spons-janitorai
# spons-labelbox
# spons-lava
# spons-listenlabs
# spons-livekit
# spons-mastra
# spons-mercor
# spons-meter
# spons-mintlify
# spons-promise
# spons-regeneron
# spons-reka
# spons-ripple
# spons-scale-ai
# spons-sideshift
# spons-snap
# spons-sui
# spons-terac
# spons-thehousefund
# spons-toolhouse
# spons-vapi
# spons-visa
# spons-voltage-park
# spons-warp
# spons-windsurf
# spons-wordware



```

### backends/Dockerfile

```
FROM python:3.11-slim

ENV UV_SYSTEM_PYTHON=1
WORKDIR /app

RUN pip install --no-cache-dir uv

COPY . ./

RUN uv sync

EXPOSE 8000
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

```

### backends/pyproject.toml

```
[project]
name = "ai-todo-backend"
version = "0.1.0"
description = "LLM-assisted hierarchical todo backend for the Godot task game"
readme = "README.md"
authors = [{ name = "CalHacks Team" }]
requires-python = ">=3.10,<3.13"
dependencies = [
    "fastapi>=0.115.0",
    "uvicorn>=0.30.0",
    "sqlmodel>=0.0.21",
    "pydantic-settings>=2.4.0",
    "openai>=1.50.0",
    "chromadb>=0.5.5",
    "sentence-transformers>=3.0.0",
    "python-dotenv>=1.0.1",
    "tenacity>=9.0.0",
    "fastmcp>=0.4.0",
    "httpx>=0.27.0"
]

[project.optional-dependencies]
dev = [
    "pytest>=8.3.0",
    "httpx>=0.27.0",
    "ruff>=0.5.0",
    "mypy>=1.10.0",
    "alembic>=1.13.0"
]

[project.scripts]
task-mcp = "app.mcp_server:main"

[tool.uv]
package = false
managed = true

[tool.ruff]
line-length = 100

[tool.pytest.ini_options]
minversion = "8.0"
addopts = "-q"
testpaths = ["tests"]

```

### backends/app/main.py

```python
#!/usr/bin/env python
# -*-coding:utf-8 -*-
'''
@File    :   main.py
@Time    :   2025/10/25 15:55:18
@Author  :   Ethan Pan 
@Version :   1.0
@Contact :   epan@cs.wisc.edu
@License :   (C)Copyright 2020-2025, Ethan Pan
@Desc    :   FastAPI bootstrapper.
'''


from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlmodel import select

from app import models
from app.config import settings
from app.database import init_db, session_scope
from app.middleware import RateLimiterMiddleware
from app.api import routes_ai, routes_memory, routes_todos

# creating the FastAPI app.
app = FastAPI(title="AI Task Backend", version="0.1.0")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
app.add_middleware(
    RateLimiterMiddleware,
    limit=settings.RATE_LIMIT_REQUESTS,
    window_seconds=settings.RATE_LIMIT_WINDOW_SECONDS,
)
# including the routes.
app.include_router(routes_todos.router)
app.include_router(routes_ai.router)
app.include_router(routes_memory.router)


# initializing the database on startup.
@app.on_event("startup")
def _startup() -> None:
    init_db()


# healthcheck endpoint.
@app.get("/health", tags=["Health"])
def healthcheck() -> dict[str, str]:
    return {"status": "ok", "debug": str(settings.DEBUG).lower()}


# readiness endpoint.
@app.get("/ready", tags=["Health"])
def readiness() -> dict[str, str]:
    try:
        with session_scope() as session:
            session.exec(select(models.TodoItem.id).limit(1))
    except Exception as exc:  # pragma: no cover - surfaces infra bugs
        raise HTTPException(status_code=503, detail="Database unavailable") from exc
    return {"status": "ready"}

```

### backends/app/__init__.py

```python
"""Application package bootstrap."""

```

### backends/tests/test_agent_router.py

```python
from __future__ import annotations

import pytest

from app.services.agent_router import agent_router
import asyncio


def test_agent_router_fallback(monkeypatch):
    monkeypatch.setattr(agent_router.providers["fetchai"], "base_url", None)
    result = asyncio.run(agent_router.run("fetchai", model=None, user_input="Test fallback", metadata=None))
    assert result.used_fallback is True
    assert "Test" in result.output

```

### backends/tests/test_ai_service.py

```python
from __future__ import annotations

import json

from app.services import ai_service


def test_mock_ai_generation(tmp_path, monkeypatch):
    sample = {"todos": [{"title": "Sample", "priority": "medium", "status": "pending", "subitems": []}]}
    mock_file = tmp_path / "mock.json"
    mock_file.write_text(json.dumps(sample))

    monkeypatch.setattr(ai_service.settings, "MOCK_AI_RESPONSES_FILE", str(mock_file))

    todos = ai_service.generate_structured_todos("ignored")
    assert len(todos) == 1
    assert todos[0].title == "Sample"

```

### .github/workflows/backend.yml

```yaml
name: Backend CI

on:
  push:
    paths:
      - "backends/**"
      - "docs/**"
      - ".github/workflows/backend.yml"
  pull_request:
    paths:
      - "backends/**"
      - "docs/**"
      - ".github/workflows/backend.yml"

jobs:
  tests:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: backends
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - name: Install uv
        run: pip install uv
      - name: Install dependencies
        run: uv sync --extra dev
      - name: Lint
        run: uv run ruff check
      - name: Tests
        run: uv run pytest

```

### backends/tests/conftest.py

```python
from __future__ import annotations

import sys
from pathlib import Path

import pytest
from sqlmodel import SQLModel, Session, create_engine

ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))

from app import models
from app.services import chroma_service


@pytest.fixture()
def session(monkeypatch: pytest.MonkeyPatch) -> Session:
    engine = create_engine("sqlite://", connect_args={"check_same_thread": False})
    SQLModel.metadata.create_all(engine)

    # Avoid hitting the real vector DB in unit tests
    monkeypatch.setattr(chroma_service, "index_todo", lambda *args, **kwargs: None)
    monkeypatch.setattr(chroma_service, "delete_todo", lambda *args, **kwargs: None)

    with Session(engine) as session_obj:
        yield session_obj

```

[31 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]