Skip to content
All posts
3 min read

The bot's default answer is no

Sharing my thumbnail bot with friends meant building tenants, credit budgets, and a default that grants nobody anything.

AIEngineering

The thumbnail bot started as staff tooling: one Discord server, our games, my API bill. It generates Roblox thumbnails and icons with an image model, and friends with their own games kept asking for it. The hard part of saying yes was not multi-tenancy. It was that every generation spends real money, and an open-ended yes is an open tab.

The tenant unit I landed on is a workspace: a friend's Discord server, or my own home workspace, owning its games and a monthly credit budget in dollars. People hold per-workspace memberships with capabilities, so the same person can be trusted in one workspace and a stranger in another. Games belong to workspaces too, which is what makes the panel and the bot show a friend their titles and not mine.

The money mechanism is the part I would defend in an argument. Budgets are enforced by pre-authorization, not by checking. Before a generation starts, the workspace reserves the worst-case cost of the request; when it finishes, the reservation settles to the actual cost, and if it fails, the hold releases. The naive version, check the remaining budget then spend, has a race: two concurrent generations both pass the check and the cap becomes a suggestion. Reserve-then-settle holds the cap even when friends spam requests at the same second, which, being friends, they will. The lesson generalizes to any shared resource with a limit: a check is an opinion about the past, a reservation is a claim on the future, and only one of them survives concurrency.

The other design choice is that generosity is manual. When the bot joins a new server, it creates a pending workspace with a budget cap of zero dollars and grants nothing to anybody, including the server's owner. Adding the bot to a server proves you can click an invite link, not that I want to fund your art department. Every person who can spend is someone I added by hand in the access panel, into a workspace whose budget I typed. Friends hit real friction here and I chose it on purpose: a default that fails toward an apology beats one that fails toward an invoice.

Identity runs through Discord OAuth for everyone, so the same login works in the server and on the site. Friends get a scoped web console at /studio that shows only the games their memberships reach, with generate and approve buttons. The site re-checks every capability server-side with the same access rules the bot uses, because a web UI is just another client, and clients lie.

Shipping it to the live bot was its own little lesson in mechanical sympathy. The access data on disk was the old single-tenant shape, so the new build migrates it on load, wrapping everything that existed into a home workspace; the bot came up mid-conversation with its history intact and nobody noticed, which is the entire grade a migration can earn. The deploy itself fought back first: the process manager was watching the tree and kept re-stamping a version file, which blocked the git pull until I stopped the watcher, unstuck the file, and pulled clean. The boring failure modes are always the ones actually waiting on the box.

One more operational surprise: moving the slash commands from guild-scoped to global made them vanish. Global Discord commands take about an hour to propagate, so for one hour, fourteen commands, zero bugs, and a bot that looked completely broken. Now I know, and now so do you.

Everything above exists so the answer can start at no and move to yes cheaply: one person, one workspace, one typed budget at a time. No is a great default. It is the only one that never surprises you at the end of the month.

Remiel Shirazi

CTO & Co-founder | AI, Roblox & Piano