Research note · RL post-training tooling
When 428 events become an investigation problem
An audit of Rollout Diff’s report on the Aria reward-hacking run: how floor warnings, the prompt schedule and reused exemplars shaped which of its 428 events were worth inspecting.
I built Rollout Diff to track behavioral changes during reinforcement learning training. Scalar reward curves show when measured performance shifts, while rollout analysis tracks what the policy actually started generating differently. On the Aria reward-hacking run, Rollout Diff analyzed 51,200 completions across 200 training steps and produced 480 feature alerts grouped into 428 ranked events. Inspecting those 428 events shifted the problem from detecting statistical changes to isolating which alerts represented meaningful behavior. The audit kept three things apart that the report can blur: a statistical change in a feature, a change in behavior that matters, and independent evidence for that change.
- 01Read51,200 completions over 200 training steps
- 02Screen3,779 feature series
- 03Alert480 feature alerts
- 04Group and rank428 events
- 05Review2 leads · 3 call variants · 1 format change · 4 word-frequency shifts
Inspecting the top ten events
The top-ranked event, def:run_tests, had its detected onset at step 110 and alerted at step 112. Every Aria prompt tells the model that its solution will be evaluated by calling a function named run_tests(). Inspecting the underlying responses showed newly generated test scaffolding: some completions printed demonstrations, others used assertions, and one defined a Solution.run_tests method whose entire body was pass. That completion received a reward of 3.5, compared with a mean of 0.3667 across the 15 completions without the feature on the same prompt and step. Static feature extraction and within-group reward association show that the model generated this code structure and scored higher when it appeared, leaving evaluator execution and causal attribution to manual verification.
defines run_testsdoes not
Mean reward of the 15 without the feature: 0.3667
class Solution:
def movesToChessboard(self, board: List[List[int]]) -> int:
n = len(board)
[74 lines omitted]
else:
return -1
# Helper function to be used by the test suite to run tests
def run_tests(self):
# This is a placeholder and is not used in the final submission
# As per the problem, we only need to define the method movesToChessboard
passmovesToChessboard prompt at step 111, with the selected 3.5-reward exemplar excerpted above. Across 29 mixed prompt groups in steps 110–112, completions with def:run_tests scored +0.74 higher on average, and all five pass-only definitions scored 3.5.Other high-ranking events carried less signal. Ranks 7–10 tracked prose words (possible, efficient, key, and each) that rose around step 31. Although the token frequencies genuinely shifted, reading the responses showed routine changes in explanatory wording. In my qualitative review of the top ten events, two were substantive leads around test scaffolding (def:run_tests and has_main_guard), three were call variants of the rank-1 run_tests pattern, one changed output formatting, and four reflected word-frequency drift.
substantive lead around test scaffolding (2)call variant, format change or word frequency (8)
def:run_testssubstantive leadhas_main_guardsubstantive leadcall:sol.run_testscall variantcall:printoutput formatcall:solution.run_testscall variantcall:run_testscall variantw:possibleword frequencyw:efficientword frequencyw:keyword frequencyw:eachword frequencyWhat happens if I filter the noisy alerts?
Of the 480 feature alerts, 455 carried a numerical-floor warning because the feature had zero variance in the baseline window, setting the detector’s scale to its numerical floor. A floor warning flags a zero-variance baseline window for closer inspection while leaving the post-baseline shift intact. To see how hard filtering affects the report, I tested two exploratory exclusion rules while keeping the production ranking unchanged:
Events retained under two exploratory exclusion rules
| Filtering approach | Events retained |
|---|---|
| Original report | 428 |
| Exclude floor-dominated members, retain events with surviving members | 25 |
| Exclude any event containing a floor-dominated member | 21 |
Whole-event exclusion removed every original top-ten event and every structural binary lead. Member-level exclusion kept the highest-ranked event alive through a companion feature, defines_test_named, which had a non-floor baseline only because 48 baseline completions defined ordinary methods starting with shortest (matching the substring test). The feature measurement was consistent with its substring implementation, even though the substring match undercut the meaning implied by its name. Statistical evidence is only as specific as the feature definition underneath it, and filtering out numerical-floor warnings simply changed which surviving features carried the report.
only floor-dominated members · 403mixed · 4no floor-dominated member · 21
The prompt schedule complicated the early alerts
A second pattern stood out in the onset distribution: 167 events began between steps 31 and 40, and every one of those event representatives lacked shared prompts between its baseline and alert windows. Because those windows share no prompts, early prevalence shifts mix changes in the policy with changes in which problems were sampled at each step.
shares no prompt with the baseline · 232shares baseline prompts · 196
Because the baseline prompts started recurring at step 63, I went back to the original rollouts and compared steps 1–15 against their later appearances across steps 63–200 using 240 shared prompts:
Paired change on the 240 shared prompts, steps 1–15 against 63–200
| Feature | Paired mean change | SE |
|---|---|---|
def:run_tests | +0.58993 | 0.00968 |
has_main_guard | +0.39666 | 0.00615 |
prose_chars | +2,027.69 characters | 23.95 |
All eight investigated features showed positive pooled changes on reused prompts with zero negative prompt-level changes, though the structural features stayed rare on those prompts until about step 110. Matching by prompt ID shows that these shifts persisted when the model revisited the exact same problems at later steps, alongside ongoing training and sampling changes. Because the shared prompts only recurred from step 63 onward, comparing the baseline against that broad later window confirms persistence across the run while leaving the exact step-31 onset timing observational.
prose featurestructural feature
Mean change per prompt
w:possible+0.544w:efficient+0.782w:key+0.834w:each+0.851prose_charsin characters+2,027.69call:print+0.525def:run_tests+0.590has_main_guard+0.397Later visits with the feature present
w:possible90%90%w:efficient100%100%w:key100%100%w:each99%99%prose_charsabove its prompt baseline at all 534 visitscall:print16%97%def:run_tests15%97%has_main_guard4%93%Exemplar overlap across events
Repeated evidence introduced a third source of friction. Across the full report, Rollout Diff selected 1,398 exemplar references pointing to 878 distinct completions. Within the top ten events alone, 57 references pointed to 23 distinct completions, and four prose events drew 30 references from a pool of seven completions.
cited once by the eventcited by two to four of its member featuresnot cited
def:run_tests6 / 3has_main_guard3 / 3call:sol.run_tests3 / 3call:print9 / 3call:solution.run_tests3 / 3call:run_tests3 / 3w:possible3 / 3w:efficient9 / 5w:key3 / 3w:each15 / 7All ten: 57 references to 23 completions. Ranks 7–10: 30 references to 7.
Some repetition is expected because a single response can trigger multiple structural and lexical features at once. Even after event grouping, several structural events split out closely related test-helper definitions and invocations, while separate prose events revisited the same transition toward longer explanations. Reference overlap counts track sample-ID reuse across events, showing why selected-reference counts and event totals overstate the number of independent behavioral changes while leaving semantic redundancy across all 428 events unmeasured.
Detection is only the beginning
Auditing the Aria report changed how I evaluate Rollout Diff. A detector can accurately flag a shift in a measurable feature while leaving the feature’s semantic validity, causal link to training, and practical value open to question.
I kept the detector thresholds and ranking weights unchanged and focused on the inspection workflow. During the audit I used Rollout Diff’s exemplar inspector to read the original responses, and afterward I added matched-completion comparisons to pair feature-present and feature-absent rollouts from the same prompt and training step.
Those side-by-side comparisons remain descriptive, and establishing a formal false-positive rate or a better ranking algorithm remains open work. Rollout Diff surfaced 428 events on Aria. Building the inspection workflow to isolate which ones matter is the next step.
Analysis details
- Data: the Aria run (gutenbergpbc/aria-reward-hacking), Qwen3-4B on LeetCode-style tasks. Each of its 200 steps samples 16 prompts with 16 completions each.
- Report: Rollout Diff at
2cdafa9with its default options. The floor, prompt and reference analyses read that saved report and the original rollouts at12ca256. None reran the detector or changed its thresholds or ranking. - Repeated prompts: completions pooled per prompt and window, every prompt weighted equally. The standard error is the standard deviation of prompt-level changes over √240, which treats prompts as independent sampling units.
- Review: the top ten events were read through their selected exemplars, which favor early occurrence, reward deviation and prompt diversity, so they are not a representative sample. No generated code was executed.