Collective intelligence  ·  Part 1  ·  Lessons from the groundwork  ·  Part 2  ·  Part 3 →  ·  Part 4 →

The Harness Is Part of the Model

Before training agents to work as a team, I ran the groundwork, and the agent harness kept turning out to matter more than the agents. Untrained, a 27B model did no better with teammates than alone, and the harness's stopping rules changed its behaviour more than any collaboration tool. Single-call GRPO nearly doubled a 9B model on FrontierCS, but trained as an agent inside a general coding harness, the same model showed no gain by step 10, thinking only a few dozen tokens per call. Agentic RL used 8× the GPU-hours per step, mostly idle GPUs rather than extra work. And the failures that mattered most were silent.

Models · Qwen3.8-27B (baselines) · Qwen3.5-9B (RL) Tasks · FrontierSmith → FrontierCS · NL2Repo · KernelBench · Toolathlon · EnterpriseOps-Gym Stack · vLLM · verl / FSDP2 · Qwen Code · GRPO
97-second summary · animated with Manim · narration voice: ElevenLabs
Findings so far
  1. Untrained, the harness mode doesn't matter. Solo, built-in subagents, team mode and three versions of my collective harness are all within noise on up to 96 tasks (one attempt each). Collaboration appears only where the work splits by module, and the best "collective" number came from a single agent working longer under new stopping rules.
  2. Single-call GRPO nearly doubled FrontierCS. Qwen3.5-9B went from 6.22 → 12.22 on 172 problems by step 70, paired +6.00 [+4.00, +8.33]. Most of the gain came by step 40, and it then flattened; part 2 looks at why.
  3. Trained as an agent in a general coding harness, it showed no sign of learning by step 10. The eval change was −0.02 [−1.01, +0.90], and reward on revisited training tasks didn't rise. In the harness it thought a median of 27 tokens per call. My minimal loop raised that only to 41, so short per-call thinking is largely the model's own multi-turn habit.
  4. Agentic RL used about 8× the GPU-hours per step for the same number of tokens; only ~1.4× of that is extra work. The rest is idle or underfilled GPUs. Feeding the model its own token ids (2.7× faster updates) and speculative decoding (1.9×) are already in. The biggest remaining fix, overlapping rollout and training, would change the algorithm. When serving many long-context agents, admission control alone was worth about 10×.
  5. The dangerous failures are silent. These include judges that return wrong scores without erroring, advantage scaling that amplifies noise, gradients that diverge under parallelism, and graders a policy could game.
Epistemic status

No team has been trained yet. The 27B baselines are single attempts per task, which vary by up to 0.25, so they describe behaviour rather than establish effects. The RL results are paired over the same problems with bootstrap intervals. The collective-intelligence design is summarised in §7, clearly marked as a plan.

Snapshot of work through October 10, 2026.


01 · Result 1 · Untrained 27B

Solo, subagents, a team, or a collective: on single attempts, nothing wins

Before training anything, I wanted to know what an off-the-shelf model does with teammates. I ran Qwen3.8-27B inside the Qwen Code agent harness on a 96-task eval set in six arms. Three are the harness's own modes: solo, built-in subagents (a lead delegates pieces to helpers with fresh contexts), and agent-team mode (a leader plus up to three teammates and a shared task list). The other three are versions of a collective harness I built, in which any agent can fork a copy of itself and agents talk peer to peer (§7 describes it briefly). Every arm used the same model, sampling, 262K context window and compaction rule, the same caps (1 h for FrontierCS, NL2Repo and KernelBench; native step caps for the tool and enterprise tasks), and one attempt per task.

The three versions of my harness: v1 has a shared workspace, fork with full context, messages and a whiteboard. v1 + submit adds a team-wide submit that ends the task. v2 gives each agent its own git worktree (a fork copies its parent's tree), grades the submitting agent's tree, and stamps the time left on every tool result.

The eval set was chosen to be hard for a single agent. I picked tasks only from published per-task difficulty, never from my own runs: low but nonzero scores for comparable models, and often failed by frontier agents. I dropped anything that overlaps the training pools, for example ten FrontierCS problems that are copies of FrontierSmith training problems. None of them is in the 172-problem set used for the 9B results.

Figure 1 · Qwen3.8-27B: mean score by harness arm, one panel per task family
solo (vertical line = solo score)my collective harness, v2built-in modes and earlier versions of my harness
Toolathlon 8 tasks · 0–1 Solo 0.500 Subagents 0.500 Team 0.625 Collective v1 0.750 v1 + submit 0.625 Collective v2 0.500 EnterpriseOps-Gym 16 tasks · 0–1 Solo 0.312 Subagents 0.500 Team 0.500 Collective v1 0.500 v1 + submit 0.438 Collective v2 0.312 FrontierCS · 1 h 40 tasks · 0–1 Solo 0.215 Subagents 0.262 Team 0.189 Collective v1 0.192 v1 + submit 0.238 Collective v2 0.233 FrontierCS · 3 h 20 of the 40 Solo 0.251 Subagents not run Team 0.225 Collective v1 not run v1 + submit 0.238 Collective v2 0.309 NL2Repo 22 tasks · 0–1 Solo 0.454 Subagents 0.435 Team 0.423 Collective v1 not run v1 + submit 0.464 Collective v2 0.465 KernelBench · fast_1 10 tasks Solo 0.6 Subagents 0.3 Team 0.7 Collective v1 not run v1 + submit not run Collective v2 0.6
No arm reliably beats solo. Bars right of the blue line beat solo on that family, but no difference is statistically significant (paired sign tests, p ≥ 0.11 for every pair, most above 0.2). On Toolathlon and EnterpriseOps-Gym my three harness versions all ran single-agent, so they are near-repeats, and they moved by up to 0.25. Scores are each benchmark's own 0–1 metric; KernelBench's fast_1 is the share of tasks with a correct kernel faster than the PyTorch reference. "Not run" means that arm wasn't evaluated on that family.
Table 1 · Qwen3.8-27B, mean score per family, one attempt per task
Family (tasks, cap)SoloSubagentsTeamCollective v1v1 + submitCollective v2
Toolathlon (8, step cap)0.5000.5000.6250.7500.6250.500
EnterpriseOps-Gym (16, 50 turns)0.3120.5000.5000.5000.4380.312
FrontierCS (40, 1 h)0.2150.2620.1890.1920.2380.233
FrontierCS (20 of the 40, 3 h)0.251–0.225–0.2380.309
NL2Repo (22, 1 h)0.4540.4350.423–0.4640.465
KernelBench L2/L3 (10, 1 h), fast_10.60.30.7––0.6
No difference here is statistically significant. Paired sign tests between arms give p ≥ 0.11 for every pair, most above 0.2. Three near-identical single-agent runs on the tool and enterprise families moved by up to 0.25 (3 of 8 and 6 of 16 tasks flipped), so with one attempt per task the arms can't be separated; that takes at least 4 attempts per task. A dash means the arm wasn't run on that family. KernelBench is scored with the benchmark's own verified evaluator.

So the honest headline is a null: on these tasks, an untrained 27B model doesn't get better by being given teammates, in either the harness's built-in team modes or mine. Three behaviours behind the numbers are more informative than the means.

Collaboration only appears where the work splits

In the collective harness the model never forked on tool workflows, enterprise tasks or GPU kernels. On FrontierCS, where the deliverable is a single optimised program, it rarely forked (v1: 2 of 80 trials; v2: 4 of 40). On NL2Repo, which means building a multi-module package from a spec, it forked often (v1 + submit: 15 of 22 trials, 44 forks, up to six agents at once; v2: 9 of 22 trials, 27 forks). The work divides by module, so the model divides it.

The structure it built was always the same, and nobody told it to: in v1 and v1 + submit, all 47 forks came from the first agent, one level deep. That agent made itself the lead, wrote a brief for each worker into the workspace, forked workers in waves, ran the full test suite and sent failures back to whoever owned the module. Workers did talk to each other, warning about broken imports. But the lead also spent a lot of time idle, waiting on messages, and the payoff was small (v1 + submit: forking trials +0.03 over solo; v2: −0.04).

The clearest collaboration was on one hard FrontierCS problem (v1 + submit). The lead forked two helpers to build a brute-force checker. One saw that its assignment duplicated the other's, took the orphaned test harness instead and told both, and later reported two compile errors back to the lead. That trial scored 0.486 against 0.176 solo, and it didn't recur in v2.

The best collective number came from one agent and new stopping rules

The second version of the harness gave every agent its own git worktree and showed time remaining in every tool result. Its best number, 0.309 vs 0.251 solo on 3-hour FrontierCS (10 tasks better, 8 worse, not significant), came from trials where the first agent worked alone and used the time: its median submission moved from about 43 to about 75 minutes. At 1 hour the behaviour flipped the other way: submissions rose from 17 to 37 of 40 trials, and runs that hit the time cap fell from 16 to 2. v2 changed worktrees, submit semantics and the clock all at once, and I haven't ablated the clock alone. Even so, its stopping rules changed when agents stopped more than any collaboration primitive changed what they did.

The submit action was adopted immediately, as a "done" button. That cost points: two FrontierCS submissions at minutes 4 and 27 scored 0, and four trials submitted while workers were still running.

Long tasks are compaction tasks

Between 77% and 90% of 1-hour trials compacted their context at least once, in every arm, some up to five times, and the median peak prompt was about 225K tokens. At this length "solo vs team" is partly "one lossy memory vs several". That's the regime where a team should help, and one more reason a single-attempt null is a starting point, not a verdict.

Other things this baseline caught
  • Lighter load, not delegation, explained a gain. The subagents arm on FrontierCS decoded at 167 tokens/s against solo's 148, because it ran with less load. Trials that never called a subagent gained as much as trials that did.
  • Forking slows decoding. NL2Repo collective trials ran at 114 tokens/s against 149 solo, which costs real work under a time cap.
  • A 32K per-call output cap halved solo FrontierCS (Table 5).
  • Team mode stranded work. In two NL2Repo tasks, teammates' packages were left in worktrees the grader never saw, and the tasks scored 0.
  • One collective trial scored 0.551 where every other arm scored 0. On a 3-hour FrontierCS problem, the lead built test tools, forked an "integrator" and an "optimiser", and checked every merge with sanitizers and brute force. It didn't recur in the next version.

The untrained model has the mechanics of teamwork (forking, briefs, peer fixes, integration testing) but not the judgment of when it pays. That judgment is what RL is supposed to teach.


02 · Result 2 · 9B post-training

GRPO on FrontierSmith nearly doubled FrontierCS, then flattened

A team-RL result would mean nothing if the RL stack couldn't improve a single model, so the first training run was solo and as simple as possible. I trained Qwen3.5-9B with single-turn GRPO on the 200 FrontierSmith training problems and evaluated it on all 172 algorithmic FrontierCS problems with the benchmark's own partial-credit judge, 5 samples per problem. Scores here are on a 0–100 scale; §1 reported the same judge on 0–1. Avg@5 is the mean over the 5 samples, and Best@5 the mean of each problem's best sample.

Figure 2 · Qwen3.5-9B on FrontierCS (172 problems), Avg@5 with 95% CI
0 4 8 12 16 Avg@5 base 10 20 30 40 50 60 70 80 training step stopped at 77 6.22 12.22 step 70: +6.00 [+4.00, +8.33] vs base
Most of the gain arrives by step 40. Step 70 beats the base model by +6.00 [+4.00, +8.33] (81 problems better, 17 worse), but it isn't distinguishable from step 50 (+0.37 [−0.82, +1.54]). The share of samples that ran out of budget fell from 42% to 9%, and the share that compiled rose from 43% to 86%. The shaded band is the 95% bootstrap CI over problems; hover a point for details.

For reference, the published FrontierSmith recipe reports 1.80 → 10.62 for this model after 100 steps at a 16K-token budget. In that setting my base model scores 1.19; at my 64K budget it starts at 6.22. So the comparable number is the gain, not the endpoint.

A 16K budget starves GRPO

At the published 16K-token cap with my 8-group batches, 98% of responses were truncated and every step-1 group scored 0. When every sample in a group scores the same, the group-relative advantage is zero and there is nothing to learn. The paper still learned at 16K, from the handful of problems that do earn reward; I didn't want to rely on that. At 64K, step-1 reward was about 0.15, with signal in 6 of 8 groups.

Teaching it to stop thinking

Even at 64K, more than half of the responses ran out of budget mid-thought. Injecting the model's own early-exit sentence at a 32K thinking budget wasn't enough. It kept reasoning in the answer (a median of about 22K more tokens), and 40 of 64 step-1 samples wrote a second </think>. What worked was a 56K budget followed by the early-exit sentence plus an opening code fence, both masked from the loss, and an 8K answer cap. That brought it to 9 of 64. The new cap did not make the base model better (−0.50 [−1.54, +0.51]), so the later gain is training, consistent with the model learning to finish. In the base model, samples whose thinking closed naturally scored 11.1 on average against 1.75 for force-closed ones.

Keep loops and failures in the batch

About 14% of base-model responses loop, typically detectable only around 50K tokens in. Dropping them would hide looping from the gradient and bias the group baseline upward, so loops run to the budget and stay in the batch as failures. An early-stop detector slowed generation 20–35% to save about 3–4% of tokens, so I turned it off. Retries are only for infrastructure failures, and those are masked, never scored as 0. I also dropped an overlong-response penalty that would have dominated the signal: mean step-1 reward was −0.62 with the penalty, while task scores were around 0.1.

Why it flattened

Dynamic sampling draws 12–16 problems per step to fill 8 groups, so by step 70 each of the 200 training problems had been drawn about 5.5 times and trained on about 3. On repeat draws, training reward roughly doubled (0.136 → 0.303). Groups where all eight samples got the same partial-credit score rose from 1% to 10%, while all-zero groups fell from 24% to 5%; identical groups carry no gradient. Held-out gains stopped. (I first read this as fitting a small pool; part 2 corrects that: training reward and reward on the training problems went flat too, so it's a wall of model and recipe.) I stopped the run at step 77 and kept the step-40 and step-70 checkpoints.

One detail matters for anyone reaching for an entropy-preserving fix. With one optimizer update per step, the importance ratio is exactly 1. The PPO clip never fires (clip fraction 0.0 at every step), so clip-higher and KL-Cov are no-ops: KL-Cov produced bit-identical updates. Fixes that act on the gradient directly, such as Clip-Cov's token masking, or more problems, are the levers that are left.

Why the 40-problem subset can mislead

For quick checks I also kept a 40-problem subset: §1's hard FrontierCS set, on which the base model scores only 0.47. On it, step 20 looked no better than step 10 (−0.16 [−1.35, +1.06]), while on all 172 problems it was clearly better (+1.40 [+0.05, +2.87]). The subset has only 10–16 problems the model can partly solve, too few to resolve a trend. It's useful as a smoke test, not for deciding whether training is working.

The single-call recipe
  • GRPO with std-normalised group advantages, group size 8, 8 groups per step. Dynamic sampling keeps only groups with a reward spread (12 prompts at a time, up to 3 rounds).
  • Reward is the task score with no length term; one optimizer update per step.
  • PPO clip 0.2 with dual clip 3, sequence-mean-token-mean, no entropy bonus, KL loss 0.001 (low-variance KL) to the frozen base.
  • AdamW, lr 1e-6 constant, weight decay 0.01, grad clip 1.0.
  • Sampling at T 1.0, top-p 1.0. Evaluation uses the same thinking cap: 5 samples per problem, Avg@5 and Best@5.

03 · Result 3 · The agentic arm

Inside a general-purpose coding harness, the same model showed no sign of learning by step 10

A team has to run inside an agent harness, so the next step was the same model, problems and judge, trained as a solo agent in a coding harness (Qwen Code). That meant a 262K context window, many short model calls per trial, and deadlines of 15–30 minutes. The recipe was token-level GRPO with a standard-deviation floor on the advantage, KL 0.01 to the base model, one update per batch, and parallel dynamic sampling (launch 12 groups × 8 trials, keep the first 8 groups with a reward spread). Only the model's own sampled tokens were trained. It isn't identical to the single-call recipe: it uses a token-mean loss, a standard-deviation floor, KL 0.01, a wider clip, a warmup, and an 8K per-call thinking guard (which fired on only 0.03% of calls).

Training looked healthy: rewards 0.18–0.33, gradient norm about 0.15, no dropped trajectories. The eval disagreed.

Figure 3 · Paired gain over each arm's own base, same 40 problems, same judge
single callagentic harness
−2 −1 0 +1 +2 +3 +4 paired change vs own base (points, 0–100) Single call · step 10 Single call · step 20 Agentic harness · step 10 +1.83 +1.67 −0.02
Same problems, same judge, same number of steps; one arm moves and the other doesn't. Each arm is compared with its own base model in its own mode, because the mode alone changes the base score (about 2 in the harness vs 0.47 single-call). Whiskers are 95% paired bootstrap intervals over the 40 problems. On training tasks it revisited, the agentic arm's reward changed by −0.014 [−0.034, +0.007]. The subset's paired interval is about ±1.3 points, so this rules out only large gains (why the subset is coarse).

At step 10 both arms' KL to the base model was about 0.0003; the single-call arm's then kept rising (0.0015 at step 20, 0.0026 at step 30), and the agentic arm's barely moved. Changing the KL coefficient would have been the reflexive fix, but at that drift the KL term's pull is about a thousand times weaker than the reward's, so it was the wrong lever. Reading trajectories found the problem. On the same packing puzzle:

Across agentic evals, thinking was a median of 27 tokens per call. 3.5% of eval trials ended with no solution file; in the one I traced, the agent deleted it during cleanup. There are three likely contributors:

The harness also gave the agent no local scorer, although the benchmark's own agent mode provides one.

I stopped the run and replaced the general-purpose harness with a minimal loop of my own:

It follows the benchmark's official agent mode: unlimited submits in eval (training caps them at 100), credited with the best of the final and best submission, which is a different metric from the evals above. In a first smoke test, the base model in that loop still thinks briefly per call (median 41 tokens), but its planning call runs to a few thousand tokens, and it submits 16–39 times per trial against real feedback. Whether that trains is the next result.

A healthy training dashboard can sit on top of a policy that isn't learning anything useful. Read what the model actually does, and treat the harness as part of the model.


04 · AI infrastructure

Making agent RL affordable: what actually moved throughput

Agentic RL is expensive in a way single-call RL isn't. Per training step, the agentic arm used about 8× the GPU-hours of the single-call arm while generating about the same number of tokens. Most of this project's engineering went into closing that gap without touching what the model learns. Two rules made that safe:

Table 2 · Where the time and money went, per training step (9B)
Single callAgentic harness
Step time≈ 18 min≈ 43 min (35 with early stop)
GPU-hours per step≈ 1.2≈ 9.3
Tokens generated per step≈ 5.6M≈ 5.0M
Share of trainer tokens that are the model's own output≈ 96%≈ 42%
Generation throughput per GPU, averaged over the whole step including idle time≈ 2,200 tok/s≈ 400 tok/s
Why the agentic arm costs ~8×: only ~1.4× is extra work. The rest is idle or underfilled GPUs. Rollout and training GPUs wait on each other in turn, which accounts for about half of it; overlapping them would change the algorithm, which I chose not to do. Agents also generate serially with tool calls in between, so few requests are in flight per server. And each trajectory drags its prompt, file reads and compiler output into the trainer, so training sequences are 2.3× longer for the same amount of model output.

Serving many long-context agents

The workload is unusual. Up to a dozen agents share each inference replica, each holding up to ~200K tokens of context and taking short turns between tool calls. The biggest lesson is that decode is memory-bandwidth-bound: what pays is keeping the right prefixes in cache and the right number of agents per GPU, not adding raw capacity.

Table 3 · Serving: problem → fix → measured effect
ProblemModelFixMeasured effect
KV-cache thrash. 22 agents × ~192K tokens need ~4.2M tokens of KV against 3.5M available. Agents take turns cyclically, so LRU always evicts the prefix needed next: hit rate fell 97% → 1–4%, and every turn re-prefilled ~190K tokens.27BAdmission control. Never run more concurrent trials than the cache can hold (12–14 per replica on the 27B).≈10× throughput, the rate lost to thrashing. Before, 70 of 73 pilot trials hit a 2 h cap after a median of ~130 tool calls. Healthy 4 h runs made 367–893 calls.
Want more agents per replica.27BOffload KV to CPU memory.Thrash gone: evicted prefixes reload at ~118K tok/s. But replica throughput stayed flat (38–44 turns/min): adding agents (12 → 20 per replica) only divided it, with per-agent speed falling 4.4 → 2.2 turns/min. Kept as a safety margin, not a speedup.
Want more KV capacity.27BFP8 KV cache.Capacity 3.1M → 6.0M tokens. Quality held: top-1 agreement 99.1%, NLL −0.2% at 81K and +1.2% at 164K. But it was 13% slower at equal load, and doubling agents halved per-agent speed. Rejected; stayed on BF16.
Slow per-call decode.27B, 9BSpeculative decoding with the model's own multi-token-prediction head.27B: 1.9× agent turns/min (2 draft tokens). 9B: calls without it are ~1.9× slower. Single-call rollout generation −26%. Lossless: the trainer-vs-rollout log-prob gap is unchanged. Locked at 3 draft tokens.
How many agents per GPU?9BMeasured the knee on the 9B.12 → 18 agents per GPU: GPU output +5% (850 → 896 tok/s), and trials ran 32–44% slower. Knee ≈ 12. Prefix hits stay ~97% up to 16 agents per replica, 63–67% at 32.
Which tensor-parallel layout?9B, 27BMeasured both workloads.48 agents on 4 GPUs: four 1-GPU replicas beat one 4-GPU replica by 29% (976 vs 754 tok/s/GPU). Single-call RL rollout, the opposite: one 4-GPU engine is ~20% faster. 27B at 262K: 4-GPU replicas, for KV per agent.
Hybrid (linear-attention) model reused only 10.6% of a resubmitted prompt.9BKeep recurrent state at every cache block, not just prompt boundaries.10.6% → 90.3% reuse, same decode speed, identical greedy output. Costs ~4× cache blocks per kept prefix.
Decode micro-optimisations.9BChecked CUDA graphs and async scheduling.Already on for 90.8% of steps; a denser capture list gave +1–2%. Below the 5% bar; stopped.
Which inference engine?9BHead-to-head on one GPU (single, untuned runs).vLLM decoded 8–22% faster and held 2.7× more KV than SGLang, which also ignored the thinking budget with speculative decoding on. Stayed with vLLM.

Between rollout and trainer

The agentic arm's step was ~89 minutes at first. Token-in/token-out alone brought it to ~47. The other fixes below are smaller, two are built but not yet in a completed run, and every one was checked to leave the update unchanged.

Table 4 · Rollout and training: problem → fix → measured effect
ProblemFixMeasured effect
Re-tokenisation drift. The harness sends text, and the server re-tokenises it. In 3.3% of calls the model had sampled a non-canonical split, so ids no longer matched and the training sequence broke: 2.71× the unique tokens were trained.Token-in/token-out. Store the sampled ids and splice them into the next request.Overhead 2.71× → 1.05×. Update 3,184 → 1,171 s. Step ~89 → ~47 min. +73 ms per call. Stored ids are reused on ~98% of calls, so training sees exactly what the model generated. The base model was re-evaluated under the new path.
Dynamic sampling launches more groups than it keeps, and the step waits for the slowest.Cancel surplus groups once enough groups with a reward spread finish. Regrade the kept ones after load settles.One step: 2,598 → 2,085 s (−20%), about 100 s of it from faster reload and packing done at the same time. The kept set was identical on 13 replayed steps. Regrading matters: under rollout load the judge lowered 3 of 96 scores.
Trainer idle until the last group finishes.Streaming per-group gradient accumulation. Exact, so it replaces the one-step-stale async design, which would have changed the algorithm.Built and verified: matches the batch update to rel. L2 2.5e-8 with power-of-two loss prescaling. Projected wait after the last group: 871 → 460 s; not yet in a completed run. Deterministic kernels would cost +14%, so the faster kernels were kept: cosine is at the noise floor.
Checkpoint save on the critical path.Background, per-rank writes.43.5 s → 8–11 s, bit-identical weights.
Weight sync to rollout servers.Parallel per-shard copies.Checkpoint copy to three rollout hosts 31–37 s → 10–13 s; reload and probe 83 → 22 s.
Out of memory at step 2, not step 1. Adam's moments only exist after the first optimizer.step().Load optimizer state only around the step; re-offload after each update.Peak 70.5 GiB (+4.2 GiB sleeping inference engine) → 51.0 GiB in a two-update check, 60.2 GiB in the live run. Step 2 matches step 1 (§5).
Long agent chains (up to 202K tokens) lost 5.9% of trainable tokens at a 131K max length.Sequence parallelism with a head-parallel implementation of the linear-attention layers, after a gradient check against data parallelism (§5).0 tokens dropped. A 262K chain takes 38–62 s forward + backward. Re-training one step: 666–688 s, vs 875 s on the default context-parallel path (different recipes, so indicative).
Loop detection during generation.Chunked loop-stop.Slowed generation 20–35% to save about 3–4% of tokens, and once killed a near-perfect answer. Turned off; loops end at the budget and stay in the batch as failures.
Grading latency in single-call RL.Grade inside the generation loop.Exposed judge time ≈ 0: the last grade lands 3–5 s before generation ends.
More GPUs per job?Measured scaling.Trainer 4 → 8 GPUs: 1.88–1.90× (≈95% efficient). A small single-call job used 31% fewer GPU-hours on 4 GPUs than on 8, because generation dominated.
Redundant work with one update per step.Identified: the "old log-prob" forward recomputes what the rollout already has.85–125 s per step available. Not applied mid-run; it's queued for the restart.

Evaluation and grading

Grading is CPU work with time limits, which makes it fragile in a quiet way: overload doesn't crash, it lowers scores. The biggest fixes here were about trust, not speed.

Table 5 · Evaluation and grading: problem → fix → measured effect
ProblemFixMeasured effect
Oversubscribed judges silently lower scores on time-limited problems.Test-case slots ≤ 0.75 × cores; slots + judge workers ≤ cores − 2.16 slots on 16 cores lowered 7 of 144 scores; 12 lowered none. ~90 submissions/min per judge host with correct scores; 145/min released wrong ones. Real-feedback agentic training is projected to need 190–240/min, the next bottleneck.
"Timeouts" that were really queueing; one judge stalled at ~144 concurrent submissions.One eval per judge, ≤16 workers, a long wait, and stalls logged separately from failures.26 "timeouts" re-judged in 5 s total. 256 of 860 graded in 127 s on split judges.
After a sandbox crash, a judge graded known-correct code "Wrong Answer" with no error.Known-solution canary every few minutes; any deviation pauses training. Infra failures are masked, never scored 0.It had already zeroed one training step of an earlier attempt; the canary now pauses training first (§5).
Eval cost per checkpoint.Read the 40-problem subset off the full run. Pack 20 requests per eval job instead of 10.Full 172 × 5 ≈ 9 GPU-h; subset ≈ 2 GPU-h, now free. Packing is projected to halve GPU-hours at the same wall time.
How long should an agentic eval run?Set the cap from data.0 of 320 trials ran past 55 minutes at a 1 h cap, and a 3 h cap would triple the worst-case cost per checkpoint. That was for the general harness; the official-agent-mode eval now uses the benchmark's native 3 h.
27B baselines: a 32K per-call output cap.Raise it to the window.6% of responses hit the cap but held 51% of output tokens. Raising it, and lifting a 15-minute stream limit, took solo FrontierCS from 0.108 to 0.215.
Container start-up and isolation cost for agent trials.A lighter container-free sandbox, checked for parity.~9% faster per trial; paired Avg@4 difference −0.0014 (parity).
Wall-clock deadlines make reward depend on server load.Measured first: deadlines bound only 3.2% of solo training trials.A per-agent virtual clock was deferred to the team arm. Its exact message ordering costs +10% wall time.

05 · Silent failures

Silent failures: what corrupts RL without crashing it

None of these crashed a run. Each one either fed the optimizer a wrong signal or would have turned a real result into noise.

1 · Normalising by a tiny standard deviation amplifies noise

GRPO divides each group's reward differences by their standard deviation. With partial-credit rewards, a group of nearly identical scores, 0.549 / 0.549 / 0.550 / 0.553, becomes advantages of −0.74 / −0.74 / −0.22 / +1.69: a full-strength gradient on what is basically timing noise. Dropping the division (A = R − mean, which works for 0/1 rewards) went too far the other way: the gradient norm was 0.022, 10–20× too small for 0–1 partial credit. The fix is a floor: A = (R − mean) / max(std, 0.05). The agentic arm used it; the single-call arm, which learned, used plain normalisation. So this is about noisy partial-credit groups, not why the agentic arm stalled.

2 · Sequence-parallel gradients on a hybrid model

Qwen3.5 mixes full attention with Gated DeltaNet linear-attention layers. In my full training stack with Ulysses sequence parallelism, the forward pass matched plain data parallelism but the backward pass did not. On a 47K-token sequence, the gradient's cosine similarity with data-parallel training was 0.851 (0.787 in the linear-attention layers, 0.666 in the embeddings), against a run-to-run noise floor of 0.9999. A second check on a 67K-token rollout, through a different trainer code path, found the lm_head gradient off as well: cosine 0.682 at 1.39× the expected norm. A head-parallel implementation of the linear-attention layers brought the first check back to 0.99991. For the single-call arm I simply turned sequence parallelism off and offloaded optimizer state instead.

I haven't pinned down the cause. On a second machine with a different GPU architecture, I ran the linear-attention layers alone at 2 and 4 sequence-parallel ranks, on multi-chunk and packed sequences, with and without activation checkpointing. The parameter gradients matched a full-sequence reference to cosine 0.99998 or better, and a control that cut the cross-rank state moved the outputs at least 10× more than sequence parallelism did. So the problem is either in how the pieces are put together in the full stack or specific to the original hardware. Two lessons hold either way:

3 · Optimizer state that appears on step 2

Adam's moment buffers don't exist until the first optimizer.step(). A long-context run that passes step 1 comfortably can therefore run out of memory at step 2. The fix is to load offloaded optimizer state only around the optimizer step, and with several updates per step, re-offload after each. The general lesson is to smoke-test with at least two updates at full sequence length.

4 · Judges that fail silently

After a sandbox crash, one judge kept answering, but it graded known-correct solutions as "Wrong Answer": score 0, status done. Retries can't catch that, because nothing looks like an error. I added three fixes:

5 · One token over

A trial died three times with "maximum context length is 262144 tokens; you requested … for a total of at least 262145". In every case the request was exactly one token over. The harness estimated prompt size at about four characters per token, but one tool result was a 22K-character output, mostly digits, and this tokenizer emits one token per digit. The 13K-token underestimate exceeded the harness's safety margin. The server then clamped the output budget to the space left but counted one more prompt token in a later check, so the request failed by exactly one token, and failed again the same way on every resume.

6 · Red-team the grader before the policy does

Every grader I built or adopted got an adversarial audit before it was allowed to produce reward. Every audit found something. No policy found these first, but an RL policy optimising against the grader eventually would.

Table 6 · Grader exploits found by red-teaming
GraderExploitPayoff
Judge bundled with the training setPrint Ratio: 1 to stderr, then crashfull marks; the upstream judge scores it 0
My first spec-to-repo graderRegister an atexit hook in the package's own __init__.py that prints a fake pytest summary line after the real onereward 1.0 with no implementation; the file looks completely legitimate
Same graderAdd a test file of trivially passing tests where the test command collects a whole directoryinflated pass counts on three tasks
My first kernel graderWrite the result file from inside the submission's own process, which the privileged grader then trusted, including the path of "the output" to compare and the measured time"correct" plus any speedup without a working kernel
Sandbox network policyAllowlist filtered by host, not portunintended services reachable
Result aggregationVerifier errors were not distinguished from model zerosone launch scored 186 of 192 rollouts as false zeros before errors were separated from scores

The fixes are boring: score from a channel the submission can't write to; have the privileged grader pick every path itself; time code outside the submission's interpreter; add tamper checks (a reported pass count larger than the number of collected tests zeroes the trial); and grade final numbers by the benchmark's own published rules.


06 · Process

Running an RL campaign with a team of AI agents

The work itself was done collectively. A coordinator agent and a rotating set of builder, auditor and red-team agents, all running in Claude Code, wrote the harness, ran the experiments and reported back while I made the calls. That's a small preview of the subject, and it taught me a few rules I now apply to every run:


07 · Where this is going

The collective-intelligence idea, briefly

Plan, not a result

No team has been trained yet. This section explains the idea and the credit tool built for it; the only measurements here are of that tool.

The question is whether post-training can teach one shared policy to work as a team: when to fork a copy of itself, what to share and with whom, whose result to trust, and when the team is done. Three things set this apart from simply sampling more:

Figure 4 · The idea: independent sampling vs online collective computation
independent test-time compute task agent A agent B agent C select no information moves until every sample is finished online collective root fork discovery verifies it changes search submit a mid-run result can redirect compute that hasn't been spent yet
The difference is when information moves. With independent samples, what agent A finds is worth nothing to B and C until the end, when a selector chooses. In an online collective, a result can change what the rest of the team does with the compute it hasn't spent yet. The cost is that a wrong but persuasive result can spread to everyone.

Credit assignment is the hard part: one team reward says little about which fork or message helped. The tool below exists for that. So far it is a measured tool, not a learning result.

The credit tool: freezing a running team

A team-outcome reward can't say which fork or message helped. The tool I built for that freezes the whole team just before one decision, samples alternatives from the same state, runs each to the end, and credits the decision with the difference (a leave-one-out baseline, as in counterfactual credit-assignment work). With the 9B model on a spec-to-repo task, the same fork decision led to 2, 1 or 151 of 211 passing tests across three branches. The rest of the team was re-sampled in each branch, so the spread includes downstream noise; an earlier run of the same decision gave 48, 24 and 113. Even so, one decision showed a spread large enough to be worth crediting.

Figure 5 · Freeze, branch, credit: one fork decision on a spec-to-repo task
team running agents a1, a2 … barrier hold next calls, pause clock snapshot contexts · files · harness a₀ original fork resample 1 resample 2 R = 0.009 R = 0.005 R = 0.716 Â −0.35 Â −0.36 Â +0.71 Âⱼ = Rⱼ − mean of the other alternatives · applied only to the decision's own tokens every branch resends a byte-identical decision request; the other agents re-roll stochastically
One decision, three futures. On a spec-to-repo task (211 tests), the same fork decision led to 2, 1 or 151 passing tests, with the other agents re-sampled in every branch, so this includes downstream noise. The leave-one-out advantage lands on the decision's own tokens; everything else in the trajectory keeps the team-outcome advantage. An earlier run of the same decision gave 0.227 / 0.114 / 0.536.

Freezing a team means capturing more than text. It includes every agent's context and pending request, each working tree and the container filesystem, the message queues (undelivered messages too), the whiteboard and the timers. The costs that matter:

Getting the measurement right took an audit. The first version sampled the original decision from the request built before the freeze and the alternatives from the one rebuilt after restoring, which had one extra resume message. Now every alternative, including the original, restarts from the snapshot and resends a byte-identical request.


  1. Agentic solo arm, rebuilt. The minimal loop is red-teamed and its eval is running on the base model and the step-40 checkpoint. RL starts once those are in, with a go/no-go rule set in advance: by step 10, reward on revisited training tasks must be rising.
  2. Single-call restart from step 40, with an entropy-preserving change that acts even with one update per step (Clip-Cov is the candidate). A larger problem pool comes later.
  3. A short team-RL pilot, roughly 5–10% of the full budget, with paired solo-vs-team evals at every checkpoint and freeze-and-branch credit on fork, message and submit decisions.
  4. Several attempts per task on every headline comparison. Table 1 shows why one isn't enough.

The next post will say whether a team learns any of this.