Aryan Kumar

chunk: mythosAI learningfinal-year project2024 to 2025

Mythos

My final-year project, and the first time I put an LLM in my own code. You type a chore, press Questify, and a model hands it back as a fantasy quest worth some XP.

The reply is parsed and checked, then the XP is clamped to 10 to 150questController.js
const rawJSON = response.choices[0].message.contentconst questData = parseAndValidateQuestJSON(rawJSON)return {  taskId,  status: 'success',  quest: {    user: userId,    originalTask: task._id,    ...questData,    isComplete: false,    xpReward: Math.min(Math.max(questData.xp, 10), 150),  },}
Source: Project-RPG backend/controllers/questController.js, lines 59 to 72
Role
Solo, full stack
Timeline
Nov 2024 to Apr 2025
Status
Never deployed
Stack
React, Express, MongoDB, OpenAI
Sources for these facts
  • Role: Project-RPG git shortlog: 40 of 41 commits mine, 1 automated
  • Timeline: Project-RPG git log: 41 commits, 12 Nov 2024 to 30 Apr 2025
  • Status: frontend/src/api/*.ts call localhost:3001; no live URL found
  • Stack: frontend/package.json; backend/package-lock.json

The short version

Mythos is a to-do app that hands your chores to an OpenAI model and gets them back as fantasy quests, with XP, levels, streaks and charts of your progress.

It was my first full-stack project and my first LLM integration. The part I'm still glad about: I didn't trust a word the model said.

What happens when you press Questify

  1. You type a chore and press Questify.

    Source: frontend/src/pages/Adventure.tsx, line 125
  2. Your tasks go to the model, at most 5 at a time.

    Source: Project-RPG backend/controllers/questController.js, line 37
  3. The model is told to answer in JSON: a quest title, a description and an XP number.

    Source: Project-RPG backend/controllers/questController.js, lines 12 to 16 and 54
  4. The reply is cleaned, parsed and checked, and the XP is clamped to 10 to 150.

    the hard part, below
    Source: Project-RPG backend/controllers/questController.js, lines 59 to 70 and 207 to 222
  5. The quests are saved in one insert and pushed live to every device you are signed in on.

    Source: Project-RPG backend/controllers/questController.js, lines 91 to 95; backend/index.js, lines 75 to 110

For anyone who finds a to-do list dull

The README's whole pitch is turning "mundane tasks into exciting quests", and that is as far as the docs go.Source: Project-RPG README.md, line 3

I have no users or feedback to show you, so this page is about the build, not results.

Quests, levels, streaks, charts and live sync

Questify
Type a task and the model returns a quest with a title, a description and an XP reward.Source: Project-RPG backend/controllers/questController.js, lines 49 to 70
Levels
Level n needs n × 100 XP. Leftover XP carries over, and several level-ups in one go are handled in a loop.Source: Project-RPG backend/controllers/questController.js, lines 153 to 159
Streaks
Finish 3 quests in one day (UTC) and your streak goes up by one.Source: Project-RPG backend/controllers/questController.js, lines 161 to 184
Live sync
Socket.IO checks the same login token as the API, then puts each user in their own room, so updates only reach your devices.Source: backend/index.js, lines 63 to 110
The Codex
D3 charts of your progress, including cumulative XP with a 3-point moving average and zoom.Source: frontend/src/components/graphs/PlayerProgression.tsx, lines 26 to 60
Sign-in
A JWT in an httpOnly cookie with a 24-hour expiry, and bcrypt-hashed passwords of at least 8 characters.Source: backend/controllers/userController.js, lines 4 to 8; backend/models/User.js, lines 27 to 47 and 97 to 105
The plumbing
Swagger API docs, Docker setups for development and production, and a CI workflow on Node 18 and 20.Source: backend/config/swagger.js; docker-compose*.yml; .github/workflows/node.js.yml

The hard part: never trusting the model's reply

This is where my AI learning started. A language model doesn't return data. It returns text that usually looks like data. Mythos was going to add that number to someone's XP, so I treated every reply the way you'd treat a form filled in by a stranger.

I asked for JSON and switched on the API's JSON response format. Then, belt and braces, the code strips any Markdown code fences the model wraps around its answer, parses what is left, and throws if the title, the description or a numeric XP is missing.Source: Project-RPG backend/controllers/questController.js, lines 51 to 60 and 207 to 222

Last, the XP is clamped: that is line 70, highlighted at the top of this page. The prompt asks for a number from 0 to 150. Whatever comes back, the quest stores at least 10 and at most 150. The prompt is a request; the clamp is the rule.Source: Project-RPG backend/controllers/questController.js, lines 15 and 70

Cleaning and checking the replyquestController.js
  const jsonString = rawJSON    .replace(/```json/g, '')    .replace(/```/g, '')    .trim()  const data = JSON.parse(jsonString)  if (    !data.questTitle ||    !data.questDescription ||    typeof data.xp !== 'number'  ) {    throw new Error('Invalid quest format from OpenAI')  }
Source: Project-RPG backend/controllers/questController.js, lines 208 to 220

Batches that fail one task at a time

Questify can send several tasks at once. They go out at most 5 at a time, and each succeeds or fails on its own: a bad reply comes back as an error for that one task, tasks that already have a quest are skipped, and the rest are saved in a single database insert.Source: Project-RPG backend/controllers/questController.js, lines 25 to 96

XP you can only earn once

Completing a quest is one database update that only matches a quest that is yours and not yet complete. Press Complete twice and the second press finds nothing, so nobody is paid XP twice.Source: Project-RPG backend/controllers/questController.js, lines 141 to 148

Promise.allSettled, so one bad reply costs one quest

The obvious tool for firing off several calls is Promise.all, and it rejects the whole batch the moment one call fails. I used Promise.allSettled, which waits for every task and reports which ones failed.

The cost is some bookkeeping: the code sorts every result into created or skipped, and the response returns both lists. One sulking reply no longer costs you the other quests.Source: Project-RPG backend/controllers/questController.js, lines 38 and 81 to 102

Tests for the database models, none for the parser

  • Three backend test files cover the database models for quests, users and tasks, written to run against an in-memory MongoDB.Source: backend/test/Quest.test.js, User.test.js, task.test.js; backend/jest.config.js
  • Nothing tests the parser or the clamp, which is the part this page is proudest of.Source: backend/test/*.test.js
  • A GitHub Actions workflow runs lint, tests and a build on Node 18 and 20. The setup file the tests rely on was never committed, so I can't show you a passing run.Source: .github/workflows/node.js.yml; backend/jest.config.js, line 7 (test/setup.js is not tracked)

What I'd own up to

  • It was never deployed. There are Docker files for development and production, but the frontend calls http://localhost:3001 directly while the compose file runs the backend on port 3000.Source: frontend/src/api/authAPI.ts, line 10; taskQuestAPI.ts, line 4; userAPI.ts, line 5; docker-compose.yml
  • The README promises more than the code delivers. Password reset, rate limiting and token refresh are listed; the user model has fields for a reset token, but no route uses them.Source: Project-RPG README.md, lines 170 to 201; backend/models/User.js, lines 59 to 66; backend/controllers
  • Three files are empty: the Rewards page, the profile page and the navbar.Source: frontend/src/pages/Rewards.tsx, pages/profile.tsx, components/Navbar.tsx: 0 lines each

Next time: a schema, real tests and a little retrieval

What I'd change now I'm learning RAG and agents. Intentions, not things I have built yet.

  • Define the quest shape once, as a schema, and use it both to ask the model and to check its reply, instead of hand-written ifs.
  • Keep every reply that broke the parser and turn it into a test, so the guard is proven and not just written.
  • Keep the model's raw reply when a task fails. Today it comes back as "API error" and the reply is gone.
  • Give the model the player's past quests as context, so the world stays consistent. That is retrieval on a small scale, which is what I'm learning now.
  • Try a model that plans first and then acts, splitting one big chore into smaller quests: a first, small agent.
  • Deploy it, with the API address in config rather than hard-coded.

Where this sits in the index

The highlighted points are the chunks of my index that come from this page.

web workAI learning1 first LLM call2 XP clamped 10 to 1503 Birmingham, 2024-25
  1. 1Mythos: my first LLM call. Type a chore, get back a fantasy quest with XP.source: Project-RPG backend/controllers/questController.js
  2. 2Mythos checks the model's JSON and clamps its XP between 10 and 150.source: Project-RPG backend/controllers/questController.js, lines 51 to 70
  3. 3Final-year project at the University of Birmingham, 2024 to 2025.source: Project-RPG; git.cs.bham.ac.uk projects-2024-25

Query the full index