Skip to main content
Chapter 2 climbed the objective J(θ)J(\theta) of the repair MDP by differentiating through an exact model of the environment. Chapter 5 ended with the opposite situation: TinyAgent acts in a real environment whose transition probabilities nobody can write down, and the only feedback is a number at the end of each episode, produced by running hidden tests. Neither the tests nor the sampler are differentiable. This chapter derives the estimator that makes learning possible anyway, the score-function or REINFORCE estimator, and implements it on token sequences. The derivation is short, and every line has an engineering consequence. Environment dynamics drop out of the gradient, which is why RL needs no model of the repository. Tool tokens drop out too, which is where the action mask of Chapter 5 comes from. The loss we backpropagate is a surrogate whose gradient is the estimator and whose value means nothing. Baselines leave the estimator unbiased and can reduce its variance. The way per-token terms are summed into one scalar silently decides how much each trajectory counts. Before any of that can be tried on the gym, we also need a policy that succeeds at least sometimes. We build one, the book’s base agent, and then run the first RL training of TinyAgent. The saved code of this chapter calls earlier chapters through arl., so the imports are part of a saved block.

6.1 The score-function estimator

The objective is the expected return of trajectories sampled from the policy, J(θ)=Eτ∼pθ[R(τ)]=∑τpθ(τ) R(τ).J(\theta) = \mathbb{E}_{\tau \sim p_\theta}\big[R(\tau)\big] = \sum_\tau p_\theta(\tau)\, R(\tau). The return R(τ)R(\tau) is computed by the environment, here by tests, and has no derivative with respect to θ\theta. The dependence on θ\theta sits entirely in the distribution pθp_\theta. Differentiate under the sum and use ∇p=p ∇log⁡p\nabla p = p\, \nabla \log p (Appendix A.5): ∇θJ(θ)=∑τ∇θpθ(τ) R(τ)=Eτ∼pθ[R(τ) ∇θlog⁡pθ(τ)].\nabla_\theta J(\theta) = \sum_\tau \nabla_\theta p_\theta(\tau)\, R(\tau) = \mathbb{E}_{\tau \sim p_\theta}\big[R(\tau)\, \nabla_\theta \log p_\theta(\tau)\big]. The expectation on the right can be estimated from samples. What remains is the score of a whole trajectory, ∇θlog⁡pθ(τ)\nabla_\theta \log p_\theta(\tau). Section 2.2 factored the trajectory probability into what the agent controls and what it does not: log⁡pθ(τ)=log⁡ρ0(s0)+∑t=0T−1log⁡πθ(at∣st)+∑t=0T−1log⁡P(st+1∣st,at).\log p_\theta(\tau) = \log \rho_0(s_0) + \sum_{t=0}^{T-1} \log \pi_\theta(a_t \mid s_t) + \sum_{t=0}^{T-1} \log P(s_{t+1} \mid s_t, a_t). The initial distribution ρ0\rho_0 and the transitions PP do not depend on θ\theta, so their gradients are zero, and ∇θJ(θ)=Eτ∼pθ[R(τ)∑t=0T−1∇θlog⁡πθ(at∣st)].\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta}\Big[R(\tau) \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t \mid s_t)\Big]. This is Equation (6.1), the REINFORCE estimator for trajectories (Williams, 1992). Three assumptions went into it, and each one is a place where real systems break it. The return must not depend on θ\theta directly, which fails if a learned reward model shares weights with the policy. The trajectories must be sampled from πθ\pi_\theta itself, which fails as soon as data is reused after an update or the inference engine computes slightly different probabilities (Chapter 8, Chapter 13). And the sum over trajectories must be finite or well behaved, which a turn budget guarantees. The estimator needs PP only to sample. It never evaluates PP, and it never differentiates through it. We check that on the repair MDP of Chapter 2, where the exact gradient is available. The sampler below runs many episodes in parallel and records, for each episode, how often each state-action pair was visited and the total return.
The policy is a softmax over a table of logits θ\theta of shape [S, A], initialized at random so that it is neither uniform nor deterministic. Chapter 2’s policy_evaluation gives J(θ)J(\theta) exactly, and autograd gives the exact gradient. The Monte Carlo side computes ∑tlog⁡πθ(at∣st)\sum_t \log \pi_\theta(a_t \mid s_t) for every episode as visits * log_softmax(theta), multiplies by the return, averages, and differentiates.
Output:
The policy passes in about 13% of episodes (J=0.132J = 0.132). The estimate has a cosine similarity of 0.999 with the exact gradient, and its relative error of 6.8% is the Monte Carlo noise of 20,000 episodes. Look at what the code differentiated: returns, visits, and log_pi. Only the last depends on θ\theta, and PP appears nowhere in the differentiated expression. A coding environment is in the same position as PP: we can run it, but not differentiate it.

The surrogate loss

The expression we differentiated, 1N∑nR(τn) log⁡pθ(τn)\frac{1}{N}\sum_n R(\tau_n)\, \log p_\theta(\tau_n) with the returns held fixed, is a surrogate objective. Its gradient at the current parameters equals the estimator of Equation (6.1). Its value is not JJ, and it does not estimate anything useful. The returns enter as constants, so shifting every return by the same amount changes the surrogate’s value arbitrarily, while its expected gradient stays ∇J\nabla J, because E[∇log⁡pθ(τ)]=0\mathbb{E}[\nabla \log p_\theta(\tau)] = 0.
Output:
The three surrogates have values of −0.83-0.83, −0.41-0.41, and −17.0-17.0, and gradients that all estimate the same vector. None of the values is related to J=0.132J = 0.132. The shift by −J-J moves the gradient estimate slightly closer to the exact one (cosine 0.9992 against 0.9988), and the shift by +5+5 moves it much further away (0.78). Shifting the return is a baseline, and Section 6.4 explains why one shift helps and the other hurts. The practical rule follows immediately: never read a policy-gradient loss curve as a measure of progress. Log the reward, the pass rate, and the gradient norm instead.

6.2 A base policy to learn from

Equation (6.1) weights each trajectory’s score by its return. If every sampled trajectory has the same return, the estimator is that constant times ∑n∇log⁡pθ(τn)\sum_n \nabla\log p_\theta(\tau_n), a quantity with expectation zero, and with a baseline it is exactly zero. Chapter 5 measured an untrained TinyAgent: 384 malformed calls out of 384 and a reward of 0 in every episode. RL cannot start from there. Large models never start from there either. A pretrained model already writes fluent text, and an instruction-tuned one already emits well-formed tool calls most of the time. RL then improves which calls it makes. We give TinyAgent the same starting point with behavior cloning: supervised training on demonstrations, with the cross-entropy loss restricted to the policy tokens. The demonstrations come from Chapter 4’s random and blind scripted policies on the training tasks. They are well formed and unskilled: random chooses uniformly among the nine valid calls, and blind guesses a repair op without reading the code. A model that imitates them learns the call format and the rhythm of an episode, but not how to choose the right op. We call the result the base agent. It is the book’s analogue of a pretrained base model, and every later chapter starts RL from it. Chapter 15 studies supervised warm starts properly, including what they do to exploration. The behavior-cloning loss is a mean over policy tokens, which we need often enough to save as a function.
make_base_agent collects episodes_per_task demonstrations per training task from each scripted policy, encodes them with scripted_rollout (Section 5.6), and runs steps Adam updates on random minibatches. Everything is seeded, so the same call returns the same weights.
To measure a policy we need summary statistics of its rollouts. rollout_stats reports the mean reward, the pass rate (submitted, and every hidden test passes), the fraction of calls that were well formed and valid, the submit rate, and the mean number of turns and policy tokens per episode. Later chapters log the same numbers.
We build the tokenizer exactly as Chapter 5 did, then the base agent, and compare it with an untrained TinyAgent on both splits, 16 episodes per task.
Output:
The untrained model never passes, none of its calls are valid, and it always exhausts the six-turn budget. The base agent’s calls are valid in about 98% of turns, it submits in about four episodes of five, and it passes in 12% of training episodes and 14% of held-out ones. That is where its demonstrations put it: between random (0.10 on the training split in Section 4.6) and blind (0.21), far below the oracle’s 1.00. Its held-out numbers match its training numbers within sampling noise, because nothing it imitated depended on which function it was fixing. Other samples in this chapter put its pass rate between 0.12 and 0.19; with a few hundred episodes, that spread is the sampling error. On the check host, make_base_agent took about five seconds when this chapter was written. Timings are not checked by the build. A sampled episode shows the behavior that RL has to work with.
Output:
Each episode is a sequence of valid calls with no strategy behind it. On clamp, one episode submits the unchanged file, one guesses add_zero_guard and submits, and one wanders through edits and tests until the budget runs out. When the base agent does read, which it does in a minority of episodes, its next op is unrelated to what it read. Successes are lucky guesses. Every success, however, is a trajectory whose return differs from the others, so the policy gradient is no longer zero.

6.3 The estimator on tokens

At the token level of Section 2.8, a trajectory is one token sequence y1,…,yTy_1, \dots, y_T with prefixes ci=y<ic_i = y_{<i}. Every token is drawn either by the policy or by the environment and template. Write mi=1m_i = 1 when token ii was sampled from the policy, which is exactly the action mask of Section 5.6. Then log⁡pθ(τ)=∑imilog⁡πθ(yi∣ci)+∑i(1−mi)log⁡Penv(yi∣ci),\log p_\theta(\tau) = \sum_i m_i \log \pi_\theta(y_i \mid c_i) + \sum_i (1 - m_i) \log P_{\text{env}}(y_i \mid c_i), where PenvP_{\text{env}} includes the prompt, the template, and every tool response. The second sum does not depend on θ\theta, so ∇θJ(θ)=Eτ[R(τ)∑imi ∇θlog⁡πθ(yi∣ci)].\nabla_\theta J(\theta) = \mathbb{E}_\tau\Big[R(\tau) \sum_i m_i\, \nabla_\theta \log \pi_\theta(y_i \mid c_i)\Big]. This is Equation (6.2). Tool tokens carry no direct gradient: the model is never pushed to predict them. They do carry an indirect gradient, because every later policy token is conditioned on them through attention, so the gradient flows through their hidden representations into the shared weights. The corresponding loss for a batch of BB trajectories is L(θ)=−1B∑n=1BRn∑imn,ilog⁡πθ(yn,i∣cn,i),\mathcal{L}(\theta) = -\frac{1}{B} \sum_{n=1}^{B} R_n \sum_i m_{n,i} \log \pi_\theta(y_{n,i} \mid c_{n,i}), with RnR_n treated as a constant. We check both claims on a batch of 64 base-agent episodes. A hook on the token embedding keeps the gradient with respect to each position’s input representation, and retain_grad keeps the gradient with respect to the logits.
Output:
Exactly 70 positions receive a gradient on their logits, and they are exactly the policy tokens of the successful episodes. Failed episodes have R=0R = 0 and contribute nothing at all, and no prompt, template, or tool position receives a direct gradient. The indirect gradient is visible on the input side: the embedding gradient at tool positions is not zero.
Output:
Prompt and tool positions receive gradient through the representations they feed to later policy tokens. At tool positions it is about a fifth of the size it has at policy positions, but it is not zero. That is how the policy learns to use an observation, for example to choose an op after reading the file, without ever being trained to produce the observation. The mask removes the direct term, and attention keeps the indirect one.

6.4 Baselines

The shift experiment of Section 6.1 suggested that subtracting something from the return can reduce the noise of the estimator without biasing it. The general statement is the following. Let b(st)b(s_t) be any function of the state at time tt, or of anything that is fixed before ata_t is sampled. Then Eat∼πθ(⋅∣st)[b(st) ∇θlog⁡πθ(at∣st)]=b(st)∑aπθ(a∣st) ∇θπθ(a∣st)πθ(a∣st)=b(st) ∇θ∑aπθ(a∣st)=b(st) ∇θ1=0.\mathbb{E}_{a_t \sim \pi_\theta(\cdot \mid s_t)}\big[b(s_t)\, \nabla_\theta \log \pi_\theta(a_t \mid s_t)\big] = b(s_t) \sum_a \pi_\theta(a \mid s_t)\, \frac{\nabla_\theta \pi_\theta(a \mid s_t)}{\pi_\theta(a \mid s_t)} = b(s_t)\, \nabla_\theta \sum_a \pi_\theta(a \mid s_t) = b(s_t)\, \nabla_\theta 1 = 0. Taking the outer expectation over sts_t (the tower property) gives Eτ[b(st)∇log⁡πθ(at∣st)]=0\mathbb{E}_\tau[b(s_t) \nabla \log \pi_\theta(a_t \mid s_t)] = 0 for every tt. Therefore ∇θJ(θ)=Eτ[∑t(R(τ)−b(st)) ∇θlog⁡πθ(at∣st)]\nabla_\theta J(\theta) = \mathbb{E}_\tau\Big[\sum_t \big(R(\tau) - b(s_t)\big)\, \nabla_\theta \log \pi_\theta(a_t \mid s_t)\Big] for any such bb. This is Equation (6.3). The proof uses only that bb is fixed when ata_t is drawn. A baseline may depend on the task, the prompt, and earlier tokens. It may not depend on ata_t, on anything sampled after ata_t, or on the trajectory’s own return. A baseline computed from the batch mean including the trajectory’s own reward violates the last condition slightly, and Chapter 7 quantifies the consequence. The baseline cannot change the mean, so it can only change the variance. For a constant baseline bb and per-trajectory score g=∇log⁡pθ(τ)g = \nabla \log p_\theta(\tau), the total variance E∥(R−b)g∥2−∥∇J∥2\mathbb{E}\lVert (R - b) g\rVert^2 - \lVert \nabla J \rVert^2 is a quadratic in bb, minimized at b∗=E[R ∥g∥2]E[∥g∥2].b^* = \frac{\mathbb{E}\big[R\, \lVert g \rVert^2\big]}{\mathbb{E}\big[\lVert g \rVert^2\big]}. This is a weighted mean reward, with weights proportional to the squared score norm. In practice the plain mean reward is close to it, and V(s0)V(s_0), the expected return from the task’s initial state, is the natural state-dependent choice (Appendix A.3 treats baselines as control variates). We first confirm unbiasedness where the exact gradient is known. We sample 400 independent batches of 64 repair-MDP episodes and average the estimator over the batches for four baselines: none, the exact JJ, the batch’s own mean return, and a deliberately bad constant 1.
Output:
For a softmax table the score has a closed form: each visit to (s,a)(s, a) adds ea−πθ(⋅∣s)\mathbf{e}_a - \pi_\theta(\cdot \mid s) to row ss, which is what the first lines compute. The noise level is the standard error of the 400-batch average relative to the size of the gradient. For every baseline, the average agrees with the exact gradient to within about one noise level (1.4 for the constant 1), which is what an unbiased estimator must do. The variances differ: subtracting the exact JJ cuts the per-batch variance by 20% (0.0071 to 0.0057), and subtracting 1 multiplies it by 2.7. The batch mean is an exception to unbiasedness in principle: it includes each trajectory’s own return, which shrinks the expected gradient by the factor 1−1/641 - 1/64, an error of 1.6% that is well below the noise level here. Now the same measurement on the real system. For 240 base-agent episodes (12 per training task) we compute each episode’s score ∇θ∑imilog⁡πθ(yi∣ci)\nabla_\theta \sum_i m_i \log \pi_\theta(y_i \mid c_i) over all of TinyAgent’s parameters, one backward pass per episode.
Output:
The scores form a 240 by 157,244 matrix, one row per episode, and this probe sample happens to have a mean reward of 0.188. Multiplying each row by Rn−bR_n - b gives that episode’s contribution to the estimator, and the trace of its covariance is the quantity a baseline should minimize. We evaluate several baselines. The per-task baseline uses the mean reward of each task measured on a separate batch of episodes, so it is fixed before the probe episodes are drawn, as the proof requires.
Output:
Figure 6.1: Total variance of the REINFORCE estimator for the base agent on the BugFix training tasks, as a function of a constant baseline b, with the named baselines marked. 240 episodes; gradients over all TinyAgent parameters. Figure 6.1 draws the full curve. The optimal constant b∗=0.123b^* = 0.123 gives a total variance of 39.3, and the mean reward 40.9. Because the mean reward is small, no baseline at all is only 14% worse (44.7). The parabola rises steeply on the right: a baseline of 1 multiplies the variance by more than seven, because every failure, which is most episodes, now contributes a full-sized score with weight −1-1. The per-task baseline (40.8) does no better than the global mean, even though it is the better baseline in principle. Each task’s mean is estimated from only 12 independent episodes, so the noise of that estimate is added to every advantage, and at a pass rate below 0.2 it cancels the gain from tracking each task’s difficulty. Chapter 7 returns to exactly this trade-off with group baselines.

6.5 Causality and reward-to-go

The baseline argument has a second consequence. Suppose rewards arrive during the episode, R(τ)=∑t′rt′R(\tau) = \sum_{t'} r_{t'}. For t′<tt' < t, the reward rt′r_{t'} is fixed before ata_t is sampled, so it can play the role of a baseline for ata_t, and E[rt′∇log⁡πθ(at∣st)]=0\mathbb{E}[r_{t'} \nabla \log \pi_\theta(a_t \mid s_t)] = 0. Removing those terms gives ∇θJ(θ)=Eτ[∑t∇θlog⁡πθ(at∣st)∑t′≥trt′],\nabla_\theta J(\theta) = \mathbb{E}_\tau\Big[\sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t) \sum_{t' \ge t} r_{t'}\Big], Equation (6.4). The inner sum is the reward-to-go GtG_t. Causality says an action is credited only with rewards that come after it. At the token level, tt runs over token positions and Gi=∑j≥irjG_i = \sum_{j \ge i} r_j. For an outcome-only reward placed on the last policy token, Gi=R(τ)G_i = R(\tau) at every policy token, and causality changes nothing. It matters when the reward has intermediate terms: a cost per turn, a format penalty on a malformed call, or a per-turn shaping reward (Chapter 11, Chapter 12). We add a cost of 0.1 per call to the probe episodes. The cost is placed on the last token of each call and the hidden-test reward on the last token of the final call.
Output:
With the total return, every token of the episode gets the same weight. With reward-to-go, an early call is charged for its own cost and every later one, and the last call only for its own. We measure the variance of the two estimators over the 240 probe episodes. The total-return estimator reuses the scores; reward-to-go needs one more backward pass per episode, because its weights differ from token to token.
Output:
Removing the past costs from each token’s weight reduces the variance, by an amount that grows with the number and size of the intermediate rewards. Chapter 7 saves a general returns_to_go with discounting that skips tool tokens, and replaces the remaining reward-to-go by an estimate of the advantage.

6.6 Loss aggregation

Equation (6.2) is a sum over tokens and an average over trajectories. Implementations rarely compute exactly that. They compute a per-token term ℓn,i=−A^n,ilog⁡πθ(yn,i∣cn,i)\ell_{n,i} = -\hat A_{n,i} \log \pi_\theta(y_{n,i} \mid c_{n,i}) on a padded [B, T] tensor and reduce it to a scalar, and the reduction they choose sets a weight wn,iw_{n,i} on every token: ∇L=∑n,iwn,i ∇ℓn,i\nabla \mathcal{L} = \sum_{n,i} w_{n,i}\, \nabla \ell_{n,i}. Let Ln=∑imn,iL_n = \sum_i m_{n,i} be the number of policy tokens of trajectory nn. The four reductions in common use are Only seq_mean_token_sum is Equation (6.2) as derived. The others rescale it, and two of them rescale it by something that depends on the batch. token_mean divides by the number of policy tokens in the batch, so the effective learning rate shrinks when the batch’s trajectories get longer. seq_mean_token_mean gives every trajectory the same total weight regardless of its length, so a token of a long trajectory counts less than a token of a short one. constant divides by a fixed number CC, for example the batch size times the maximum number of policy tokens, which keeps the per-token weight fixed across batches. Which public recipes use which mode, and the Dr. GRPO critique of per-sequence normalization (Liu et al., 2025), are the subject of Chapter 9. Here we build the reductions and measure what they do.
reinforce_loss detaches the advantages. They are constants in the derivation, and Section 6.8 shows the bias that appears when they are not. The weights in the table are the gradient of the loss with respect to each per-token term, so autograd can read them off. Three trajectories with 4, 8, and 24 policy tokens:
Output:
The output is the table with numbers in it. Under token_mean, seq_mean_token_sum, and constant, the per-token weight is the same for all three trajectories, so the 24-token trajectory has six times the total weight of the 4-token one. The three modes differ only by an overall scale, and only token_mean’s scale depends on the batch (here 1/361/36). Under seq_mean_token_mean, each trajectory has total weight 1/31/3, and a token of the short trajectory weighs six times as much as a token of the long one. Lengths are not random in an agent task. In the probe batch of the base agent, successful episodes are shorter than failures, because a lucky guess ends with an early submit while failures wander. With an advantage of R−RˉR - \bar R, successes push up and failures push down, and the aggregation decides how hard.
Output:
The advantages are centered, ∑nA^n=0\sum_n \hat A_n = 0, but the pushes are not balanced under the two length-proportional modes. Their total push is proportional to ∑nA^nLn\sum_n \hat A_n L_n, and since failures are longer than successes on average, the push down exceeds the push up by 19% (0.157 against 0.132). Every token is pushed equally hard, so a long failure (19.6 tokens on average) receives the same push per token as a short one (6.4 tokens). seq_mean_token_mean balances the totals exactly, because each trajectory’s total weight is 1/B1/B and the advantages sum to zero. Per token, however, each long failure is pushed down only a third as hard as each short failure. If a policy can make a failing episode longer, for instance by adding calls before giving up, seq_mean_token_mean reduces the per-token penalty on every one of those calls. That is the mechanism behind the length growth that Dr. GRPO attributes to per-sequence normalization (Chapter 9). Figure 6.2: Left: share of the batch's total weight carried by each trajectory of the base agent's probe batch, against its number of policy tokens. Right: weight of one token of a 10-token trajectory in a batch of 64, as the other 63 trajectories get longer, relative to a batch in which all have 10 tokens. Figure 6.2 separates the two effects. On the left, a trajectory’s own length: the three length-proportional modes lie on one line through the origin, while seq_mean_token_mean is flat, so the shortest and the longest trajectories in the batch count the same. On the right, the length of the rest of the batch: only token_mean changes a trajectory’s per-token weight when other trajectories get longer, falling to a quarter when their mean length grows from 10 to 40. As episodes lengthen during training, token_mean therefore lowers the effective learning rate of every token, and the other three modes do not. The book uses token_mean as the default, because it is what most public recipes use and because the batch-dependent scale is easy to monitor. Appendix B, Problem 1 argues the choice at length.

6.7 Training TinyAgent with REINFORCE

Everything is now in place for the first RL run. One update samples a batch of episodes from the current policy, computes advantages, and takes one gradient step on the surrogate. policy_gradient_step performs the update on a collated batch. It clips the global gradient norm, a standard safeguard whose value we also log, and it returns the loss and the gradient norm before clipping. Chapters 8 and 9 replace the loss but keep this shape.
The training loop samples 64 episodes per step from random training tasks. The advantage is the reward minus a baseline bb that is an exponential moving average of past batch means, initialized at the base agent’s training pass rate measured in Section 6.2. The baseline is computed before the batch is seen, so it satisfies the condition of Equation (6.3). Every ten steps we measure the held-out pass rate with 8 episodes per held-out task. The model is a copy of the base agent, which stays untouched for later comparisons.
Output:
Figure 6.3: The first REINFORCE run. Mean reward of each training batch of 64 episodes (thin line) and the held-out pass rate every ten steps (markers), with the base agent's level and the blind guesser's 0.21 for reference. The batch reward climbs from 0.16 to 0.84 by step 70, and Figure 6.3 shows the whole curve. The loss column illustrates Section 6.1: it takes both signs, stays within a few hundredths of zero, and says nothing about progress, while the reward column does. The gradient norm stays between 0.06 and 0.13, well below the clipping threshold of 1, so clipping never acts in this run. The held-out pass rate is measured on only 80 episodes per point, so it moves by several points from one evaluation to the next. The run took about seven seconds on the check host. Before reading anything into the held-out column, we measure both splits with 16 episodes per task and look at what the policy actually does.
Output:
Output:
The training pass rate rose from 0.14 to 0.78 and the held-out pass rate from 0.13 to 0.46. The behavior statistics explain both numbers. The policy never reads and never tests before editing, so the only task-specific information it has when it chooses its first edit is the function name in the issue. Its first edit is the right op in 50% of training episodes and 23% of held-out ones, against 20% for a uniform guess among the five repair ops. On the training tasks it has partly memorized which op goes with which function name. On held-out tasks the function names are words it never saw in training, and its first choice is a guess. The rest of the gain comes from a second strategy: applying several ops before submitting, 2.2 per episode on training tasks and 3.5 on held-out ones. A wrong op often leaves the source unchanged (no change) or changes it harmlessly, so a sequence such as fix_comparison -> add_zero_guard -> fix_comparison repairs both the compare and the zero families. The most frequent held-out episode applies swap_minmax five times, which leaves min and max swapped by parity. Stacking is a legitimate repair under this reward, but it is a strategy no engineer would accept, and it transfers to held-out tasks only because the edit menu is small. Reading, the behavior that would transfer cleanly, never appears. The base agent reads in only a minority of episodes and then picks an op unrelated to what it read, so reading is not correlated with reward in its own samples, and REINFORCE can only reinforce what it samples.
Evidence. Measured here, on one seed of a 158k-parameter model with 20 training tasks. The run demonstrates the mechanics of the estimator. It is not a claim about which strategies large models learn. The qualitative lesson is a general property of the estimator rather than of this model: RL amplifies behavior that is already rewarded in the policy’s own samples, and memorizing training tasks or exploiting a small action menu are cheap ways to be rewarded. Chapter 4 set a held-out bar of 0.2 for “learned more than the format”, and this run shows that clearing it does not by itself prove the agent learned to read. Chapters 7, 9, and 12 return to this run’s two failure modes, credit for useless steps and the absence of information gathering.

6.8 Design problems in practice

Problem: high variance on sparse binary rewards. Symptom: the per-step reward curve is noisy, different seeds learn at very different speeds, and progress on hard tasks stalls while easy tasks saturate. Cause: with binary rewards and a low pass rate, most episodes contribute a score with a small negative weight and a few contribute large positive ones. The estimator’s variance is large relative to its mean (Figure 6.1), and a task whose sampled episodes all fail contributes nothing. Options: (a) more episodes per step, which costs rollout compute linearly for a 1/B1/\sqrt{B} reduction in noise. (b) Better baselines, especially per-task ones from groups of samples (Chapter 7). (c) Denser rewards, which risk being gamed (Chapter 11). (d) A curriculum that keeps tasks near a 50% pass rate (Chapter 14). Default: measure the gradient noise directly before tuning anything else. The block below splits the 240 probe episodes into disjoint batches and computes the cosine similarity between gradient estimates from independent batches of the same size.
Output:
Two gradient estimates from independent batches of 8 episodes are uncorrelated (the mean cosine of −0.09-0.09 is zero within the noise of 20 pairs), batches of 32 agree weakly (0.17), and even batches of 120 agree only partly (0.42). A 64-episode batch, like the one in Section 6.7, is mostly noise in any single step. Learning works anyway because the noise averages out across steps and the signal does not. That is also why the learning rate and the batch size trade off against each other.
Problem: length bias introduced by aggregation. Symptom: mean episode length grows over training without a matching rise in reward, often concentrated in failing episodes. Cause: seq_mean_token_mean divides each trajectory’s push by its own length (Section 6.6), so a longer failure is penalized less per token. token_mean couples the step size to the batch’s total length. Options: (a) seq_mean_token_sum or constant, which keep per-token weights fixed and need a learning rate retuned for the larger gradient. (b) token_mean, which is widely used, with the batch’s policy-token count logged. (c) Explicit length costs, which change the task. Default: token_mean, while logging the mean length of successes and failures separately. If failures lengthen, switch to constant.
Problem: detaching the wrong tensor. Symptom: training runs and the loss decreases, but learning is slower or drifts in odd directions, and the gradient differs from a reference implementation. Cause: a quantity that should be a constant in the surrogate, such as a baseline or advantage computed from the model’s own outputs, was left attached to the graph. Its gradient is then added to the estimator, and nothing in the derivation justifies that term. Options: (a) detach advantages inside the loss function, as reinforce_loss does. (b) Compute advantages under torch.no_grad(). Default: both, plus a unit test that compares the gradient to a reference on a fixed batch. The block below reproduces the bias on a five-call bandit with a baseline read off the policy’s own probabilities.
Output:
With the baseline detached, the estimate matches the exact gradient up to sampling noise. Attached, the gradient picks up the extra term −∇b⋅log⁡π‾-\nabla b \cdot \overline{\log \pi}, whose expectation is H(π) ∇bH(\pi)\, \nabla b, and the error is of the same order as the gradient itself. The same bug appears when a value head shares the trunk with the policy and its prediction enters the policy loss without detach() (Chapter 7).
Problem: the zero-advantage batch. Symptom: the policy keeps changing, and sometimes degrades, during steps in which every episode had the same reward and the logged loss and gradient were exactly zero. Cause: with all rewards equal and a mean baseline, every advantage is zero, so the gradient is zero. Adam still moves the parameters, by its first-moment estimate from earlier steps. Weight decay moves them too. A zero-advantage step is not a no-op. With token_mean, zero-advantage trajectories also dilute every other trajectory’s weight by adding tokens to the normalizer. Options: (a) skip the optimizer step when the batch has no signal. (b) Filter zero-advantage groups before forming the batch and resample, which is dynamic sampling (Chapter 9). (c) Accept the drift. Default: (b) when groups are used, otherwise (a). The block below takes one ordinary step and then one step on an all-zero advantage.
Output:
The loss and the gradient norm are exactly zero, and the parameters still move by a nonzero amount, the momentum of the previous step.

Summary

  • The REINFORCE estimator E[R(τ)∑t∇log⁡πθ(at∣st)]\mathbb{E}[R(\tau) \sum_t \nabla \log \pi_\theta(a_t \mid s_t)] needs the environment only to sample. Its dynamics drop out of the score, and at the token level so do all prompt, template, and tool tokens, which is the action mask. Tool tokens still affect the gradient through attention.
  • The surrogate loss has the estimator as its gradient and no meaningful value. Log rewards and pass rates, not the loss.
  • Any baseline fixed before the action is sampled leaves the estimator unbiased. On the base agent, the mean reward is close to the optimal constant, a baseline of 1 inflates the variance more than sevenfold, and a per-task baseline estimated from 12 episodes is no better than the global mean. Reward-to-go removes past rewards from each token’s weight, which cut the variance by 43% under a per-call cost.
  • The loss aggregation sets every token’s weight. Three common modes weight trajectories in proportion to their length, seq_mean_token_mean weights them equally, and token_mean couples the step size to the batch’s length.
  • The base agent, TinyAgent behavior-cloned on random and blind play, writes valid calls 98% of the time and passes 12% to 19% of episodes. Eighty REINFORCE steps raise its pass rate from 0.14 to 0.78 on training tasks and from 0.13 to 0.46 on held-out tasks, by partly memorizing function names and by stacking edits, never by reading.

Exercises

  1. Repeat the unbiasedness check of Section 6.4 with a state-dependent baseline b(s)=Vπ(s)b(s) = V^\pi(s) from policy_evaluation, applied at every step. How much does the per-batch variance fall compared with the exact constant JJ? Then use b(st,at)=Qπ(st,at)b(s_t, a_t) = Q^\pi(s_t, a_t) and show that the mean is now wrong. Which line of the proof fails?
  2. Prove that the optimal constant baseline is b∗=E[R∥g∥2]/E[∥g∥2]b^* = \mathbb{E}[R \lVert g\rVert^2] / \mathbb{E}[\lVert g\rVert^2], and explain why it differs from the mean reward. For which kinds of episodes is ∥g∥2\lVert g \rVert^2 large in TinyAgent?
  3. Change an assumption: train with REINFORCE using seq_mean_token_mean and then seq_mean_token_sum (retune the learning rate for the second, and say why it needs retuning). Log the mean number of policy tokens in failing episodes. Does the length of failures behave as Section 6.6 predicts?
  4. Build the base agent from blind play only, and from random play only. Measure the pass rate, the read rate, and then the result of 80 REINFORCE steps. Which starting distribution leads to more held-out improvement, and why?
  5. Make reading useful to REINFORCE: give the base agent demonstrations from an oracle that is wrong 50% of the time (it reads, then picks the right op with probability 0.5). Does REINFORCE now learn to read? Measure the held-out pass rate.
  6. The token-level estimator treats each token as an action. Derive the turn-level estimator in which an action is a whole call. Show that its score is the sum of the token scores of the call, so the two estimators are identical. Where do the two castings stop being equivalent?
  7. Design review: a colleague proposes to stabilize training by normalizing the surrogate loss by its running mean absolute value, “so that the loss stays around 1”. What does this do to the gradient? Under what condition is it equivalent to a learning-rate schedule, and when does it change the direction of the update?