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
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.
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.
RL player. Action-masked PPO (and TRPO) on PettingZoo / RLCard Gin Rummy. Illegal
moves are blocked, so the agent always plays a legal move.
Gold standard. A hand-coded perfect player that uses RLCard's exact meld
solver. It is the measuring stick. It never trains the RL agent.
Distributed LLM stack. A master and worker setup so many GPUs can answer game
questions at once, using the ordinary Ollama API.
Curriculum system. It schedules opponents from random, to past versions of the
agent, to strong models, so the agent always gets a fair but rising challenge.
Web game. A no-install browser page where you can play any trained agent.
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.
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.
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.
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.
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 baseline
vs champion %
best vs gold %
gin % vs gold
reward: pay 3× for gin
44
37
0.38
PPO (vs TRPO)
49
35
0.62
curric: drop random late
46
34
0.42
smaller steps
47
33
0.38
reward: early-knock
51
33
0.25
curric: PFSP
45
33
0.38
reward: knock-forward
46
33
0.38
TRPO baseline
48
32
0.44
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.
Idea
Verdict
Why
Keep the best checkpoint
helps
training drifts past its peak, so saving the best one recovers 2 to 3 points for free
Warm-start from the champion
helps
start strong, then improve. better than from scratch
TRPO over PPO
helps
safer, smaller policy steps suit rare rewards and a changing opponent
Reward knocking, not gin
helps
copies the perfect player's low-risk style
Curriculum of rising opponents
helps
always a fair but harder challenge
Paying more for gin
no effect
the agent refuses the bad habit no matter the reward
Fancy opponent-picking (PFSP)
about even
no better than a simple schedule here
Longer memory (high discount)
hurts a bit
added noise, not foresight
Learned state embeddings
hurts
a fixed, squeezed input throws away useful detail
Imitation (DAgger)
fails
copies moves, not the thinking behind them
Dense short-term rewards
fails
short-sighted: greedy for points, blind to winning
Live LLM-in-the-loop
not practical
strong, but far too slow for millions of moves
Our strongest agents (checked carefully over 2000 games each)
agent
vs gold % (95% CI)
vs champion %
vs random %
gin % vs gold
Marathon Champion II
34.2 ±2.1
51.4
99
0.45
League Tactician
34.0 ±2.1
49.8
99
0.25
League Tactician II
34.0 ±2.1
50.6
99
0.80
Curriculum Ace (deadwood-coached)
33.9 ±2.1
48.0
99
0.60
Marathon Champion
33.1 ±2.1
50.1
100
0.40
Deadwood Specialist
33.1 ±2.1
47.3
99
0.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_s0
33.1%
arch_deepsets_s0
30.7%
arch_deepsets_s1
30.6%
arch_deepsets_s2
30.2%
arch_mlp_wide_s1
29.6%
arch_mlp_default_s2
29.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:
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).
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.
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 / move
10
30
60
120
win% vs the expert
10%
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 model
win% vs fair ISMCTS
search rollouts
tactician
69%
60
ace
69%
60
goldhunter
68%
60
selfplay
66%
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.
Opponent
Strength
What it is
🎲 Rookie (Random)
easiest
plays a random legal move, a gentle warm-up
🤖 Self-Play Champion
strong
our earlier best, trained against copies of itself
🃏 Curriculum Ace
strongest learned
our best agent, about 34% vs the perfect player, built by stacking every idea that helped
🛡️ League Tactician
strongest learned
a close second, trained to practise most against whoever beats it (PFSP)
🏆 Gold Standard
expert
the hand-coded expert, the wall every learned agent hits
🧠 Search Mastermind (oracle boss)
hardest
a 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
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.
We pushed a small agent from the old champion's roughly 30% up to
34% against a perfect player, by stacking the ideas that really help.
We found one clear, reusable result. The best Gin Rummy play almost never gins, and an
RL agent learns the same thing on its own no matter how you reward it.
We mapped what works (algorithm, reward style, curriculum, keep-best) and what does not
(imitation, dense rewards, learned embeddings, live LLM-in-the-loop). The honest negatives are
included, because they can save the next team a lot of time.
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
A complete, working system end to end: a masked RL learner, a perfect benchmark to grade
against, a distributed LLM server, an opponent curriculum, and a playable web game.
A universal pipeline. The same code trains on any PettingZoo game or your own
environment, not just Gin Rummy.
A lot of careful evidence: 100+ controlled runs, all judged on one fair number (win rate vs
the perfect player), with honest results for the methods that did not work.
One clean, reusable finding: the best play almost never gins, and an RL agent learns this no
matter the reward. You cannot pay it into a bad habit.
Solid engineering: a 62× faster model-serving setup, and a self-running sweep that
resubmits failures, re-collects results, and republishes this report with no human in the loop.
Fully reproducible: every figure rebuilds from saved JSON, and training curves are on
Weights and Biases.
Where it is weak (the honest part)
We did not beat the perfect player. Our best agent wins about 34% against it. This is
a clear picture of the ceiling, not a state-of-the-art win.
The main results are on one game (Gin Rummy). The universal pipeline is checked on small
games (Connect Four, Tic-Tac-Toe) with short smoke runs, not trained to convergence.
The original idea, training against a live LLM, turned out to be too slow to be practical.
The LLM ended up helping only indirectly (as a benchmark, in the design, and in an embedding
experiment that also did not help).
The methods themselves (PPO, TRPO, curriculum, keep-best) are standard. The contribution is
the careful study, the benchmark, the universal pipeline, and the honest negatives, not a new
algorithm.
We did not run a search or planning method or a memory-based agent, which are the most likely
ways to pass the ceiling. They are listed as future work, not done.
Compute was limited to one cluster, and some comparison runs are short (2M steps).
What is next, in order
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.
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.
Test richer inputs (hand-structure features) trained to convergence. We only built and
smoke-tested this.
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.
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