It found a 38% faster sprint recipe — then optimised itself into instability.
A 2.302 s (44%) gain, measured the hard way: Beam's slower seed against the control's faster one. Beam's two seeds sit 0.120 s apart — tighter than the unchanged recipe's own 0.219 s. It also beats Pollen's published reference (4.883 s), which used 3× the training budget.
| recipe | seed 1 | seed 2 | spread | |
|---|---|---|---|---|
| Control — unchanged recipe forward_reward 2.0 | 5.438 s @ 0.50 m/s | 5.219 s | 0.219 s | stable |
| Beam round 1 forward_reward 2.0 -> 3.0 | 3.403 s @ 0.75 m/s | 3.271 s | 0.132 s | stable |
| Beam round 2 forward_reward 2.0 -> 4.0 | 2.795 s @ 0.90 m/s | 4.469 s | 1.674 s | unstable |
| Beam round 3 forward_reward 2.0 -> 4.0; 1 env.py edit(s) | 2.816 s @ 0.80 m/s | — | — | one seed |
| Beam round 4 forward_reward 2.0 -> 4.0; 1 env.py edit(s) | 3.367 s @ 0.90 m/s | — | — | one seed |
| Beam round 5 forward_reward 2.0 -> 4.0; step_reward 3.0 -> 5.0 | 2.751 s @ 0.80 m/s | 3.159 s | 0.408 s | stable |
| Beam round 6 forward_reward 2.0 -> 4.0; upright_reward 2.0 -> 3.0; step_reward 3.0 -> 5.0 | 2.945 s @ 0.90 m/s | 3.085 s | 0.140 s | stable |
| Beam round 7 forward_reward 2.0 -> 4.0; upright_reward 2.0 -> 3.0; step_reward 3.0 -> 5.0; 1 env.py edit(s) | 2.917 s @ 0.85 m/s | 2.797 s | 0.120 s | stable |
| Beam round 8 forward_reward 2.0 -> 4.0; upright_reward 2.0 -> 3.0; step_reward 3.0 -> 5.0; 2 env.py edit(s) | 2.745 s @ 0.85 m/s | 3.252 s | 0.507 s | unstable |
| Beam round 9 forward_reward 2.0 -> 4.0; upright_reward 2.0 -> 3.0; step_reward 3.0 -> 5.0; 2 env.py edit(s) | 3.394 s @ 0.75 m/s | 2.979 s | 0.415 s | stable |
“Spread” is the same commit retrained on a different seed. A difference smaller than the spread is noise, not a result — and the spread is recipe-dependent, not one global number.
Left: the unchanged recipe. Right: the recipe Beam engineered. Same 1,000-iteration budget, same evaluator, raced side by side.
Rendered with the Arena's own arena clip from the recorded traces of both policies.
Speed was bought by losing low-speed walking entirely. The Arena sweeps 21 speeds and keeps the best, so this is invisible to the score — but on hardware a duck that cannot walk at 0.2 m/s is worse, not better. Point an optimiser at a best-over-sweep metric and it will specialise.
| recipe | 0.20 m/s | 0.25 m/s | 0.30 m/s | 0.35 m/s |
|---|---|---|---|---|
| control · fwd 2.0 | 16.49 s | 10.85 s | 8.04 s | 6.72 s |
| Beam r1 · fwd 3.0 | fall | timeout | 6.94 s | 5.83 s |
| Beam r2 · fwd 4.0 | timeout | timeout | fall | 5.85 s |
“Raising forward_reward from 2.0 to 3.0 makes track_linear_velocity more important relative to the gait and posture terms, so the policy should push farther up the speed sweep and finish faster while heading_hold keeps it straight.”
No prior run exists in this session. The only measured bar is Pollen's reference at 4.883 s, 5/5 finishes, 0.55 m/s. The recipe already commands the full Arena sweep, 0.20–1.20 m/s straight ahead, with heading_hold and upright rewards in place. The first change should follow the knob-first discipline and increase the direct speed-tracking incentive without altering physics, observations, or reward structure.
@@ -11,7 +11,7 @@ seed = 1 -value = 2.0 +value = 3.0
| change type | knob |
| predicted | 4.4 s, 5/5 @ 0.6 m/s |
| actual | 3.403 s, 5/5 @ 0.75 m/s (ckpt 999) |
| error | -0.997 s · pessimistic |
| tokens | 37242 in / 4430 out |
“Raising forward_reward from 3.0 to 4.0 makes speed tracking dominate the gait/posture stack even more, pushing the speed plateau beyond ~0.60 m/s actual while heading_hold keeps drift bounded.”
Round 1 raised forward_reward 2.0→3.0 and produced a large, healthy gain: 3.403 s, 5/5, 0.75 m/s (vs control 5.438 s), with fell_over 0.167 and episode length 983. The sweep shows actual speed plateaus near 0.59–0.60 m/s above command 0.75 (0.80→3.52 s, 0.85→6.25 s), so the remaining bottleneck is the policy's willingness/safety margin to push stride frequency higher. The strongest single lever is still the speed-tracking weight, so increase it further.
@@ -11,7 +11,7 @@ seed = 1 -value = 2.0 +value = 4.0
| change type | knob |
| predicted | 3.1 s, 5/5 @ 0.8 m/s |
| actual | 2.795 s, 5/5 @ 0.90 m/s (ckpt 750) |
| error | -0.305 s · pessimistic |
| tokens | 39435 in / 4458 out |
“Keeping forward_reward at 4.0 and doubling heading_hold's weight from 1.0 to 2.0 makes sustained yaw drift at speed cost enough to discourage crab-walking while preserving the speed-tracking gain, so the policy should finish reliably at 0.95 m/s or above.”
Round 2 improved the best score to 2.795 s, 5/5, at 0.90 m/s, but its sweep shows the policy still falls at 0.95–1.00 m/s and stalls at higher commands; the known failure mode is crab-walking/yaw drift at speed. Since every run resets to the pristine recipe, the forward_reward gain from round 2 must be carried forward along with the new heading-control change.
@@ -11,7 +11,7 @@ seed = 1 -value = 2.0 +value = 4.0 @@ -306,7 +306,7 @@ def make_env_cfg( - weight=1.0, + weight=2.0,
| change type | combined |
| predicted | 2.5 s, 5/5 @ 0.95 m/s |
| actual | 2.816 s, 5/5 @ 0.80 m/s (ckpt 750) |
| error | +0.316 s · optimistic |
| tokens | 39760 in / 7150 out |
“Tightening heading_hold std from 0.4 to 0.2 at unchanged weight 1.0 makes sustained yaw drift at speed cost enough gradient to discourage crab-walking without over-constraining the gait, pushing the failure cliff above 0.90 m/s while keeping 5/5 finishes.”
Round 3 (forward_reward 4.0 + heading_hold weight 2.0) scored 2.816 s, 5/5 at 0.80 m/s, slightly worse than round 2's 2.795 s at 0.90 m/s with heading_hold weight 1.0 — so extra heading weight hurt top speed rather than helping. The sweep shows a stability cliff between 0.90 (2.93 s) and 0.95 (4.24 s), and the diagnosed failure mode is crab-walk yaw drift at speed. The root cause is that heading_hold's std=0.4 makes a steady ~3 deg drift almost free (exp(-0.052^2/0.4^2)=0.997), giving almost no gradient to correct it; tightening std keeps the reward mass similar (weight stays 1.0, avoiding round 3's over-constrai
@@ -11,7 +11,7 @@ seed = 1
-value = 2.0
+value = 4.0
@@ -307,7 +307,7 @@ def make_env_cfg(
- params={"std": 0.4, "asset_cfg": SceneEntityCfg("robot")},
+ params={"std": 0.2, "asset_cfg": SceneEntityCfg("robot")},
| change type | env |
| predicted | 2.6 s, 5/5 @ 0.95 m/s |
| actual | 3.367 s, 5/5 @ 0.90 m/s (ckpt 999) |
| error | +0.767 s · optimistic |
| tokens | 39908 in / 5546 out |
“Raising step_reward from 3.0 to 5.0 increases air_time reward weight, encouraging more dynamic and stable stepping at high commanded speeds while forward_reward 4.0 maintains speed tracking, pushing the failure cliff above 0.90 m/s with 5/5 finishes.”
Round 4 (forward_reward 4.0 + heading_hold std 0.2) scored 3.367 s, 5/5 at 0.90 m/s — worse than round 2 (2.795 s, 5/5 at 0.90 m/s) which used default heading_hold std 0.4. Tightening the heading_hold std over-constrained the gait and hurt stability, as the sweep showed scattered falls across many speeds. The best config so far is round 2: forward_reward 4.0, heading_hold default (std 0.4, weight 1.0), step_reward 3.0, upright_reward 2.0. The untried knob is step_reward (air_time weight), currently 3.0. Increasing it should encourage more pronounced stepping at speed, potentially improving gait stability and push
@@ -11,7 +11,7 @@ seed = 1 -value = 2.0 +value = 4.0 @@ -23,6 +23,6 @@ max = 5.0 -value = 3.0 +value = 5.0
| change type | knob |
| predicted | 2.6 s, 5/5 @ 0.95 m/s |
| actual | 2.751 s, 5/5 @ 0.80 m/s (ckpt 999) |
| error | +0.151 s · optimistic |
| tokens | 40171 in / 4401 out |
“Raising upright_reward from 2.0 to 3.0 strengthens trunk-verticality pressure at speed, reducing forward-lean falls in the 0.95-1.10 m/s band while preserving the dynamic gait from step_reward=5.0, pushing 5/5 robustness to 0.90+ m/s.”
Round 5 scored 2.751 s, 5/5 at 0.80 m/s (best so far), with forward_reward=4.0 and step_reward=5.0. Its sweep shows a fall band at 0.95-1.10 m/s (falls at 0.95, 1.00, 1.05, 1.10) but success at 0.85 (2.71 s) and 1.15-1.20, indicating trunk/yaw instability in the mid-speed range. upright_reward has never been changed from its default 2.0 across all five rounds, so it is the one knob left to tune for stability at speed. track_linear_velocity reached 2.055 and fell_over 0.25, so the gait is healthy but not robust enough in the 0.95-1.10 band.
@@ -11,18 +11,18 @@ seed = 1 -value = 2.0 +value = 4.0 -value = 2.0 +value = 3.0 -value = 3.0 +value = 5.0
| change type | knob |
| predicted | 2.6 s, 5/5 @ 0.85 m/s |
| actual | 2.945 s, 5/5 @ 0.90 m/s (ckpt 750) |
| error | +0.345 s · optimistic |
| tokens | 40121 in / 6435 out |
“Halving the action_rate curriculum's final weights (−0.1→−0.4 instead of −0.1→−0.6 by iter 1000) reduces smoothing pressure on the dynamic gait, allowing the policy to sustain speeds beyond 0.90 m/s and pushing the failure cliff above 1.00 m/s while keeping 5/5 finishes.”
Round 6 (forward 4.0, upright 3.0, step 5.0) scored 2.945 s, 5/5 at 0.90 m/s (seed 1) and 3.085 s (seed 2), spread 0.140 s — stable but not faster than round 5's 2.751 s. The sweep shows a failure cliff at 1.00–1.05 m/s (falls at 1.76 m and 1.57 m), while 0.80–0.90 m/s is the reliable sweet spot. Critically, checkpoint 750 (action_rate −0.4) beat checkpoint 999 (action_rate −0.6) in both rounds 5 and 6, suggesting the action_rate curriculum's final tightening is suppressing the dynamic gait needed at higher speeds. Multiple things ramp at iter 1000 (action_rate −0.4→−0.6, head_pose_bias 1.0→2.0, CoM 0.01→0.015),
@@ -11,18 +11,18 @@ seed = 1
-value = 2.0
+value = 4.0
-value = 2.0
+value = 3.0
-value = 3.0
+value = 5.0
@@ -717,10 +717,10 @@ def make_env_cfg(
- {"step": 750 * NUM_STEPS_PER_ENV, "weight": -0.4},
- {"step": 1000 * NUM_STEPS_PER_ENV, "weight": -0.6},
- {"step": 1250 * NUM_STEPS_PER_ENV, "weight": -0.8},
- {"step": 1500 * NUM_STEPS_PER_ENV, "weight": -1.0},
+ {"step": 750 * NUM_STEPS_PER_ENV, "weight": -0.3},
+ {"step": 1000 * NUM_STEPS_PER_ENV, "weight": -0.4},
| change type | env |
| predicted | 2.8 s, 5/5 @ 0.9 m/s |
| actual | 2.917 s, 5/5 @ 0.85 m/s (ckpt 750) |
| error | +0.117 s · optimistic |
| tokens | 40788 in / 10221 out |
“Keeping the round-7 recipe and tightening heading_hold std from 0.4 to 0.2 makes sustained yaw drift at speed costly enough to reduce crab-walking, pushing the 0.95–1.00 m/s failure band above 1.00 m/s while keeping 5/5 finishes.”
Round 7 reproduced well across seeds (2.917 s and 2.797 s, spread 0.120 s) and improved robustness, but the sweep still falls at 0.95–1.00 m/s, likely from sustained yaw drift/crab-walking at speed. Heading hold currently charges only a tiny penalty at a 3° offset because std=0.4, so tightening it to 0.2 should discourage crab-walking while preserving the winning reward knobs and smoother action-rate curriculum.
@@ -11,18 +11,18 @@ seed = 1
-value = 2.0
+value = 4.0
-value = 2.0
+value = 3.0
-value = 3.0
+value = 5.0
@@ -307,7 +307,7 @@ def make_env_cfg(
- params={"std": 0.4, "asset_cfg": SceneEntityCfg("robot")},
+ params={"std": 0.2, "asset_cfg": SceneEntityCfg("robot")},
@@ -717,10 +717,10 @@ def make_env_cfg(
- {"step": 750 * NUM_STEPS_PER_ENV, "weight": -0.4},
- {"step": 1000 * NUM_STEPS_PER_ENV, "weight": -0.6},
- {"step": 1250 * NUM_STEPS_PER_ENV, "weight": -0.8},
| change type | combined |
| predicted | 2.7 s, 5/5 @ 0.9 m/s |
| actual | 2.745 s, 5/5 @ 0.85 m/s (ckpt 999) |
| error | +0.045 s · optimistic |
| tokens | 41320 in / 6833 out |
“Doubling heading_hold weight from 1.0 to 2.0 on top of the round-7 recipe increases yaw-drift gradient at speed to reduce crab-walking, while keeping std at 0.4 preserves the tight two-seed spread, pushing best finish above 0.85 m/s at 5/5.”
Round 8 improved seed-1 to 2.745 s but destroyed reproducibility (spread 0.507 s, seed 2 regressed to 3.252 s at ckpt 500 and collapsed at 750/999). The culprit was tightening heading_hold std from 0.4 to 0.2, which makes the reward landscape too sharp and seed-sensitive. Round 7 (std 0.4, halved action_rate) was the best reproducible baseline (2.917/2.797, spread 0.120). The remaining problem is heading drift at speed (crab-walking), which needs more gradient without changing the reward's shape. Doubling heading_hold weight (1.0→2.0) scales gradient everywhere while keeping std at the reproducible 0.4, addressin
@@ -11,18 +11,18 @@ seed = 1
-value = 2.0
+value = 4.0
-value = 2.0
+value = 3.0
-value = 3.0
+value = 5.0
@@ -306,7 +306,7 @@ def make_env_cfg(
- weight=1.0,
+ weight=2.0,
@@ -717,10 +717,10 @@ def make_env_cfg(
- {"step": 750 * NUM_STEPS_PER_ENV, "weight": -0.4},
- {"step": 1000 * NUM_STEPS_PER_ENV, "weight": -0.6},
- {"step": 1250 * NUM_STEPS_PER_ENV, "weight": -0.8},
| change type | env |
| predicted | 2.8 s, 5/5 @ 0.85 m/s |
| actual | 3.394 s, 5/5 @ 0.75 m/s (ckpt 750) |
| error | +0.594 s · optimistic |
| tokens | 41694 in / 7576 out |
Beam never ran anything; the operator never decided anything. Each change was applied unedited to a fresh branch off a pinned pristine tag, then gated by: schema → path check → knob ranges → exact-once search/replace → a banned-pattern guard → Pollen's own pytest suite (137 tests) → a CPU smoke test. A failure went back to Beam verbatim as a same-round retry.
The guard earned its place: round 3's first reply quoted value = 2.0 / min = 0.5 / max = 5.0, which matches both forward_reward and upright_reward. A naive apply would have silently changed the wrong knob and trained a recipe nobody intended. The exact-once rule rejected it; Beam self-corrected on the first retry.
Scoring every checkpoint mattered too: round 2's final checkpoint scored 6.114 s and its best scored 2.795 s. Scoring only the last one — the obvious shortcut — would have recorded round 2 as worse than the control.
Simulation only. One event, one model, one provider. Two seeds per confirmed recipe, one for the rest — enough to show fwd=4.0 is unstable, not enough to characterise the distribution. No A2 arm, so the control's own seed spread is unmeasured; the gain is 19× the Beam recipe's spread, which is strong but not the same statement. Local Arena scores are indicative — only the Space times an official entry, and simulations on different CPU architectures drift after a few seconds (our local export and the job's own x86 export agreed exactly at 2.795 s). Human-operated, not autonomous. Beam saw session history only, not the confirmation arms, so it could not price variance even in principle.
On the public board our entry ranks #191 of 252. That rank is not the result: the median there is 1.196 s and the top entries reach 0.817 s — 2.45 m/s average while commanded 0.20 m/s, i.e. by ignoring the speed command. Pollen's own reference sits near #225 on that same board. Our entry obeys its command.