Bsheel hands you three random real-world quests. You pick one, do it before its timer runs out, and post a photo or video as proof. The XP arrives only after the proof is approved. I co-founded it with Razan Hasbini and Tayseer Laz, and I work on it as systems engineer and product manager. It is live on Google Play, the App Store, and bsheel.app, with more than 200 users.
From a hackathon app to a real backend
Bsheel started in 2025 as a hackathon app built on Supabase. In September 2026 we rebuilt it on a self-hosted NestJS backend: Flutter clients for the app and the admin console, PostgreSQL as the source of truth, Redis and BullMQ for queues and locks, S3-compatible storage for proof media, and a worker built from the same codebase as the API. Every domain is layered the same way, from presentation down to infrastructure, and slow or cross-domain effects go through a transactional outbox to idempotent workers.
A quest counts when a person has seen the proof
The core is a small state machine. Submitting proof marks the quest as submitted and the proof as pending. A moderator approves it, and the XP is awarded once, or rejects it. A rejected player can appeal once: the submission goes back to pending with the appeal spent, and a second rejection is final. Those rules are enforced on the server, not in the app.
The feed, votes, comments, collab groups, and leaderboards all sit on top of that. They are what make Bsheel fun, but they only mean something if the proof underneath them is honest. That is why the admin console is built around a review queue, appeals, and an XP reconciliation audit.
Asking a photo only what it can answer
The part of Bsheel I spent the most time on is automated proof checking. My first pass was a single vision-model call with a generic prompt. The prompt was not the problem. When I went through the quest catalogue and asked of each quest whether a photo could prove it, roughly a third could not. A photo of a book does not show you read twenty pages. “Spend an hour with no phone” cannot be proven by a picture the phone took. A model asked anyway still answers, with a confidence score attached, and that is how an automated reviewer confidently rejects an honest player.
So every quest now carries a verification contract: either the image can show the task, or it can only show whether the photo is authentic, or nothing about it bears on the task. The check runs in stages. Forensics come first and cost almost nothing: capture time, exact-byte and near-duplicate matching, screen dimensions, and markers of generated content. Then a cheap model, and a stronger one only where the cheap one could not tell. Most submissions stop early. Before any rejection, the most capable model has to agree; a confident verdict from a cheaper tier is never enough to reject.
I keep two numbers apart: how sure the model is of its verdict, and how relevant the media is to the quest. A photo of a coffee cup is irrelevant to “compliment a stranger”, and the submission is still honest. So relevance can hold back an automatic approval, but it can never cause a rejection.
Shadow mode, and a number to earn
The pipeline is live in production. It started in shadow mode, recording what it would have decided while a person still decided. Moderators see the agent’s read beside the proof, including what it looked for and did not find, because “no sunrise in this frame” is usually the line that matters. To know whether it deserves to act, I built an evaluation that scores its verdicts against decisions moderators already made. It includes a backfill over submissions reviewed before the agent existed, so its verdict could not have influenced the decision it is scored against.
Bugs you only find by leaving it running
The hardest bugs showed up after the pipeline had been running for a while, not when I read the code. A run interrupted by a restart could never be picked up again, because the claim on it was a label, not a lease that expires. A decision could be recorded and then quietly never applied. Both are fixed: claims expire, and a decision that fails to apply fails the run, so a retry is a real retry.
The perceptual hash that catches re-used photos was the strangest bug. It records whether each pixel is brighter than its neighbour, so a dark screenshot, a white wall, and a night sky all hash to the same value. Two unrelated people photographing two unrelated blank things looked like one stolen photo. It never rejected anyone, because automatic rejection was switched off, but it would have the moment that changed. Those frames now get no hash at all. A fingerprint that a whole class of images shares is not a fingerprint.
The rest of my part
Around the proof check I built business accounts for places on the map, granted by membership rather than by an admin role, with a partner dashboard as its own web app. I added an event store for the top of the funnel, kept apart from completions because a phone saying it showed a quest card is not something the server can confirm. I also built admin tools for quest chains and collections, and keyset pagination for the admin lists. Much of the app itself is Tayseer’s and Razan’s work.
In a game about doing things in the real world, the playful features are the easy part. Trust in the proof is the product, and the moderation behind it is where most of the engineering went.