Tic-tac-toe, but every move for one side comes from Jev, a System One Model built for fast, structured decisions — unstructured state in, typed probabilistic decisions out.
Each Jev move is a structured, typed decision, not free-form text. The backend hands Jev the board as a Choice question — one labeled criterion per open cell ("center", "top-right corner", etc.) — and gets back a chosen cell, a confidence score, and a full calibrated probability distribution over every option it weighed. The frontend surfaces that reasoning move by move: expand any Jev entry in the move log to see the probability bar for every cell it considered before committing.
It's a small decision space — 9 cells, at most 8 legal moves at any turn — which makes Jev's reasoning easy to watch end to end, move by move.
POST /gamescreates a game with a chosen human symbol and who goes first.POST /games/{id}/movessubmits a human move, then — if the game isn't over — asks Jev for its move.- On each Jev turn,
JevClient.choose_move(backend/app/jev_client.py) builds aChoicequestion whose criteria are only the currently-empty cells, sends the board as natural-language state, and gets back aChoiceAnswer: the chosen cell, its confidence, and the probability Jev assigned to every candidate cell. - The full move history — including Jev's confidence and per-cell probabilities — is kept on the game state and returned to the frontend, which renders it live in the move log.
backend/ FastAPI service: game state, rules, and the Jev client
frontend/ React + Vite UI: board, setup form, move log with probability bars
assets/ Screenshots and a recording of the app in use
Requires Python 3.12+, uv, and a Jev API key.
cd backend
cp .env.example .env # then set JEV_KEY to your key
uv sync
uv run uvicorn app.main:app --reloadThe API listens on http://localhost:8000 and only allows CORS from http://localhost:5173 (the Vite dev server).
Run the backend tests:
uv run pytestRequires Node.js.
cd frontend
npm install
npm run devThe dev server runs on http://localhost:5173 and talks to the backend at http://localhost:8000 by default (override with VITE_API_BASE_URL).
Run the frontend tests:
npm testThe backend reads settings from backend/.env (see backend/.env.example):
| Variable | Required | Description |
|---|---|---|
JEV_KEY |
yes | API key for Jev |
JEV_BASE_URL |
no | Override the default Jev API base URL |
JEV_DEFAULT_MODEL |
no | Override the default Jev model |
JEV_TIMEOUT |
no | Request timeout in seconds (default 10.0) |



