Inspiration

Streamers give things away in chat all the time: merch drops, slots in a viewer game, the next song request, a coaching spot. The rule is always "first to say dibs", and it is never applied fairly. Chat scrolls faster than anyone can read. Viewers write "dibs", "me!!", 🙋, "slot 3 pls" or "mujhe ek chahiye", and the streamer ends up picking by eye. When chat is busy, nobody can tell who was actually first. We wanted a referee that is fast, fair and fun to watch.

What it does

  • Reads chat the way viewers write it. Clear claims ("dibs", "me", "slot 3 pls", emotes, Hinglish such as "mujhe ek chahiye") are decided instantly by local rules. Questions ("is it free?", "who got it?") and refusals never win.
  • Orders by Twitch's timestamp, not by arrival. Every EventSub chat message carries Twitch's own timestamp, and Dibs ranks claims by it. A message that arrives late but was sent earlier slides into its rightful spot and bumps the latest winner to the waitlist. The overlay shows this happening ("FAIR · EARLIER TIMESTAMP").
  • Asks a model only when chat is ambiguous. Unclear lines ("bhai ek mere liye bhi rakhna", "save one for my brother") are batched to OpenAI's open-weight gpt-oss-20b. A validator sits between the model and the allocator:
    • the model cannot auto-grant a message with no first-person claim;
    • a request on someone else's behalf always goes to the streamer;
    • quantity is clamped to the per-viewer limit;
    • a timeout, rate limit or missing key sends the message to review. It is never granted.
  • Keeps the streamer in charge. A review queue on the dashboard has keyboard shortcuts (A to approve, R to reject, J/K to move). Cancelling a winner ("nvm", "cancel mine") promotes the first person on the waitlist automatically. Channel Points redemptions count as claims too.
  • Talks back in chat. The bot names the winners, answers !spot ("you're #2 of 5") and "who got it?", and announces SOLD OUT with the time it took. Replies are batched and throttled so a burst doesn't spam the channel.
  • Can rehearse on a live channel. The streamer can add a practice crowd, and real chat claims race it on stream. Practice viewers are never named or @-mentioned in Twitch chat.
  • Makes the race fun to watch. In the OBS overlay, names land in their spots with the gap to the previous winner (+0.12 s, +0.31 s…). The overlay also shows a waitlist ticker, "MOVED UP" flashes and a SOLD OUT stamp. The dashboard exports winners as CSV.
  • Has a rehearsal mode. With no Twitch credentials, a 32-second scripted crowd runs through the same resolver and allocator, so anyone can try it in under a minute.

How we built it: with Codex

The whole project was built in one Codex thread, in fourteen short steps. Every line of code, the tests, the README and the licence were written by Codex; the only hand edit is the deployed URL in the README. Each step started from a short brief that reported what the previous build actually did when it was run:

  1. Product brief and core: the event-sourced allocator (timestamp ordering, settle window, per-viewer limit, waitlist, review) and the claim rules, with 61 tests.
  2. Model fallback: the gpt-oss-20b client with strict JSON-schema output (falling back to json_object if the provider refuses), 250 ms batching, a 2.5 s timeout, a token budget and the validator. We then ran it live against Groq and fed Codex the three messages it got wrong.
  3. Twitch: Codex read the Twitch docs live and implemented Device Code auth, an EventSub WebSocket client (welcome, keepalive, reconnect, revocation, de-duplication by message id), Helix chat replies and an SSE server, then tested all of it against a fake EventSub server.
  4. Overlay and dashboard: plain HTML, CSS and ES modules, with no framework, so it loads instantly as an OBS browser source.
  5. The show: we checked the overlay in Chrome at 1080p and reported what didn't read on stream: the race didn't animate, SOLD OUT covered the rows, and the cancels promoted nobody. Codex rewrote the rehearsal as choreography.
  6. Hosted demo:
    • a one-tab try-it page;
    • per-visitor rooms;
    • rate limits;
    • SSE keep-alive;
    • a model-decision cache;
    • a Render blueprint.
  7. Chat that talks back:
    • replies that name the winners;
    • !spot ("you're #2 of 5");
    • throttled answers to "who got it?";
    • a SOLD OUT announcement with the sell-out time.
  8. Ready for a live channel: a /stage scene for OBS, and a practice crowd that races real chat on a live drop. Practice viewers are never named or @-mentioned in Twitch chat.
  9. Polish: a transparent overlay frame on /stage, a problem we found while recording the video.
  10. Hosted stage: public room redirects keep the query string, so /stage?overlay=br works on the hosted demo too.

Steps 11 to 14: live on a real Twitch channel. We streamed Dibs with OBS to a real channel, ran an 8-spot drop with the practice crowd and real chat messages, recorded the page as a logged-out viewer, and logged chat with a second EventSub listener. Each brief listed what that run measured, and Codex fixed it:

  • Twitch's keepalives arrived 9.99 to 10.01 s apart against a 10 s timeout, so the client reconnected about every 11 s. It now allows a short grace period.
  • Helix answered 400 "Message cannot be replied to." because replies pointed at the EventSub envelope id. Replies now thread to the chat message id, with a retry without a parent.
  • Threaded replies repeated the @mention Twitch already adds, and the bot read its own replies back as chat. Both are gone.
  • Practice activity put 15 lines into chat in 35 s and pushed a real !spot answer about 20 s late. Practice-only lines are gone, and answers to real viewers go first.
  • With 8 spots, the overlay card was taller than the 1080 px frame. It now fits 1 to 20 spots.
  • "who got it?" said "+15 practice" while 7 practice viewers held spots. Chat now counts only practice winners.

Codex's sandbox can't write .git, so each step was committed right after it finished, with the thread id in the commit message. The git history therefore lines up with the Codex thread step by step. Steps 7 to 14 were also recorded in a terminal while Codex worked, and the video shows part of that recording. The suite is 134 tests and tsc --noEmit is clean.

Challenges we ran into

  • Fairness versus delivery order. EventSub can deliver messages out of order. Dibs keeps a short settle window (1.5 s live) in which an earlier-timestamped claim can still displace the latest winner. After that window, results are final, so nobody loses a spot they've already seen.
  • Letting a model help without letting it give things away. The model can down-rank anything, but it can't promote a line with no first-person claim into an automatic win.
  • "Slot 3 pls" and Hinglish. The first rule set sent these to review. We added them as claim cues, with tests for their near neighbours.

Accomplishments that we're proud of

  • It works on a real Twitch stream. On the final run, a real "dibs" landed at #5 of 8 among the practice crowd, and the bot answered the claim, !spot and "who got it?" within 1.1 s of each message.
  • In the rehearsal, the fairness rule plays out on screen: a late message with an earlier timestamp visibly takes its rightful spot.
  • Model hiccups only ever slow a claim down into review. They never hand out a spot.
  • There is nothing to install for viewers. The streamer registers a Twitch app once, signs in, and adds one browser source to OBS.

What we learned

Short Codex steps that report measured results back to the agent worked far better than one long spec. For each step we ran the build for real and told Codex exactly what happened.

What's next

  • A Twitch Extension panel so viewers can see their place in line.
  • Subs-first windows tied to live subscriber status.
  • Multi-item drops with sizes and variants.

Built with

openai-codex · gpt-oss-20b · groq · twitch-eventsub · twitch-helix · typescript · node.js · server-sent-events · vitest · obs · render

AI tools used

  • OpenAI Codex (CLI): wrote all the code, tests and docs (thread 01a18b69-bb72-7981-a6cc-f3c9214d86e6).
  • Claude Code: wrote the step briefs, ran the builds and browser checks, and committed each step.
  • gpt-oss-20b via Groq: used at runtime for ambiguous chat only.

Links

Built With

Share this project:

Updates

Submission history