Adversarial Co-Evolution of RL and LLM Agents in Gin Rummy

We built a full system to train a small reinforcement-learning (RL) card player, a perfect "gold standard" opponent to measure it against, and a way to put a large language model (LLM) into the game. Then we ran 100+ experiments to answer one question. How close can a small RL agent get to perfect play, and what really makes it stronger? We use Gin Rummy as a clean example, but the system and the lessons carry over to other games and to RL plus LLM agents in general.

Nima Kelidari · Mohammadsaeed Haghi · Mahdi Salmani · University of Southern California
34%
best agent vs the
perfect player
<2%
how often the perfect
player gins
106+
controlled training
runs in the sweeps
62×
faster LLM serving
(scratch vs NFS)

1The whole story, in one minute

Gin Rummy is a card game that needs two skills at once. You have to count fast (your "deadwood" is the cards that do not yet fit a pattern) and you have to plan ahead (you build "melds", which are runs and sets). It is a good test because it is easy to score but hard to play well, and you never get to see your opponent's cards.

Training an RL agent has a chicken-and-egg problem. We call it the opponent bottleneck. An agent is only as good as the players it practises against. Practise against a weak player and you pick up weak habits. So we built three things. A fast RL player, a perfect gold-standard opponent to grade everyone fairly, and a way to use a slow but smart LLM as a teacher. Then we tried almost every sensible way to make the RL agent stronger, and we measured each one against the perfect player.

Win-rate vs the perfect player across the project
The climb. Our best agent went from the old champion's roughly 30% up to 34% against the perfect player. It got there through a careful search, not luck.
70 to 99%
the perfect player beats every learned agent
TRPO > PPO
the algorithm choice that helped
knock, don't gin
the reward lesson that held across 60 runs
DAgger & live LLM
honest results: neither beat plain RL

The short version, up front. No single trick beat the perfect player. It is a very high bar. But when we stacked the ideas that really help (a better algorithm, a reward that copies the perfect player's style, a curriculum of stronger and stronger opponents, and always keeping the best checkpoint), we pushed a small agent up to 34% wins against perfect play. We also found one clear, solid result along the way. That is the next section.

2The game, and why it is hard

Each turn you draw a card and throw one away. You are trying to line up your 10 cards into melds (a run like 5, 6, 7 of hearts, or a set like three 9s). Cards that do not fit a meld are deadwood. You win by knocking (ending the hand with low deadwood) or by gin (zero deadwood, which gives a big bonus but is rare and risky). You never see the opponent's hand, so you have to play with hidden information.

a single turn (what the agent sees and chooses) hand: 4 planes x 52 cards ──▶ [ masked PPO policy ] ──▶ draw / pick-up / discard / knock / gin (your cards, the top discard, │ known picks, the rest) └─ illegal moves are blocked (logits -> -inf), so the agent only ever picks a legal move
Why use a game like this? It needs both skills at the same time, fast counting and long-term planning. And the rules give an exact score, so we can build a perfect reference player and grade everything against it. That reference is what makes the rest of this report trustworthy.

3What we built: the framework

Most of the work was building a system where the RL agent, a perfect expert, and an LLM can all meet in the same game. There are five pieces.

System overview
How the pieces connect. The gold standard is used for scoring only. It never trains the agent.

The distributed LLM server (so a 7B model can keep up with RL)

One RL training run asks the opponent tens of thousands of questions. At 0.5 to 3 seconds per call, a simple loop would take hours per round. So we keep the LLM serving separate from the training loop.

env subprocess ─▶ Master (CPU, FastAPI) ─▶ suit-symmetry cache ──(hit)──▶ return (per-step query) Ollama-compatible API │ miss │ round-robin ▼ ┌───────────────┼───────────────┐ ▼ ▼ ▼ GPU worker GPU worker … GPU worker (1 GPU each, Qwen2.5-7B) self-registers in a shared-filesystem registry; master health-checks + balances
One infrastructure lesson that really mattered. Loading a 7B model from home network storage runs at about 11 MB/s, which takes around 28 minutes and trips the health-check timeout. Putting the weights on fast scratch storage (BeeGFS) cuts that to about 27 seconds. That is a 62× speedup, and it is a must at scale. With about 14 workers the pool handled about 32 questions per second.

4The gold standard, and the surprise it revealed

To grade everyone fairly we built a perfect Gin Rummy player. Every turn it works out the best possible melds (exact, not learned) and knocks the moment it should. It is the benchmark. It is never a teacher.

Gold beats everyone but rarely gins
Left: the perfect player beats every learned agent (70 to 99% of games). Right: but it gins under 2% of the time.
The surprise, and our cleanest result. The perfect player almost never gins. It gins in just 0.7 to 1.7% of games, even though gin scores the most points. It wins by knocking early with low deadwood. That goes against what we expected. Chasing gin is a beginner's trap. The best style is patient, low-risk knocking. This one fact changed how we thought about every reward test that follows.

5Everything we tried, and why each one worked or didn't

We tested almost every reasonable way to make the agent stronger, and we judged them all the same way: win-rate against the perfect player. Here is the full picture, from weakest to strongest.

Every regime ranked vs the perfect player
Each bar is a win-rate we actually measured against the perfect player.

🎲 Train vs random only

98 to 99% vs random, but only ~15% vs gold

Why: random opponents are too weak to teach real strategy. The agent maxes out the easy score and learns to always knock and never gin. Only a thinking opponent can break that habit.

🃏 Reward shaping (gin vs knock)

it controls behaviour: 97% knock vs 22% gin

Why: the gin to knock reward ratio is a real dial. Pay more for gin and the agent chases gin, and wins less. This is the dial the rest of the project turns.

🤖 Self-play + pool curriculum

self-play beats its own parent 61%

Why: playing past versions of yourself is a free, rising curriculum. But a pool with no guidance broke down after about 10M steps and chased itself into a corner. The design of the curriculum matters.

🧠 LLM as opponent

good (beat our RL agent 3 to 2) but ~9 to 27 s/move

Why: mid-size LLMs (Qwen2.5-7B, gpt-oss-20b) play real Gin Rummy with the right prompt, and even beat our self-play agent in short matches. But they are too slow for the millions of moves RL needs. Training against a live LLM would take weeks. (Vision LLMs failed completely. This is a text task.)

📝 Imitation learning (DAgger)

it collapsed to almost no wins

Why: copying an expert's moves one at a time does not carry over. The student copies moves in familiar spots but never learns the thinking behind them (like tracking the opponent), so it falls apart in new situations. Copying a move is not the same as understanding why.

⏱️ Dense / short-term rewards

short-sighted, stops improving

Why: rewarding every small step makes the agent greedy for instant points and blind to the real goal of winning the hand. It stopped improving after about 500k steps. The simple reward at the end of the hand won.

📊 Algorithm: PPO vs TRPO

TRPO ~22% vs PPO ~15% vs gold

Why: TRPO takes smaller, safer steps when it updates the policy, which fits a setting where rewards are rare and the opponent keeps changing. (GRPO and DPO do not apply here. They are methods for aligning language models, with no per-move game version.)

🔗 Learned state embeddings

all worse than the raw input

Why: we squeezed the big, sparse board into a small dense vector two ways: one that learned which game states are close together, and one where an LLM judged how similar two states are. Both lost to the raw input. A fixed, squeezed vector throws away detail the agent needs.

🏅 Curriculum sweep (Phase 6)

best ~33% vs gold (30 runs)

Why: a careful ladder of opponents (random, then past selves, then strong models), swept over algorithm, reward, and schedule. Everything leveled off near champion strength, but it showed clearly which dials matter (see the key finding).

⭐ Keep-best + warm-start (Phase 7)

best agent 34% vs gold

Why: three fixes stacked together. Always save the best checkpoint (training drifts past its peak), start from the previous champion instead of from scratch, and add a reward for lowering your own deadwood that teaches the optimal style. This is our strongest agent.

6The key finding: you cannot bribe the agent into ginning

Phase 6 ran 30 controlled experiments, changing one thing at a time across algorithm, reward, and curriculum. The honest headline is that every recipe ends up near champion strength against the perfect player. But one result is clear and holds across all of them.

Gin rate stays under 1% for every reward
Left: no matter the reward, even paying 3× for a gin, the agent gins under 1% of the time. Right: the "knock early" reward gives the shortest games.
What it means, in plain terms. We tried to bribe the agent into ginning by paying three times more for a gin than a knock. It still gins under 1% of the time, the same as when gin is not rewarded at all. You cannot pay an agent into a bad habit. Just like the perfect player, it works out on its own that chasing gin loses. The reward that did help was the opposite, a small push to knock faster, which gave the shortest games and the best play.

The numbers per recipe (each one averaged over its seeds, best checkpoint vs the perfect player):

one change from the baselinevs champion %best vs gold %gin % vs gold
reward: pay 3× for gin44370.38
PPO (vs TRPO)49350.62
curric: drop random late46340.42
smaller steps47330.38
reward: early-knock51330.25
curric: PFSP45330.38
reward: knock-forward46330.38
TRPO baseline48320.44
Skill rises through the curriculum
How the curriculum drives learning: win-rate vs the champion climbs as tougher opponents are swapped in (random, then pool, then self, then strong). The late dip on one run is the drift that "keep the best checkpoint" fixes.

7What moved the needle, and what didn't

Across 100+ runs, here is the honest summary of which ideas actually helped against the perfect player. Most of these are general lessons about training agents, not tricks special to this one game.

IdeaVerdictWhy
Keep the best checkpointhelpstraining drifts past its peak, so saving the best one recovers 2 to 3 points for free
Warm-start from the championhelpsstart strong, then improve. better than from scratch
TRPO over PPOhelpssafer, smaller policy steps suit rare rewards and a changing opponent
Reward knocking, not ginhelpscopies the perfect player's low-risk style
Curriculum of rising opponentshelpsalways a fair but harder challenge
Paying more for ginno effectthe agent refuses the bad habit no matter the reward
Fancy opponent-picking (PFSP)about evenno better than a simple schedule here
Longer memory (high discount)hurts a bitadded noise, not foresight
Learned state embeddingshurtsa fixed, squeezed input throws away useful detail
Imitation (DAgger)failscopies moves, not the thinking behind them
Dense short-term rewardsfailsshort-sighted: greedy for points, blind to winning
Live LLM-in-the-loopnot practicalstrong, but far too slow for millions of moves

Our strongest agents (checked carefully over 2000 games each)

agentvs gold % (95% CI)vs champion %vs random %gin % vs gold
Marathon Champion II34.2 ±2.151.4990.45
League Tactician34.0 ±2.149.8990.25
League Tactician II34.0 ±2.150.6990.80
Curriculum Ace (deadwood-coached)33.9 ±2.148.0990.60
Marathon Champion33.1 ±2.150.11000.40
Deadwood Specialist33.1 ±2.147.3990.40

7bLatest experiments: does a smarter network, or search, break the ceiling?

Network architecture vs the perfect player (from scratch, relative)

architecture (cell)win% vs perfect
arch_conv1d_s033.1%
arch_deepsets_s030.7%
arch_deepsets_s130.6%
arch_deepsets_s230.2%
arch_mlp_wide_s129.6%
arch_mlp_default_s229.6%

Everything clusters in a narrow band (mid-20s to low-30s). Structured encoders (convolutional and permutation-invariant set / DeepSets) edge nominally above the plain MLPs, but the per-cell confidence intervals overlap — the architectures are statistically indistinguishable, and making the plain network wider or deeper barely moves it. That is strong evidence the ceiling is information-bound, not capacity-bound. These are from-scratch runs, so a relative comparison.

A search baseline (ISMCTS), graded the same way

ISMCTS stands for Information-Set Monte-Carlo Tree Search. It is not a trained network and it does no learning at all — it is a planner that thinks fresh on every single turn. The hard part of this game is that you cannot see the opponent's cards, and ISMCTS deals with that by imagining. On each turn it:

  1. Guesses the hidden cards. It deals out many possible arrangements of the unseen cards that are consistent with everything it has actually observed (its own hand, what was discarded, what was picked up).
  2. Plays each guess out. For every move it is considering, it quickly simulates the rest of the hand to the end, many times, across those imagined deals.
  3. Picks what wins most. It plays the move that came out best on average across all those imagined worlds.

The number of "rollouts per move" is how many of those imagined playouts it runs — more rollouts means more imagined worlds explored, so stronger play and a little more thinking time per move. That is exactly the trade-off below:

These are the fair numbers: the search re-deals the hidden cards every rollout and never sees the opponent's true hand.

rollouts / move103060120
win% vs the expert10%17%21%26%

The surprise: played fairly, the search is weak — even at 120 rollouts it wins only about 26% vs the gold expert, below our trained agents (34%). Gin Rummy hides a lot (the opponent's whole hand plus the deck), so averaging over guessed deals is very noisy. For contrast, an oracle version that is allowed to peek at the hidden cards jumps to ~85%. That gap — 26% fair vs 85% oracle — is essentially the value of the hidden information, which is strong evidence the ceiling is information-bound: with the information you win easily; without it, even search cannot. (The in-game 🧠 Search Mastermind boss uses the oracle version so it is actually hard to play; the paper reports the fair number.)

How do our trained agents do against the search, head-to-head?

We also played each learned agent directly against the fair search. This is a like-for-like fight between a fast trained network and a slow planner:

trained modelwin% vs fair ISMCTSsearch rollouts
tactician69%60
ace69%60
goldhunter68%60
selfplay66%60

Every trained agent is comfortably above 50% — they beat the fair search head-to-head. So at these budgets the learned agents are the stronger players; naive search is not a free win when the hidden information is large.

Is the architecture result a fluke of one recipe?

No. Re-run under different recipes (a different algorithm, a different opponent schedule), the better networks stay on top — best here is sc_ppo_asym_s0 at 30.2%. The ranking carries across recipes.

Does the method transfer to a second game?

Yes. On Leduc Hold'em, a tiny poker where the perfect strategy can be computed exactly, a simple learner graded against that perfect strategy reaches near parity (mean return -0.08 over 8 seeds; random is about −0.78). So the "grade against a fixed perfect player" idea works on a game where we can check it against the true optimum. A neural baseline (NFSP) needed far more games and was still converging (mean -0.71 over 4 seeds at the time of writing).

Live numbers refresh automatically as overnight runs land (python sweep/collect_phase8.py).

8Play the heroes yourself

A no-install web game lets you play our strongest agents, with smooth card animations. The opponent menu is a simple ladder, from a beginner-friendly bot up to the perfect player that nobody beats.

OpponentStrengthWhat it is
🎲 Rookie (Random)easiestplays a random legal move, a gentle warm-up
🤖 Self-Play Championstrongour earlier best, trained against copies of itself
🃏 Curriculum Acestrongest learnedour best agent, about 34% vs the perfect player, built by stacking every idea that helped
🛡️ League Tacticianstrongest learneda close second, trained to practise most against whoever beats it (PFSP)
🏆 Gold Standardexpertthe hand-coded expert, the wall every learned agent hits
🧠 Search Mastermind (oracle boss)hardesta Monte-Carlo search (no training) that is allowed to peek at your hand — that is what makes it brutally strong (~75–85% vs the expert). Played fairly, with cards hidden, the same search is much weaker (~26%); we report that honest number in the paper
web game
The browser game (debug view, opponent hand shown). Run python game/server.py and open the URL.

9The bottom line, and what's next

We set out to see how close a small, fast RL agent could get to perfect play, and what really makes it stronger. We built the whole framework to answer that fairly, then ran the experiments. Here is the short, honest summary.

This is not really about Gin Rummy. We used Gin Rummy as a clean example, because it has hidden information, both fast and slow planning, and an exact score. The pieces and lessons carry over to other two-player games and to RL plus LLM agent systems in general: build a strong reference to grade against, train against a rising ladder of opponents, copy the style of strong play through the reward, always keep your best model, and do not expect imitation, dense rewards, or a slow model in the training loop to do the heavy lifting. To make this concrete, the code is shipped as a universal pipeline (the coev/ package): point it at any PettingZoo game, or your own environment, and it trains a masked agent through the same opponent curriculum. Gin Rummy is the test case, not the point.

The ceiling, honestly. Tuning the reward, the algorithm, the opponents, and even the network architecture all top out around the mid-30s percent against the expert. We tested whether classic search breaks through, and it does not: a fair, no-peek Monte-Carlo search is actually weaker (about 26%), while the very same search allowed to see the hidden cards jumps to ~85%. That contrast is the key result — the ceiling is set by the hidden information, not by the method or the network. To go clearly past it you would need more information (better opponent-card inference, or vastly more search), not just more reward tuning.

10Roadmap: strengths, limits, and what is next

Here is an honest look at where the project stands today, what is strong, what is weak, and what we would do next to turn it into a paper.

What is strong

Where it is weak (the honest part)

What is next, in order

  1. Train the universal pipeline to convergence on two or three non-Gin PettingZoo games, to show the generality with real numbers, not just smoke tests.
  2. Add one search or planning baseline (counterfactual-regret or a small lookahead at decision time) and one memory baseline (a masked recurrent agent with a memory-aware evaluator), and see if either passes the 34% ceiling.
  3. Test richer inputs (hand-structure features) trained to convergence. We only built and smoke-tested this.
  4. Try the offline-RL path: build a large dataset of LLM-vs-LLM games and learn from it offline, so the slow LLM is used once, not inside the training loop.
  5. Tighten the statistics on the headline runs (more seeds, paired confidence intervals) and turn the gold benchmark plus the curriculum harness into a small public benchmark suite.
In short: the contribution is the universal pipeline, plus the perfect-player benchmark, plus the study of what helps and what does not.

How to reproduce

Every figure on this page is rebuilt from saved JSON results by paper/make_figures.py, and this page by paper/make_report_html.py. The sweeps run as SLURM array jobs with a watchdog that resubmits failed runs, re-collects the results, and republishes this page on its own, with no human in the loop.

# train on ANY game with the universal pipeline (coev/)
python -m coev.examples.connect_four    # any PettingZoo game, no game-specific code
python -m coev.examples.gin_rummy       # same pipeline + a gold benchmark and reward shaping

# play the web game
python game/server.py --host 127.0.0.1 --port 8000      # open http://127.0.0.1:8000

# the gold-standard benchmark
python sweep/bench_gold.py

# the final curriculum + keep-best sweeps (SLURM array + watchdog)
python sweep/curriculum_configs.py && sbatch --array=0-29%10 slurm/curriculum.slurm
python sweep/phase7_configs.py    && sbatch --array=0-8%6 --export=ALL,CFG_DIR=phase7_cfgs slurm/curriculum.slurm

# regenerate this report
python paper/make_figures.py && python paper/make_report_html.py

The typeset paper is the arXiv version. Live training curves are on Weights & Biases (groups phase6-curriculum, phase7-ceiling).

Built from measured results · every number traces to a JSON file under sweep/ · Adversarial Co-Evolution · USC