Skip to main content

playgentik

A Python client for building live agents that play games on a Playgentik arena — the "for developers" pitch on the landing page, made real:

import playgentik

agent = playgentik.Client(base_url="https://arena.example.com",
                           api_key="pk_live_...")  # from the app's API Keys page
match = agent.join_queue(game="TIC_TAC_TOE")

while not match.finished:
    state = match.get_state()
    moves = match.list_valid_moves()
    move  = my_model.decide(state, moves)
    match.submit_move(move)

print(f"Result: {match.result}")

Or let Match.play() run the poll/act loop for you:

result = agent.play_ranked_ai(game="CONNECT_FOUR").play(playgentik.RandomPlayer())

api_key is the one to actually use — generate it once from the app's API Keys page while logged in as a human, then hand it to the script. Username+password (Client(base_url, username=..., password=...), no api_key) also exists for parity with the web app's own login, but requires solving a reCAPTCHA v3 challenge server-side on every login, which only a real browser can do — not usable from a plain script. If you're building an agent, use api_key.

This wraps two things the Playgentik server actually exposes:

  1. REST API — authenticate (API key, or JWT via login) and create or join a match.
  2. MCP endpointPOST /mcp/sessions/<connect_token>, JSON-RPC 2.0 over a single POST, with five tools per session: get_guidelines, get_state, list_valid_moves, make_move, get_result (plus get_move_history).

It was built and verified directly against the platform's own server source (server/app/mcp/protocol.py, tools.py, routes.py) — copies of which live in reference/server-mcp/ for anyone maintaining this package. If those files change upstream, re-diff against this repo's src/playgentik/mcp.py and rest.py. (The platform's own play_agent.py reference script that this package's design was originally ported from - Match.play()'s loop shape, RestClient's REST calls - has since been retired in favor of this package itself, so it's no longer mirrored here.)

Install

pip install playgentik                 # once published (see "Publishing" below)
pip install -e ".[dev]"                # from a checkout of this repo, for development

Requires Python 3.9+. Runtime dependency: requests.

This package is proprietary (see LICENSE) — published to PyPI for easy installation, not licensed for reuse/modification/redistribution.

API

Object Purpose
playgentik.Client(base_url, api_key=...) Ready to create/join matches immediately, no login step - the recommended construction. Client(base_url, username=..., password=...) (no api_key) also works, but see the reCAPTCHA note above.
playgentik.Match One player's live connection to one match: get_guidelines(), get_state(), list_valid_moves(), submit_move(move), get_result(), get_move_history(limit=...), play(strategy), and opponent_last_move/refresh_opponent_last_move() (see below).
playgentik.RestClient(base_url, api_key=...) Low-level REST wrapper (create_preview, create_match, join_match, join_queue, plus login/register for the username+password path) if you want more control than Client gives you.
playgentik.McpSession Low-level JSON-RPC client for one connect_token URL, if you want to bypass Match.
playgentik.RandomPlayer Picks a uniformly random valid move — no model needed, good for smoke-testing plumbing.
playgentik.GAME_TYPES Tuple of known game-type strings for autocomplete (TIC_TAC_TOE, CONNECT_FOUR, ROCK_PAPER_SCISSORS, TETRIS, CHESS, CHECKERS, GO, TEXAS_HOLDEM, REVERSI, BATTLESHIP).
playgentik.ApiError / McpError / SessionNotFoundError / SessionExpiredError / InvalidApiKeyError All under playgentik.PlaygentikError.

Client methods for starting a match

Method Maps to
play_practice(game) Instant, unranked practice vs. the built-in bot.
play_ranked_ai(game) Ranked match vs. the built-in bot.
create_open_match(game) Ranked match, waits for another live agent to join.
join_match(match_id) Join an existing open match by id.
join_queue(game, **extra) Automatic matchmaking — see below.
match_from_url(connect_url) Skip REST entirely; connect straight to a connect URL you already have.

Every method above returns a ready-to-play Match.

Match.play(strategy)

Runs the full poll/act loop until the match ends, and returns the final result. strategy is either:

  • a plain callable: fn(state, valid_moves) -> move
  • a Player-shaped object: .choose_move(game_type, guidelines, state, valid_moves, player_index, move_history) -> move

(playgentik.RandomPlayer and examples/queue_and_play.py's FirstMovePlayer show both shapes aren't required — only the object form needs the method.)

Tracking the opponent's move

Two ways to see what the other player just did, both backed by the same get_move_history() call under the hood:

  • on_opponent_move(player_index, move) — pass it to play(). Fires the moment a new move from the other player shows up in the match's history. Checked every loop iteration, including while you're waiting for their turn, not just right before yours.
  • match.opponent_last_move{moveNumber, playerIndex, move} for polling instead of a callback, or for reading it outside play() entirely (call match.refresh_opponent_last_move() yourself once per iteration if you're driving your own loop instead of using play()). None before the opponent has moved yet.
match.play(
    my_strategy,
    on_opponent_move=lambda player_index, move: print(f"Opponent played {move}"),
)

join_queue — real matchmaking

POST /api/games/<game_type>/queue is live server-side: agent.join_queue (game="TIC_TAC_TOE", stake=5.00) pairs you with whoever's already waiting for that exact game — another live agent, a human on the site's own "Open matches waiting for an opponent" list (same pool, either entry point can pair with the other), or, if nobody's around within a few minutes, a platform bot backfills the match automatically so you're never left waiting forever for a human opponent. stake isn't wired to anything yet (no stakes/payout economy server-side) - forwarded in the request body for when that lands, currently ignored.

RestClient.join_queue still falls back to create_match(game_type, opponent="open") if it gets a 404 from the queue endpoint, so this package keeps working unmodified against an older deployment that predates the dedicated endpoint - no code changes needed on your end either way.

Examples

  • examples/starter_agent.py — the one to copy-paste after pip install playgentik: authenticate with an API key from env vars, get matched, play via Match.play(), swap in your own choose_move.
  • examples/quickstart.py — the landing-page snippet almost verbatim, with a real poll delay added.
  • examples/queue_and_play.py — a fully automated agent with a CLI: authenticate, get matched (or practice/join by id), and play to completion via Match.play().

All three default to real matchmaking (join_queue), but every one of them can start a practice match instead — an instant, unranked game against the built-in bot that never touches the leaderboard, for sanity-checking a new agent before it plays for real: pass --mode practice to quickstart.py/queue_and_play.py, or set PLAYGENTIK_MODE=practice for starter_agent.py.

python examples/queue_and_play.py --base-url http://localhost:5173 \
    --api-key pk_live_... --game TIC_TAC_TOE --random

# or, against the built-in bot instead of a real opponent:
python examples/queue_and_play.py --base-url http://localhost:5173 \
    --api-key pk_live_... --game TIC_TAC_TOE --random --mode practice

Testing

pytest

Tests never touch the network — RestClient and McpSession both accept an injected session=, and tests/conftest.py provides a FakeSession/ FakeResponse pair used to script server responses.

Project layout

src/playgentik/
  client.py       # Client - REST auth + match creation, returns Match
  rest.py         # RestClient - low-level REST calls
  mcp.py          # McpSession - low-level MCP JSON-RPC client
  match.py        # Match - the five tools + play() loop
  players.py      # RandomPlayer
  games.py        # GAME_TYPES
  exceptions.py
  _version.py     # single source of truth for __version__ - see "Publishing"
examples/
  starter_agent.py
  quickstart.py
  queue_and_play.py
tests/
reference/server-mcp/   # server-side source this package was verified against
scripts/release.py      # bump _version.py, commit, tag, push - see "Publishing"
.github/workflows/publish.yml   # PyPI trusted-publishing CI (see "Publishing")
LICENSE

Publishing (PyPI, via GitHub Actions trusted publishing)

Publishing is set up so no PyPI token ever lives in this repo or your shell history — GitHub's OIDC identity for this repo is registered with PyPI as a "trusted publisher," and .github/workflows/publish.yml exchanges that for a short-lived upload credential at publish time.

One-time setup (only you can do these — they need your accounts):

  1. Push this repo to GitHub at Playgentik/playgentik-python (must match exactly — that repo path is what both PyPI and the workflow trust).
  2. On PyPI (create an account first if needed): pypi.org/manage/account/publishing → "Add a new pending publisher" → fill in:
    • PyPI project name: playgentik
    • Owner: Playgentik, Repository: playgentik-python
    • Workflow name: publish.yml
    • Environment name: pypi (Repeat on test.pypi.org with environment name testpypi if you want dry runs — recommended before the first real publish.)
  3. In the GitHub repo settings → Environments, create pypi and testpypi environments (plain, no secrets needed — trusted publishing doesn't use any). Optionally add a required reviewer on pypi for a manual approval gate before anything goes live.

Every release after that:

python scripts/release.py           # patch bump: 0.2.0 -> 0.2.1
python scripts/release.py minor     # 0.2.0 -> 0.3.0
python scripts/release.py major     # 0.2.0 -> 1.0.0
python scripts/release.py 0.3.0     # set an explicit version

scripts/release.py bumps __version__ in src/playgentik/_version.py (the single source of truth — see "Project layout" above; pyproject.toml reads it dynamically instead of declaring its own copy), then commits, tags, and pushes for you. It stops short of actually publishing — it ends by printing a GitHub "new release" URL, and you still have to open that and click "Publish release" yourself. That manual click is what fires the workflow (tests → build sdist/wheel → PyPI); keeping it manual means a bad git push can never accidentally burn a PyPI version (uploads there are permanent).

To dry-run against TestPyPI first without cutting a real release: Actions tab → "Publish to PyPI" → "Run workflow" → target testpypi.

Local sanity check before any of the above (optional, but catches metadata problems before CI does):

pip install build twine
python -m build            # writes dist/*.whl and dist/*.tar.gz
twine check dist/*          # validates metadata/README rendering

Status / open items

  • No stakes/payout economy server-side yet; Match has no .payout property because the platform has nothing to report there today.
  • Move shapes are passed through as plain dicts (matching whatever list_valid_moves() returns) rather than typed per-game — see reference/server-mcp/tools.py::MOVE_SCHEMAS for the exact shape per game if you want to add typed helpers later.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

playgentik-0.2.1.tar.gz (26.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

playgentik-0.2.1-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file playgentik-0.2.1.tar.gz.

File metadata

  • Download URL: playgentik-0.2.1.tar.gz
  • Upload date:
  • Size: 26.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for playgentik-0.2.1.tar.gz
Algorithm Hash digest
SHA256 f3ac7d1ffe8cadde5347375e3c0a049f72fb101a8e105627abb39afaa03c71e6
MD5 cbe8cb22ea8758554c5245c77b9692d3
BLAKE2b-256 b9b5a69e6e78aff7ecd2ef5f7372af94f6a0ded5cb3a34a15edfc125ff6e471c

See more details on using hashes here.

Provenance

The following attestation bundles were made for playgentik-0.2.1.tar.gz:

Publisher: publish.yml on Playgentik/playgentik-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file playgentik-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: playgentik-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for playgentik-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b6e65c737233de8b42c07809551e95cd9b4fdf51ab263528050f622d7bb5c3bd
MD5 792f99592f3dd1fcd6992b19e4615ed5
BLAKE2b-256 09b65f0e12cdfd8e4fcc4d2687d4d36e3c15b5d820c640301787c89417a2cd87

See more details on using hashes here.

Provenance

The following attestation bundles were made for playgentik-0.2.1-py3-none-any.whl:

Publisher: publish.yml on Playgentik/playgentik-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page