URC is agent-native: don't like the bot? Fork it.
Last post I made the case that agents and humans should be the same kind of user. Same auth, same endpoints, same online list, same Elo ladder. URC is built that way down to the schema.
That's the supply side of the question — how does an agent talk to the server. There's a demand side too: what if you don't like the agent that's already here?
Right now the default opponent on URC is a process I call urc-bot. It's Fairy-Stockfish — the variant-chess fork of regular Stockfish — wrapped in a thin HTTP service that signs moves with an Ed25519 key, plays at a rating somewhere around 1400-1500 depending on how it's tuned, and runs on a Mac mini under my desk. It's perfectly fine. It will also beat most humans at a game where most humans have never seen half the pieces.
If you don't like urc-bot, there's no "please add a new bot account" form to fill out. There's no Discord to message. The cost of replacing it with your own bot is roughly the cost of hosting an HTTP endpoint, because that's the entire interface.
Here's how that works.
urc-bot is just an account
In the URC database, urc-bot is a row in urc_users with:
- a
username - an Ed25519
publicKey - an
entityTypeof'agent' - an Elo
ratingthat updates after every game - an
inboxUrlthat the platform POSTsyour_turnevents to
That's it. There is no is_official_bot flag. There is no separate urc_bots table. The Online List you see at ultimaterandomchess.com/start-game/ is SELECT username, rating FROM urc_users WHERE lastSeen > now() - interval '5 minutes' — humans and bots in the same query, sorted by who pinged most recently.
If you want to displace urc-bot, you don't have to convince me to deploy a new one. You just have to register an account, point a webhook at your own engine, and stay online. People will challenge you the same way they'd challenge me.
What does "host your own bot" actually involve?
Three pieces. None of them are URC-specific.
1. An engine that picks moves on a random back rank
Most chess engines, including regular Stockfish, will refuse to evaluate URC positions because the starting position is illegal classical chess. The two reasonable options:
- Fairy-Stockfish (github.com/fairy-stockfish/Fairy-Stockfish) — supports Chess960 and arbitrary back ranks via UCI option
UCI_Variant=chess960and theposition fencommand. Already understands fairy pieces (archbishop, chancellor, etc.) so a future URC expansion is mostly registering piece definitions. - A toy minimax — a 200-line negamax with alpha-beta + the URC valid-moves endpoint as your move generator. Will lose to Fairy-Stockfish but it'll lose interestingly.
I'd start with the toy and graduate to Fairy-Stockfish once you have your inbox loop working. Don't optimize playing strength before you've watched a single game scroll by in your terminal.
2. A webhook that receives your_turn events
URC pushes events to your inboxUrl. The shape:
POST https://your-bot.example.com/inbox
{
"event": "your_turn",
"gameId": "g_abc123...",
"color": "white",
"yourLastMove": null
}
Your endpoint:
- Authenticates the request (URC signs it; verify against the URC platform key, published at
/.well-known/add.json). - Fetches
GET /api/games/<gameId>for the current state. - Hands the position to your engine.
- Posts the move back to
POST /api/games/<gameId>/movewith a bearer token you got from/api/auth/session-grant.
There's no streaming, no long-poll, no SSE. Just two HTTP requests per move. The whole loop fits in a single small file.
3. A way to stay registered as "online"
URC's "online" definition is: a row in urc_users whose lastSeen is within the last 5 minutes. lastSeen updates on every authenticated request, so a quiet GET /api/me once every 2 minutes is enough. Or just play more games — receiving a webhook also counts.
A minimum-viable replacement in about a page of code
Once auth and the inbox webhook are wired up (see "What does 'host your own bot' actually involve?" above), your entire bot is a pickMove function that gets called on every your_turn event. Here's the Fairy-Stockfish version in pseudo-code:
// fairy-bot.js (sketch)
const { spawn } = require("child_process");
const fs = spawn("./fairy-stockfish", []);
fs.stdin.write("uci\nsetoption name UCI_Variant value chess960\nisready\n");
async function pickMove({ gameId, color, game }) {
// Convert URC's board → FEN. (~30 lines, omitted here for brevity.)
const fen = boardToFen(game);
fs.stdin.write(`position fen ${fen}\ngo movetime 1000\n`);
const move = await new Promise((resolve) => {
fs.stdout.on("data", (chunk) => {
const m = chunk.toString().match(/bestmove (\S+)/);
if (m) resolve(m[1]);
});
});
return { from: move.slice(0, 2), to: move.slice(2, 4) };
}
That's the whole thing. Host the inbox endpoint on a $5/month VPS, register an account, and you're an opponent on URC.
If you build something that consistently beats urc-bot, I'll demote urc-bot and promote yours to the default. I'm not attached to it. The point of URC was never to make my bot the best one — it was to make the platform a place where the best one wins on the leaderboard, regardless of who runs it.
Why this matters beyond chess
The "fork the bot" affordance is what changes when agents are first-class users instead of bolted-on integrations.
On lichess, if you don't like the bot that came in the bot-pack, you have… some other bot in the bot-pack. On chess.com, you have the bots they ship, full stop. The user can't replace the agent. The agent is a feature the platform owns.
On URC the agent is a participant the user can swap out. If you don't like the matchmaker, run a different matchmaker. If you don't like the rating algorithm, run a shadow rating bot that ranks games independently and publishes a separate leaderboard. Nothing in the contract privileges the platform's own agents over yours, because the platform's agents use the same API as yours.
This is what I think networked apps should look like in 2026. The platform owns the data layer and the rules. The agents — including the platform's own — are negotiable.
— Zoltan
(If you've built a bot for URC, ping me — I'll add it to a "bots in the wild" section in a follow-up post, and I'll personally lose at least one game to it on the live server.)