Inspiration

I kept watching chats do the same thing: vote on something, see it happen, forget it a minute later. Nothing sticks. I wanted a stream where chat leaves marks that stay.

Then I remembered Nomic, a game where changing the rules is the game. What if chat could write the rules of the stream, and every rule showed up as a body part on a creature? A law passes, the beast grows something. Look at the beast and you're reading the channel's laws. Lose a vote and the beast carries a scar.

I'm building this alone with no budget, so two rules shaped everything. It has to work with five people in chat, not five thousand, and it can't cost anything to run.

(I renamed it halfway through. It started as "Constitution Beast," which sounded like something a chatbot would name a project. Beast Rules sounds like something a person would say.)

What it does

Chat types a law in plain English, chat votes on it, and if it passes it starts firing on real Twitch events and grows a part on a beast in the stream overlay.

Say someone types !law when someone subs grow wings and shake the screen. The compiler turns it into a rule with a trigger (channel.subscribe), an action (screen_shake, low), a part (yellow wings) and a zone (the back). Nothing else gets through. Chat votes, and if it passes, wings appear and a banner shows the law next to the part it made: "The beast grew yellow wings. passed 3-1. written by viewer_2". From then on every sub shakes the screen, and a small toast says which law fired and who wrote it.

The beast has five body zones: head, torso, back, limbs and aura. If a new law wants a zone that's already taken, chat holds a constitutional crisis. Two cards, a countdown, and a vote on which law survives. The screen goes still for a beat before the verdict, and the loser leaves a crack on the beast for good. Chat can also repeal a law, and a repeal leaves a scar too.

Around that core there's more to play with:

  • A numbered ballot, where the beast offers three suggested laws and viewers just type 1, 2 or 3.
  • A name for the beast, picked by a chat vote from a fixed list, so viewer text can never become the name.
  • A temperament that grows out of its laws and scars, and a desire ("Pebble wants a law about raids") that it reacts to when chat delivers.
  • A printable constitution page with every law ever written, who wrote it, and whether it's in force, repealed or overruled.
  • A recap card, a 1200x630 image of the beast and its top lawmakers, for posting in Discord.
  • Streamer controls for the broadcaster and mods: !beast pause, resume, veto and reset.

Everything is saved between streams. You can try it without a Twitch account: npm install && npm run sandbox opens a sandbox with fake viewers, and npm run demo plays the whole story by itself.

How I built it

I built it with OpenAI Codex, in one thread, from a written spec. Before the first prompt I had an AGENTS.md with the rules, a JSON Schema describing every valid law, and a 28-case test file that every compiler has to pass. Then I fed Codex one small step at a time, and each step ended with tests, a build and a lint run. The whole log, mistakes included, is in docs/CODEX_LOG.md.

Codex wrote most of it: the compiler, the rule engine, the overlay, the crisis votes, the sounds, the safety layer and the streamer controls. It got to 196 tests before its free credits ran out, right after the rename. I finished the last features with another AI assistant, under my own design decisions: the reconnect handling, the ballot, repeal, scars, the constitution page, the recap card and the recording scripts. Those log entries are labelled, and the suite is at 268 tests now. I also used other assistants early on to plan and argue with me about the idea.

A law starts as a Twitch event. EventSub delivers chat messages, follows, subs, gifts, cheers, raids and hype trains over a WebSocket, so nothing needs a public URL. Intake drops duplicate message IDs and anything older than ten minutes, parses the commands and enforces a per-viewer cooldown.

Then the deterministic parser reads the sentence. If it can't decide, a local model (gemma3:4b through Ollama, with a 5 second timeout that never blocks anything) gets a turn, and the verifier only accepts its answer if the trigger, part, color and effect actually appear in what the viewer typed. Whatever comes out has to pass an enum-only JSON Schema, and anything that doesn't gets a fixed refusal. After that it's plain code: the rule engine runs the vote, replaces zones, handles crises and scars, fires laws on events and saves to disk, and the overlay (an OBS browser source) draws the beast, banners, toasts, vote ticker and synthesized sound.

The scopes it asks for are user:read:chat (for !law and the votes), moderator:read:followers (follow v2), channel:read:subscriptions (subs and gifts), bits:read (cheers) and channel:read:hype_train. Raids need none.

Challenges I ran into

My first idea was a different project. Chat would vibe-code the overlay live, with Codex running during the stream. It didn't survive a close look: strangers' code running on a streamer's machine, a minute of waiting per request, and a free quota that would drain fast. So laws became typed rules instead.

The local model turned out to be the weakest part, and I only know because I measured it. On laws it hadn't seen, gemma3:4b was right about 22% of the time with my first prompt, and about 44% once I added worked examples. That's when I flipped the design. The parser goes first, the model only sees what the parser can't decide, and a verifier checks whatever the model says. The verifier paid for itself right away by catching the model turning "dance" into "roar", an effect nobody asked for. On a fresh set of 30 phrasings I wrote after my last parser change, the parser alone got 83.3% and the parser plus rescue got 86.7%. Earlier sets were fixed after I saw their failures, so I don't count them any more. The misses all look alike: when a sentence names an effect the beast doesn't know, like "moonwalk", the parser can fall back to a harmless default instead of refusing. I haven't fixed that yet.

Codex couldn't open a port inside its sandbox, so for a while it built an overlay it had never seen. When I finally looked, the "wings" were four yellow leaves and the shell was hiding behind the body. It took a few rounds of screenshots to sort out.

Its early mistakes were small. The first parser output eventType where the schema wants event_type, so 22 of the first 28 contract cases failed. npm's cache rejected writes in its sandbox and Codex worked around it with a temporary cache. Later the verifier treated "scene" as a fuzzy match for the color "green", and a test caught that too.

I found one safety hole late. The overlay was showing the viewer's raw sentence, so someone could type when someone subs grow wings plus anything offensive and it would go straight on stream. Now the overlay never receives the raw text, only a description built from validated values, and there are tests for it. Codex fixed that one. Bans and doxxing requests get refused as unsafe before anything runs, and the streamer can pause, veto or reset from chat if a take goes wrong.

During live testing, a short network drop timed out Twitch's connection and crashed the whole server, because one error event had no listener. I added automatic reconnecting and checked it by freezing the process for 40 seconds. It came back on its own.

My first real Twitch test failed by a single second. Real chat reached the server, the law was proposed, and then I typed "yes" one second after the vote closed. The server ignored me, which was correct. Twitch also wanted two-factor authentication before I could register an app, and my phone number didn't work at first.

What I'm proud of

It works against real Twitch chat through real EventSub, not just mocks. I tried !law, votes, the crisis vote, the ballot and a real follow from a second account. Subs, raids and cheers can't be triggered on demand, so in the video those are simulated and labelled SIM.

When a law passes, the first thing you see is the law next to the part it created, in one banner. I wanted someone who has never heard of the project to get it in a second.

The crisis has a moment I'm fond of. When the vote ends, the alarm cuts and the screen holds still for a second and a half, then the verdict lands and a crack appears. It's a small thing, and it's the part people remember.

And it costs nothing: no paid API, no server, a local model on a laptop.

What I learned

The Twitch docs saved me from several mistakes I'd otherwise have made. EventSub delivers at least once and repeats keep the same message ID, so I dedupe on it. Chat sending is limited to 20 messages per 30 seconds, and breaking the limit gets a bot ignored for an hour, so vote status and refusals go on the overlay and chat replies are off by default. Each event lists the scope it needs, which let me ask for exactly five. WebSocket transport needs no public callback, so the whole thing runs on the streamer's machine with no tunnel. And the Twitch CLI can send mock events, which is how I tested follows, subs and cheers I couldn't trigger myself.

Codex is better at this than I expected when you hand it a spec and tests. It wrote a lot of the project quickly, and watching a failing suite turn green after one prompt never got old. It also gets things wrong in small, believable ways, and the tests are what caught them. I wouldn't have trusted it without them.

The other lesson: measure the model before you trust it, and put the parts that can hurt you in plain code. The part I was most sure would work, a small local model reading chat, turned out to be the weakest.

What's next for Beast Rules

  • Try the OBS scene switching on a real stream (it's only tested with mocks so far)
  • A desire with a long memory, so a beast that's ignored for two streams starts to sulk
  • A word-level refusal for effects and parts the beast doesn't know, instead of a silent default
  • More body zones and parts, so a long stream doesn't run out of things to grow
  • A setup that doesn't need a terminal, for streamers who have never opened one
  • Beasts that meet when two streamers raid each other

The voiceover in the video was made with ElevenLabs. The demo part uses simulated viewers, and the live part uses Twitch accounts of mine typing in real chat.

Built With

Share this project:

Updates

Submission history