Julian Reder Discord Bots · 2026 - Present

Strife

Discord game platform: threaded matches, plugin games, Postgres history, crash-safe live resume. PlayCord rewrite.

2026 - Present
Pythondiscord.pyPostgreSQLasyncpgPydanticDocker
Discord thread slash, components Host lobby, session, routing play() live = replay = CLI Plugin GameContext Move log append-only, resume PostgreSQL 16 matches, players, live integrity
A Discord thread is just a view. play() is the same function for live, replay, and CLI.

Built

Slash lobbies, matches in threads, and a plugin API so the same play() runs live, in replay, and on a CLI. Postgres 16 holds guilds, matches, the move log, and live-match integrity. Games are in-process plugins — fault isolation, not a security sandbox. Builtins: Tic-Tac-Toe, Connect Four, Chess (incl. Chess960), Coup, Mafia, Spyfall, Liar's Dice.

Hard parts

  • Live matches resume after a restart by replaying the move log; abandon if the plugin build changed.
  • Lobby/thread timeouts and signed msgpack custom_id routing were the long tail.
  • Hang caps and a thread pool keep a stuck play() off the event loop.
  • Public invite exists; the bot is not hosted yet.

Learned

A game host is a durability problem: deterministic simulation, an append-only log, and 'live' as a first-class match state.

Also

  • Seven builtin games plus git-installable plugins
  • Postgres migrations through live-match integrity constraints
  • Replay / CLI / live share one play() implementation
  • Docker Compose self-host; ~27k LOC in the bot package

A game host, not a chess bot

Strife is a rewrite of PlayCord: slash lobbies, matches in threads, and a plugin API so the same play() runs live, in replay, and on a CLI. Games are in-process plugins — fault isolation, not a security sandbox. I keep saying that because “sandbox” is the word people want and not the thing I built.

Builtins: Tic-Tac-Toe, Connect Four, Chess (including Chess960), Coup, Mafia, Spyfall, Liar's Dice. Plugins can be git-installed. Public invite exists; the bot is not hosted yet. Docker Compose is how you run it.

Durability is the product

Postgres 16 holds guilds, matches, the move log, and live-match integrity. A restart resumes a live match by replaying the log. If the plugin build changed, it abandons instead of lying. That is the whole trick: deterministic simulation plus an append-only log, with “live” as a first-class match state.

The long tail was not the games. It was lobby/thread timeouts, signed msgpack custom_id routing, hang caps, and a thread pool so a stuck play() stays off the event loop. About 27k LOC in the bot package, most of it host machinery.

Discord thread slash, components Host lobby, session, routing play() live = replay = CLI Plugin GameContext Move log append-only, resume PostgreSQL 16 matches, players, live integrity
Discord is I/O. Postgres is memory. play() is the game.

What I would not hide in a cog

Piranha was the earlier chess bot: one file, Stockfish, a PNG board server. Fine for a weekend. Useless as a platform. The moment you want Coup and Chess to share resume, you need a host, not another cog.

I would still not call the plugin loader a sandbox. Process isolation is a different project. This one is honest about the boundary it actually has.