RL Code Review Environment
A reinforcement-learning curriculum for code-review agents
- Period
- 2026
- Role
- Author
- Status
- live
Overview
A custom RL environment where an agent's action is the full corrected Python source for a messy file. It is a curriculum, not a single task: the environment starts with PEP8 hygiene, moves to shell-injection security, then to structural complexity, advancing automatically once a stage is cleared at a 0.95 reward. It runs as a Hugging Face Space with a Qwen-2.5-Coder 7B baseline agent, containerised with Docker.
How it works
Built against OpenEnv's Environment interface, with reset and step returning
an observation of messy_code, feedback and a scalar reward between 0 and 1.
MAX_STEPS = 10 bounds an episode.
Reward blends two objectives equally:
- Functional score — the fraction of unit tests the submitted function passes.
- Quality score — Ruff static analysis with a per-violation penalty.
Both are gated by an AST-based structural identity check. The submitted code is parsed and compared against the original: the target function must still exist under the same name and the argument count must be unchanged. Identity is a multiplier on the total, not a bonus, so an agent cannot pass by deleting the function, renaming it, or widening its signature. That check is the main defence against the obvious reward hack here.
The quality half is stage-specific, which makes the rule selection part of the task definition rather than a constant:
easyselectsE,W,F— pycodestyle errors and warnings plus Pyflakes.mediumselectsS602— the Bandit rule forsubprocesscalls withshell=True.hardselectsC901with McCabe max complexity forced to 3.
On the security stage the penalty is binary: any S602 hit scores zero quality.
On the other two it decays by 0.1 per violation. The raw total is rescaled into
(0, 1) so partial credit is always strictly positive.
What made it non-trivial
- Two objectives on one scalar. Tests-passing and lint-clean are different signals, and weighting them evenly means neither can be optimised alone.
- Without the identity gate, deleting or renaming the target function trivially satisfies the tests.
- Advancing the curriculum resets state to the new task's original code, so the agent cannot carry a fixed solution across stage boundaries.
- Ruff is invoked as a subprocess against stdin with JSON output, so lint failures have to be handled without taking the episode down.
What I learned
- The reward function is the environment. Everything interesting — stage gating, rule selection, the identity multiplier — is shaping, not mechanics.
- Verifying structure via the AST is cheap and blocks the most obvious exploit.
- Stage-specific rule selection doubles as curriculum: the agent never sees two different kinds of "quality" at the same time.
- The baseline agent clears the curriculum in four steps once it stops making edits that break the tests, which is a useful sanity check that the reward is not unpassable.