Skip to content

Repository files navigation

AI plays Codenames

Two teams of LLMs play Codenames against each other. Each team fields an AI Spymaster that gives clues and AI Operatives that discuss and guess, running as Mastra agents and workflows on top of models served through OpenRouter.

The Blue and Red teams can be backed by different models, letting you pit one model against another and watch a full game play out — clues, operative discussion, guesses, and win/loss conditions included.

How it works

A game is a Mastra workflow that runs until one team wins:

gameWorkflow
  └─ setupStep            create a fresh 25-card board (9/8 split, 1 assassin)
  └─ dowhile(game.winner == null)
       └─ gamePhaseRouterWorkflow   branch on the current phase
            ├─ Spymaster phase → spymasterAgent gives a { clue, number }
            └─ Operative phase → operativeAgent discusses + guesses
  • src/codenames/ — a self-contained, framework-agnostic game engine. Game (in game.ts) is an immutable state machine: Game.create() deals a board, takeAction() returns a new Game for each clue, guess, or end-turn, and it enforces all the rules (turn continuation, the "plus one" bonus guess, assassin = instant loss, win detection). wordlist.ts holds the 400-word pool; description.ts is the shared rules explainer fed to both agents.
  • src/mastra/agents/ — the spymasterAgent and operativeAgent. Each has a detailed system prompt, a per-team model selected via request context, and a helper that formats the current game state into a role-appropriate view (the spymaster sees every card's identity; operatives see only revealed cards).
  • src/mastra/workflows/ — the game loop described above. The operative phase exposes Discuss, Guess, and End turn voluntarily tools so operatives can deliberate before committing; the spymaster phase uses structured output to return a validated clue.
  • src/mastra/config/ — model selection (models.ts) and LibSQL storage (storage.ts).

Requirements

Setup

npm install

Create a .env file with:

# API key for OpenRouter (used for all team models)
OPENROUTER_API_KEY=sk-or-v1-xxx

Choosing the models

The two teams are configured in src/mastra/config/models.ts:

export const teamBlueModel = openRouter("google/gemini-3-flash-preview");
export const teamRedModel = openRouter("openai/gpt-5-mini");

Swap in any model slug OpenRouter supports to change the matchup.

Running

Start the Mastra dev server, which exposes a UI/playground for running the Game workflow and inspecting each agent's traces:

npm run dev

Then open the workflow in the Mastra playground and trigger a run (the Game workflow takes no input).

Scripts

Script Description
npm run dev Start the Mastra dev server / playground
npm run build Build with Mastra
npm start Start the built app
npm test Run the Vitest suite (game-engine tests in src/codenames/game.test.ts)
npm run lint / npm run lint-fix Lint with ESLint
npm run prettier-check / npm run prettier-write Check / apply Prettier formatting

Tech stack

  • Mastra — agent and workflow framework
  • OpenRouter via @openrouter/ai-sdk-provider — model access
  • Zod — schema validation and structured agent output
  • LibSQL — Mastra storage
  • Vitest — testing
  • TypeScript with ~/* path aliases to src/

About

AI agents play Codenames head-to-head (Mastra · OpenRouter · TypeScript)

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages