The scripts are in the repository, not in the installed package. Clone it,
then
cd recipes/papers/harness-and-weights before running the commands below. Browse this recipe on GitHub.both arm trains the weights under the searched harness and is evaluated under it; the harness arm searches only the harness, the weights arm trains only the weights, and neither does neither. All four on the same held-out tasks.
What you will learn: how to put the two levers under one measurement. A
harness here is a wai.Harness with a skills text in its instructions
(Prime Agent’s idea: the harness carries a SKILLS.md the proposer edits
between rounds [3]), an optional run_python tool and a turn cap; the
search over harnesses is the Meta-Harness loop imported from
../meta-harness (ledger, proposal, gate [6]). The
weights are GRPO with TRL and vLLM colocate, the rollouts under the current
harness’s instructions, the reward wai.verify.CodeExec on hidden tests.
wai.harness.attribute over the 2x2 grid (base and trained weights, under
the baseline and the searched harness) says which lever moved the score.
You need nothing for --dry-run. --smoke and the full run need Modal
(MODAL_TOKEN_ID and MODAL_TOKEN_SECRET, or modal token set) and one
H100; WHILEAI_API_KEY is optional and posts the four arms to the platform.
Recipe
- Base:
Qwen/Qwen2.5-Coder-1.5B-Instruct(--base). The first smoke ran the plainQwen/Qwen2.5-1.5B-Instructand it passed 0, 4 and 2 of 128 rollouts under the three harnesses at temperature 1.0 (half the baseline’s replies timed out, the rest were NameError, TypeError and wrong numbers): a floor GRPO gets almost no signal from, so the coder variant of the same size is the default and that measurement is the reason. Data:tasks.pybuilds 143 quant coding tasks in 11 families over one seeded price table (eight tickers, 500 trading days,random.Random(0), standard library only, so the dry run, CI and the grader’s sandbox all build the same bars). Every task is one function over the table: max drawdown, rolling Sharpe, RSI, realized volatility, a momentum factor’s Spearman rank correlation with next-day returns, a top-k long-short return, golden crosses, beta, ATR, VWAP, the longest up streak, with the ticker and the window varied by template. Each carriesprivileged.tests, asserts against the reference implementation’s answer computed at build time; the answer is never in the prompt. The split is by family: 5 families train (64 tasks), 6 are held out (79), anddecontaminate(train, against=holdout)drops 0. Split by task instead (--split task) and the same check drops 71 of 71, because two tasks from one template are near-copies under the 8-gram rule [4]; that is why family is the default, and it is also the harder question, since the holdout asks for quantities training never showed. - Lever 1, the harness.
candidates/*.py, in name order, eachharness(model) -> wai.Harness:00_baselineis the bare instructions, no skills, no tool, one turn;01_skillsadds a SKILLS.md text (how to read the table, sample standard deviation, what to return);02_skills_tooladdsrun_python(the model’s code on the seeded table, the printed result back as the next message) and three turns. Every candidate plays every task--ktimes on the base weights;ledger.jsonl,proposal.mdand the gate (compare_runson the holdout, interval excluding zero) are the Meta-Harness recipe’s own functions [6]. The gate’s pick is the searched harness; when no candidate clears it, the best on the train split is used and the run says so. - Lever 2, the weights. GRPO with TRL
GRPOTrainerand LoRA, vLLM colocated for rollouts, one H100, the zero-rl-format-reward recipe’s image and its memory fix (half a group per forward, twice the accumulation,expandable_segments). 40 optimizer steps, 8 generations, 6 prompts a step, 1024 completion tokens, lr 1e-4, KL 1e-4,dapoloss. The system prompt is the harness’s instructions; the reward isCodeExecon the hidden tests.weightstrains under00_baseline;bothtrains under the searched harness. - Eval: pass@1 on the same held-out tasks,
--ksamples a task at temperature 1.0, each arm played under its harness (tool loop and all). The untrained base under the baseline harness is evaluated--eval-runstimes for the noise floor (eval_variance). Four cells, paired by task (compare_runs):harnessvsneither,weightsvsneither,bothvsharness,bothvsweights.wai.harness.attributeover the full grid. - One training seed per arm: the verdict on any trained pair is unresolved until each trained arm has a second seed (
--train-seed).
Run
PYTHONUTF8=1 PYTHONIOENCODING=utf-8 uv run --with modal --with datasets python recipe.py --smoke.
A candidate
One file, one change. This iscandidates/02_skills_tool.py: the skills
text from candidate 01 plus what the tool is for, the run_python tool, and
three turns (run, read, answer). build in harnesses.py turns it into the
harness a vLLM engine plays, or the scripted stand-in when the model name
starts with scripted.
Disclosure fields, so an edit to SKILLS is a new harness
version, and every row it produces carries it. A block with a print goes
to the tool (the table is bars there, and BARS); a block without one is
the answer. The proposer is you, or the
coding agent running skills/harness-search:
read out/proposal.md, edit the skills text into candidates/03_<name>.py,
run again.
What you get
python recipe.py --dry-run, offline, the three checked-in candidates on
the scripted stand-in, 16 tasks a split, 4 rollouts a task:
run_python calls.
python recipe.py --smoke, Modal, one H100, 2026-09-21: 2 GRPO steps a
trained arm, 16 tasks a split, 4 rollouts a task, the coder base. This is
the live path proven end to end, not a result; at two steps the trained
cells are the base cells:
Result
python recipe.py, the full run, 2026-09-21, one H100 on Modal, 79 held-out
tasks in six families the training never showed, 4 rollouts a task, 40
GRPO steps a trained arm. The output, verbatim:
Recipe vs baseline (
both vs weights): +0.060 [+0.028, +0.095] over
79 paired tasks. Verdict: unresolved. One training seed per arm; a second
seed on each trained arm (--train-seed) passed as train_runs= would
resolve it to moved or flat. The eval checks pass: the interval excludes
zero and the delta clears the eval’s own re-run band (run_std 0.0032 from 3
base re-runs, band 0.019 = t(df=2) 4.30 x sqrt(2) x run_std).
The claim under test was that both beats harness and weights. It
beats weights. It does not beat harness: +0.00 [-0.04, +0.04] on the
same 79 tasks. The trained weights under the skills harness land exactly
where the base weights under the skills harness land (0.085 both), and the
attribution says the same thing from the other side: the harness explains
95% of the spread across the four cells (interval 71 to 99), the model 2%
(0 to 24), and the model ranking flips (under the bare harness the trained
weights lead, under the skills harness they tie). On this base, this task
set and this budget, the skills text did what forty GRPO steps did, and
adding the steps to the text added nothing the interval can see. That is a
flat result on the papers’ claim and it is reported as one.
What the search found is its own result. The gate picked 01_skills, the
skills text with no tool: +0.08 [+0.05, +0.12] over the bare baseline on
the holdout, on the base weights, with no training at all. 02_skills_tool,
the same text plus run_python and three turns, did worse than the text
alone (0.05 against 0.09 on the holdout) while making 288 tool calls: the
1.5B model runs its code, reads a traceback or a number, and does not
repair it. Prime Agent’s REPL [3] pays off for a model that can act on what
it reads; this one cannot yet. The proposer’s next candidate should say
what to do with the output, not only that the tool exists.
The trained weights under the bare harness moved +0.02 [+0.01, +0.04] from a
base of 0.003, and their replies grew from 821 to 1,623 characters; under
the skills harness the trained replies are 1,130 characters against the
base’s 800. The training reward on the train split climbed from 0.04 to
above 0.5 by step 30, so the policy learned the five training families;
what carried to the six held-out families is the two points above.
Runs on the platform: https://while.ai/platform/runs?agent=harness-and-weights (four versions of the agent harness-and-weights, behavior quant_code, each with its harness fingerprint and the run record).
Checks
Two notes on the grid. The
trained level of the model axis is two
separately trained adapters, one under each harness (that is what
co-training means in HASE and SIA), so the attribution’s model axis reads
“trained under the harness it is evaluated under”, not one adapter moved
between harnesses. And the base model’s own two cells come from the same
search rows that picked the harness, so the harness cell is the gate’s
holdout evidence, not a fresh draw; the weights and both cells are fresh.
Climb
Round 2 is a second training seed on both trained arms, which is what
resolves the verdict. Round 3 is the proposer’s next candidate: a skills text
that says what to do with the tool’s output, since the REPL alone lost to
the text alone.
Learned
- The two arms the papers do not run are the ones that make the claim testable:
harnessalone andweightsalone, on the same holdout, paired by task. Without them “both beats scaffold iteration alone” has no interval. With them, on this setup,bothbeatweightsby six points andharnessby none. - A skills text is cheaper than forty GRPO steps and, here, worth the same: +0.08 from a text edit on the base weights, +0.02 from training under the bare harness, and training under the text added nothing to the text. The attribution report says the harness explains 95% of the spread. Search the harness first; train the weights when the harness has stopped moving.
- A REPL is only a lever for a model that acts on what it reads. The 1.5B coder made 288 tool calls and scored below the same skills text without the tool. Prime Agent’s gains [3] came with Opus 5; the tool is not the mechanism, the reading is.
- A templated task set and an 8-gram contamination rule do not mix: split by task, and every train prompt is a near-copy of a holdout prompt. Split by template family, and the holdout is clean and harder. The recipe measures both and says which it uses.
- A skills file in the harness is a prompt edit with a fingerprint:
wai.Harnessmakes the version without anyone naming it, so the proposer can edit SKILLS.md between rounds and every row still says which text it ran under.
Artifacts on Hugging Face
Part of the Papers, replicated collection in the while-ai org.
References
- Luo, H., Huang, Y., Luo, S., Liu, F., Li, L., Hu, Z., Feng, J., Liu, Q. Harness-Aware Self-Evolving: Co-Evolving Model Weights, Harness, and Task Solutions. arXiv:2607.03935, 2026.
- Hebbar, P., Manawat, Y., Verboomen, S., Ivanova, A., Palanimalai, S., Bhatia, K., Baskaran, V. SIA: Self Improving AI with Harness & Weight Updates. arXiv:2605.27276, 2026.
- Karten, S., Zhang, A. L., Thomas, K., Müller, S., Bakouch, E., Auras, D., Senghaas, M., Obeid, F., Dunas, K., Hagemann, J., Jaghouar, S. Prime Agent: A Self-Improving RLM Harness. arXiv:2608.23552, 2026.
- Lambert, N. Reinforcement Learning from Human Feedback. arXiv:2504.12501, 2025. Chapter Evaluation.
- Zhang, Wang, Ge, Xu, Hamm, Reddy. Stop Comparing LLM Agents Without Disclosing the Harness. arXiv:2605.23950, 2026.
- Lee, Y., Nair, R., Zhang, Q., Lee, K., Khattab, O., Finn, C. Meta-Harness: End-to-End Optimization of Model Harnesses. arXiv:2603.28052, 2026.
- Lambert, N. et al. Tülu 3: Pushing Frontiers in Open Language Model Post-Training. arXiv:2411.15124, 2024.
- Gao, L., Schulman, J., Hilton, J. Scaling Laws for Reward Model Overoptimization. ICML 2023. arXiv:2210.10760.