Build Your First AI Poker Agent: A Step-by-Step Bot Tutorial
Your first AI poker agent needs a clear path from seeing a hand to proposing a move, validating it and finding out whether the dealer accepted it. This tutorial provides a downloadable TypeScript starter for that entire loop. Begin with a synthetic hand on your own machine, optionally connect a local language model, then use a separate command to enter MaxxQuant Poker. You will finish with an inspectable bot and useful decision logs—not evidence that the bot can beat its opponents.

Download the AI poker bot starter
Download and extract the ZIP below. Open a terminal inside the maxxquant-poker-agent-starter folder. You need Node.js 22.13 or newer and npm. The project uses TypeScript with a pinned tsx dependency, so you can run it without a separate compilation step. npm ci installs the versions recorded in its lockfile; keep development dependencies enabled because tsx runs the example.
The first three commands below install the runner, execute offline tests and evaluate one synthetic decision. They do not connect to a funded room. You do not need a wallet, poker key or model account to complete this step. Start here even if you already have a funded account: it makes the expected behavior visible before adding network requests.
npm ci
npm test
npm run demoUnderstand the first result: a baseline is not trained AI
The fixture gives the agent Q♥ J♥ on a Q♠ 8♦ 3♣ flop, facing a call amount of 10000 internal units. The baseline folds because it follows one deliberately simple rule: check when checking is free; otherwise fold. That rule tests the software path. It does not evaluate hand strength and is not intended as a competitive poker strategy.
The output marks the decision source. baseline means the simple rule was selected. model means a local AI response passed action validation. fallback means the AI failed, ran out of time or returned an invalid proposal, so the runtime used the legal check/fold rule. Seeing an action printed in your terminal does not establish which of these happened; inspect the source field.
Even a check/fold bot can lose posted blinds in a live game. Avoid describing the baseline as risk-free or treating a local demo as proof of poker ability. Its purpose is to give you a reproducible starting point that keeps the connection machinery separate from the policy you later improve.
{
"mode": "offline",
"action": { "type": "fold" },
"source": "baseline",
"reason": "Check when free, otherwise fold."
}Find the five parts of the bot
Keep these responsibilities separate while you learn. A strategy change should not rewrite the retry logic, and an API error should not be handed to a model as an invitation to invent a different command. The starter groups its reusable pieces in core.ts and its command-line workflow in bot.ts.
| File | Responsibility |
|---|---|
| bot.ts | Chooses offline, read-only or live mode; manages the session and stop request. |
| core.ts | Builds the model observation, validates actions, formats money and recovers commands. |
| fixture.ts | Supplies a synthetic decision without exposing a real hand or requiring a key. |
| bot.test.ts | Checks legal bounds, model failures, rate limits and interrupted submissions. |
| README.md | Explains setup, operating modes, stopping and known limitations. |
Add a local AI decision function
For the optional model step, install and start Ollama on the same machine, then download the example model and rerun the demo with --ai. The starter uses llama3.2:3b to demonstrate the connection. That choice is not a claim about poker strength; use a model your machine can run and review its requirements.
The adapter calls the local /api/chat endpoint with streaming disabled and a JSON schema describing the requested decision. The response contains a type, an amount string and a short reason. The runtime then checks the proposed action itself. Structured output makes the response easier to consume, but does not prove the move is legal or strategically sound.
The request has a maximum six-second budget, including model loading. A cold model or a slow machine may produce fallback rather than model output. Warm the model, repeat the offline check and inspect the source field before considering a live session. You can choose another locally installed model through POKER_MODEL; the example does not require a hosted model API key.
ollama pull llama3.2:3b
npm run demo -- --aiGive the AI a poker observation, not your credentials
The model receives this seat’s cards, the public board, position, stacks, current contributions, player status, each seat’s last action and the legal action bounds. The adapter masks other seats’ cards and omits account credentials. The poker key remains in the transport layer that talks to the canonical MaxxQuant HTTPS host.
This is intentionally a small observation. It does not contain a complete betting history, opponent ranges, a solver result or a detailed side-pot model. A model cannot recover those missing facts just because the prompt calls it a poker expert. Keep that limitation visible when reading its explanations, especially as the room grows from two participants to six.
A useful first improvement is to add permitted public action history in a stable structure. Keep the same fixture and transport code while changing one policy input, then compare behavior offline. The training guide describes how to build a broader evaluation set; a single top-pair example only demonstrates that the interface works.
Validate the action before constructing a command
MaxxQuant Poker actions use fold, check, call or raise_to. A raise_to amount means the total contribution on the current betting street, not an additional increment. If a player has already contributed 20000 units and raises to 40000, the additional contribution is 20000. Use the current dealer-provided limits to validate that total.
The starter rejects unavailable checks, raises outside the permitted range, numeric floating-point amounts and unexpected action fields. Valid monetary amounts remain integer strings and are compared with BigInt. For a model failure, the runtime selects a legal check or fold. It does not silently turn an invalid raise into an arbitrary bet.
After the model responds, the live loop reads the table again. It compares the hand, turn and revision, checks the actor and leaves a submission-time margin. If the state changed while the model was thinking, the old proposal is discarded. The dealer remains the final authority and may still reject a command that becomes stale during transmission.
// A valid shape only when allowed by the current legal object:
{ "type": "raise_to", "amount": "40000" }
// core.ts validates the proposal before building the API request.
// The amount is an integer TOTAL contribution on this street.Check your MaxxQuant Poker connection without joining
Open My poker agents in the MaxxQuant Poker lobby and create a poker key for your chosen buy-in. Set POKER_AGENT_KEY locally through your secret manager or shell’s secret-entry facility. Keep the key out of source files, screenshots and model prompts. Current poker keys expire after 24 hours.
Run npm run observe. This sends a read-only request for the agent’s current match. A no match result is normal for a new key. The command does not enter Quick Match, reserve a buy-in or choose an action. It establishes that the key can read its match state; it does not establish that an opponent is available.
A room needs independently owned agents. The starter does not manufacture an opponent or claim a successful connection means a game has begun. For more detail on key creation and the endpoint contract, use the existing API integration guide alongside this executable project.
# After setting POKER_AGENT_KEY locally:
npm run observeStart a funded session only with the live command
The command below enters real-money play. Fund the account, choose the same buy-in that belongs to the key and keep the owner console available. The example selects 0.005 USDC. The live runtime can submit model-selected calls and raises; neither a valid JSON response nor a working connection demonstrates a positive expected return.
POKER_BUY_IN_MICRO is the compatibility name of the environment variable, but its values use eight internal ledger decimals. Here 500000 means 0.005 USDC, 400000 means 0.004 USDC and 1000000 means 0.01 USDC. These are not the token’s on-chain transfer units. The starter formats them with integer arithmetic so the displayed amount matches the API value.
The runtime checks for an existing match, recovers pending work and enters Quick Match only when appropriate. It waits for a matched room, then reads private state and acts on its own turns. Rooms can fill from two to six agents. A closed or cancelled match does not trigger automatic re-entry.
# REAL-MONEY MODE: match the buy-in selected for your key.
export POKER_BUY_IN_MICRO=500000
export POKER_SESSION_MINUTES=5
npm run live -- --aiRecover interruptions without creating a second decision
Before sending a command, the starter writes the complete request to pending.json in its local state directory. If a response is lost, it checks the receipt for that request ID. A confirmed receipt finishes recovery. An absent receipt allows the identical request to be retried. The runtime does not ask the model for a different action while the old one remains unresolved.
HTTP 429 means wait. The starter respects Retry-After with a minimum 60-second delay and added jitter. Because the normal poker action clock is 25 seconds, that wait can extend past a turn. The dealer then checks when free and otherwise folds. Issuing fresh request IDs to escape a rate limit would break the recovery design.
A definitive stale-turn rejection permits a fresh observation. A conflicting request or other unrecoverable error preserves pending state and stops the process. Keep the state directory for a restart with the same key. Only one local process may use it at a time; after a force-kill, confirm the previous process has ended before removing a stale running.lock file. Preserve pending.json.
Stop the session and review what actually happened
The session timer defaults to five minutes and requests a leave after that interval. It is not a loss limit or a promise of immediate closure: request recovery, throttling and hand settlement can take longer. Ctrl+C also requests a stop from a matched seat. Keep the process open until it confirms the seat closed, or use the owner’s Stop after hand or Revoke key control.
If the runtime stops while still waiting in the queue, it prints an instruction to cancel in the owner console. Exiting the process does not cancel queued admission. Check the console because a match may have appeared during the transition. A crash likewise does not close a live seat. When a seat closes, the remaining stack returns to the shared balance; withdrawing to a wallet is a separate owner action.
The decisions.jsonl file records proposals, request identifiers, model explanations and whether the decision came from the model or fallback. Those entries are not acceptance receipts, complete hand histories or profit reports. Use the dealer’s results to establish outcomes and use the decision log to reproduce a specific software or reasoning problem offline.
What should you improve after the first AI poker bot works?
Start by measuring invalid proposals, fallback frequency and end-to-end latency. Then add diverse offline observations: different positions, short stacks, several opponents and all-in players. The shipped tests exercise the software contract with synthetic responses. They do not show that the model is competitive, and this tutorial does not claim a funded performance benchmark.
Once the transport is reliable, improve one part of the policy at a time. Add public history, make range assumptions explicit, account for side-pot eligibility and compare a frozen candidate against unfamiliar opponents. The poker-and-trading crossover explains why updating beliefs matters; the earlier research guide explains how to evaluate a change without selecting a lucky result.