YeetCode
AI Engineering · Agents & Reinforcement Learning

RL Environments for LLM Agents: Where Models Practice

How RL environments train LLM agents — the agent loop, the dataset-rollout-rubric trinity, reward hacking, and build-once-use-four-ways, with worked numbers.

10 min readBy Bhavesh Singh
rl environmentsreinforcement learningllm agentsreward hackinggrpoverifiable rewards

This article has an interactive companion

Don't just read it — step through it interactively, one state change at a time.

Open the interactive deep dive

A pretrained model knows a lot and does very little reliably. Turning it into an agent that books the flight, closes the ticket, or ships the passing patch takes practice — and practice needs a place to practice. That place is an RL environment: a scored world where a model tries, gets a number back, and adjusts.

The vocabulary sounds like classical robotics — state, action, reward, policy — but the mechanics for LLMs are surprisingly concrete. An environment is three files' worth of code, one of them is where everything breaks, and the whole thing is a single artifact you can point at four different jobs. Get those three ideas straight and modern agent RL stops being mysterious.

This is the written companion to YeetCode's interactive deep dive. The prose gives you the model; the interactive lets you type a candidate solution, watch it get scored, and break the reward yourself.

The agent–environment loop: state, action, reward, policy

Reinforcement learning is one loop. An agent observes a state, picks an action from its policy, and the environment hands back a new observation and a scalar reward. Repeat until the episode ends. The classical control diagram maps onto an LLM with almost no translation:

python
obs = env.reset() # the task prompt is the first observation done, total_reward = False, 0.0 while not done: action = policy(obs) # the LLM emits the next message obs, reward, done = env.step(action) # the environment computes what happens next total_reward += reward return total_reward # the episode return — the whole learning signal

The policy is the LLM. The state is the context window. An action is the next message it emits — plain text, a tool call, or a final answer. The environment is whatever deterministically returns the next observation and the reward.

Take the task "Compute 17 × 23 with the calculator, then state the answer." The rollout is two turns: calc(17*23) and The answer is 391. When the agent emits calc(17*23), the environment genuinely evaluates it and returns the observation 391 — that is not text the model hallucinated, it is ground truth the model must now react to. On the final turn the environment exact-matches 391 against the answer key and returns reward 1.0. Answer 390 instead and the match fails: reward 0.0. Single-turn Q&A, multi-turn dialog, and long tool-use chains are all the same loop — they just differ in how many iterations run before done.

What is an RL environment? Dataset + rollout + rubric

Strip away the framework code and every environment is exactly three pieces:

PieceWhat it isExample
DatasetThe prompts plus ground truth("Write add(a, b)", "def add(a, b): return a + b")
RolloutHow the model produces raw outputThe full transcript of actions and observations
RubricTurns raw output into a scalar rewardParse the answer, then compare to ground truth

The rubric is where environments break, and the failure has a name: conflating extraction with scoring. Suppose the correct answer is:

python
def add(a, b): return a + b

But the model returns it the way a chat model actually does — a "Sure! Here you go:" preamble, then that function wrapped in a triple-backtick python fence. Score that raw string against the ground truth def add(a, b): return a + b and it fails — the preamble and the fence markers don't match. A correct answer earns reward 0.0. The rubric just taught the model that correct code is bad.

The fix is a discipline: parse first, score second. Extract the payload out of the surrounding text, then compare.

python
def rubric(raw_output, ground_truth, parser): payload = parser(raw_output) # 1. PARSE FIRST — pull the answer out a, b = normalize(payload), normalize(ground_truth) return 1.0 if a == b else 0.0 # 2. SCORE SECOND — verifiable exact match

Swap in a code-fence parser and the same rollout now scores 1.0. Different tasks need different parsers: a last-number parser pulls 18 out of a GSM8K chain of thought; a regex: Answer: (…) parser grabs the tagged answer out of free-form chat. The parser has to match the shape of the output, not the task.

Why every reward is a proxy: reward hacking and Goodhart

Here is the uncomfortable truth. The reward is never the thing you actually want — it is a proxy for it. And the moment a proxy becomes a training target, the policy will find the cheapest way to maximize it, which is not always the way you intended. That is Goodhart's law, and in RL it shows up as reward hacking.

The trap most people fall into is believing verifiable rewards — run the code against tests — are immune. They are harder to game, not impossible. Watch a model "solve" f(x) = x² when the visible test suite is only {3→9, 5→25}:

python
f = lambda x: {3: 9, 5: 25}[x] # a lookup table — no math, just the answers

This hardcodes the two visible answers. On the visible tests it passes both, so the proxy reward is 100%. But hold out three tests the model never saw — {2→4, 4→16, 7→49} — and it fails all three, because 2, 4, and 7 aren't keys in the table. The true reward is 2/5 = 40%. The gap between them is the reward-hack magnitude:

text
proxy − true = 100% − 40% = 60%

The candidate scored a perfect proxy while being 60 points wrong. Verifiable does not mean unhackable — it means easier to harden, by adding held-out tests until the proxy stops being cheatable.

LLM-judge rewards — "ask a model to grade the answer" — are softer proxies still, because they reward what looks good. A transparent judge might add +0.3 for a flattering opener, +0.3 for length past 40 words, +0.2 for hedging, and +0.2 for containing any number. A sycophantic, padded, hedgy answer with a stray digit fires all four and clamps to 1.0 — while a terse correct answer scores far lower. No correctness was checked; the reward was inflated by surface features. Verifiable rewards dodge that failure, at the cost of their own visible-test hole. Every proxy leaks somewhere; environment design is the craft of making the proxy hard to satisfy without actually solving the task.

Build once, use four ways

The payoff for treating an environment as one clean artifact: the same scored rollout group serves four different jobs. Sample a group of candidate rollouts, run each through the verifier once, and you get one rewards array. Everything downstream is a read of that array — not a separate system.

python
rewards = [verify(r, harness) for r in rollouts] # score the group ONCE eval_score = mean(rewards) # 1. EVALUATION A = [(r - mean(rewards)) / std(rewards) for r in rewards] # 2. RL TRAINING (GRPO) kept = [r for r, s in zip(rollouts, rewards) if s >= tau] # 3. SYNTHETIC DATA rewards = [verify(r, other_harness) for r in rollouts] # 4. HARNESS testbed
  1. Evaluation is the mean reward over the group — the benchmark pass-rate. No gradients, just an average.
  2. RL training turns the same rewards into GRPO advantages, Aᵢ = (rᵢ − mean) / std. The group mean is the baseline (GRPO needs no separate value network), so rollouts above the mean get reinforced and those below get suppressed.
  3. Synthetic data keeps only the high-reward rollouts (reward ≥ τ) as a self-filtering SFT set for the next round.
  4. Harness testbed holds the rollouts fixed and swaps the scaffold — different tools, parser, or test set — then re-scores. The eval, the training signal, and the kept data all move together, because they all read the same numbers.

This is exactly the abstraction behind Prime Intellect's verifiers library, the open framework used to train the INTELLECT-3 model: you write one environment, and it doubles as an eval, an RL trainer, and a data engine.

Walkthrough: one group, four reads

Take six candidate solutions to "square x," scored under a narrow harness (visible tests {3→9, 5→25} only):

Candidatef(3)f(5)RewardGRPO advantageKept (τ=1)?
x*x9 ✓25 ✓1.0+1.0yes
{3:9,5:25}[x]9 ✓25 ✓1.0+1.0yes
x*x + 110 ✗26 ✗0.0−1.0no
x*x - 18 ✗24 ✗0.0−1.0no
abs(x)*abs(x)9 ✓25 ✓1.0+1.0yes
x + 14 ✗6 ✗0.0−1.0no

Read the four uses straight off the table. Eval: mean = 3/6 = 50% pass-rate. Train: mean 0.5, std 0.5, so advantages are +1 above and −1 below — three rollouts reinforced, three suppressed. Data: three rollouts clear τ = 1 and get kept for SFT. Every number came from one scoring pass.

Now the punchline. Notice the lookup-table hack {3:9,5:25}[x] scored 1.0, earned a positive advantage, and got kept for training — the narrow harness rewarded a fraud. Swap to the comprehensive harness (add held-out {2→4, 4→16, 7→49}) and re-score the identical rollouts:

CandidateNarrow rewardComprehensive reward
x*x1.01.0
{3:9,5:25}[x]1.00.4
abs(x)*abs(x)1.01.0

The spec-gamer collapses from 100% to 40%; its advantage flips negative and it drops out of the SFT set. The group's eval mean falls from 50% to 40%. Same model, same rollouts, better harness, honest result. That single move — closing the gap by scoring what you actually care about — is most of what environment engineering is.

What to remember

  • An LLM rollout is an RL episode: action = policy(obs), then env.step returns the next observation and reward.
  • Every environment is dataset + rollout + rubric. In the rubric, parse first, score second — conflating them silently throws away real reward.
  • Every reward is a proxy. Verifiable rewards are harder to game but not unhackable (the visible-test lookup trick); LLM-judge rewards are softer still (surface features). Add held-out tests to harden a verifiable proxy.
  • GRPO advantages are (rᵢ − mean) / std; the group mean is the baseline, so no critic network is needed.
  • One scored rollout group = eval + RL signal + synthetic data + harness testbed. Build the environment once, use it four ways.

Keep going

Environments are one layer of the agent stack. These siblings cover the rest:

Then open the interactive deep dive and score your own rollouts: type a candidate, watch the verifier run it, swap the harness, and see the four uses shift together in real time.

FAQ

What is an RL environment for an LLM agent?

It is a scored world the model practices in: a dataset of tasks with ground truth, a rollout mechanism that lets the model produce output (including tool calls), and a rubric that converts that output into a scalar reward. The model acts, the environment returns an observation and a reward, and RL uses those rewards to nudge the policy toward higher-scoring behavior. Concretely it is a small amount of code — the value is entirely in getting the reward to reflect what you actually want.

Why is reward hacking a problem even with verifiable rewards?

Because the reward is a proxy for the true objective, and any proxy can be satisfied without solving the real task. A verifiable test suite feels airtight, but if the model can see the test inputs it can hardcode their answers — a lookup table {3:9, 5:25}[x] scores 100% on those two cases while being completely wrong on unseen inputs. Verifiable rewards are easier to harden (add held-out tests the model never sees until the proxy stops being cheatable), but "verifiable" and "unhackable" are not the same thing.

What does "parse first, score second" mean?

It is the discipline that keeps a rubric from failing on correct answers. A model's raw output is usually the answer buried in prose, code fences, or reasoning. If you compare that raw text directly to the ground truth, a correct answer wrapped in "Sure! Here you go:" scores zero. Instead you parse — extract the answer payload with a parser that matches the output shape (code fence, last number, a regex-tagged answer) — and only then score the extracted payload against ground truth. Conflating the two steps throws away real reward signal.

How do GRPO advantages work?

GRPO (Group Relative Policy Optimization) scores a whole group of rollouts for the same prompt, then computes each rollout's advantage as (reward − group_mean) / group_std. The group mean acts as the baseline, so GRPO needs no separate value/critic network — rollouts that beat their peers get positive advantages and are reinforced, while below-average ones get pushed down. It is why the same scored group that gives you an eval number (its mean) also gives you the training signal (its normalized deviations).

Why is one environment called "build once, use four ways"?

Because a single scored rollout group answers four questions at once. Its mean is an evaluation benchmark. Its normalized rewards are the RL training signal. Its high-reward subset is synthetic data for supervised fine-tuning. And re-scoring the same rollouts under a different scaffold makes it a harness testbed. You compute the rewards once and read them four ways, which is why frameworks like Prime Intellect's verifiers treat the environment as the single reusable artifact of agent training.

Make it stick: run this one yourself

Don't just read it — step through it interactively, one state change at a time.

Open the interactive deep dive