Rounds that keep only gains
Give hi agent best-of a score, and it runs rounds that each start from the best result so far, scores every attempt itself, and commits only the ones that beat it. Limits, watching, stopping, the GPU, and overfitting.
With --score, best-of runs rounds, in the style of Karpathy’s
autoresearch. Each round starts
n boxes from the best result so far. hi scores every box itself, and commits
the round’s best only if it beats the best so far. The next round starts
from there.
hi agent best-of 4 --rounds 100 --gpu --edit train.py \
--score "make score" --lower program.md
Best-of b-7 · "program.md" · 4 boxes a round · round 9 of 100 · 2h41m · $31.20 at API prices
Score: make score (lower is better)
Best: 0.9871 from round 6, baseline 0.9979 · on best-of/b-7/best
ROUND RESULT SCORE BOX IDEA
6 kept 0.9871 ml-b7-r6-2 raise the learning rate to 0.04
7 discarded 0.9951 ml-b7-r7-4 switch to GeLU
8 unchanged – – no box changed anything
Now: round 9: scoring ml-b7-r9-2 (2 of 4)
The quickest way to a project that’s ready for this is a template.
The score
--score "<command>" is any command that prints a number. hi runs it in
each box after the agent ends and takes the last number it prints.
--lower or --higher says which way is better; one is required.
- A score that fails, times out, or prints no number makes that box a
crash: it can’t be kept. A round is
kept,discarded(its best didn’t beat the best so far),rejected(its best beat the score but lost on validation, with--validate),unchanged(no box changed anything), orcrash(no box had a usable result). --check "<command>"still works as a gate: a box that fails it isn’t scored. With--scorethere is no check unless you give one.- The score runs under a time limit of twice the baseline’s time plus a minute, so a hung run ends.
- The score should be the same every time for the same code. Measure its
noise first (run it a few times);
--min-gain 0.002makes hi ignore gains smaller than that.
The baseline
Before any agent starts, hi runs the check and the score on your last commit, in a box with the run’s options. A score that fails or prints no number stops the run there, before it costs tokens.
Measuring the baseline on 3f2a91c0e4b1.
Baseline: 0.9979, in 5m12s.
The baseline runs in a box, like every attempt, so a score that depends on
speed (the best loss in five minutes of training) compares fairly within a
run. make score on your own machine can differ: in a test, the same
training scored 2.56 on the host and 2.68 in a box, which gets less of the
CPU.
Only hi commits
The run has a branch for its gains, best-of/<run>/best, that only hi
writes. In their boxes, agents may commit, experiment, and run the score as
often as they like; that is scratch. When a round’s best beats the best so
far, hi adds one commit to the gains’ branch:
best-of b-7 round 6: raise the learning rate to 0.04
Score 0.9871, was 0.9932. From ml-b7-r6-2 (claude), checked and scored by hi.
The commit holds the agent’s files as it left them, snapshotted before the
check and score ran, so a run.log the score writes isn’t in it. Every
round’s boxes and scratch branches are removed when the round ends.
What agents see
Each round’s task is yours plus what hi adds: the round, the score command and its direction, the best score so far, the check, the files they may change, and a table of every earlier attempt with its score, result, and one-line idea. Agents build on what worked and skip what didn’t.
Earlier attempts, oldest first:
round agent score result idea
0 - 0.9979 baseline your last commit
1 claude 0.9932 kept raise the learning rate to 0.04
1 codex 1.0041 discarded switch to GeLU
Keeping agents to the experiment
--edit train.py (files, folders, or globs, comma-separated) is the only
part of the project a result may change. A result that changes anything
else, such as the evaluation or its data, is disqualified, whatever its
score. This enforces what autoresearch only asks of the agent.
The templates go further: their evaluation refuses code that reads files or imports modules outside a short list, so the editable file can’t read the data it is scored on.
Limits
| Option | Ends the run |
|---|---|
--rounds 50 |
after 50 rounds (default 1; forever has no end) |
--for 8h |
no round starts after 8 hours |
--budget 30 |
no round starts once agents have used $30 at API prices, as Claude Code reports it; on a subscription that is a measure of usage, not a bill, and Codex reports no dollars |
--patience 10 |
after 10 rounds without a gain |
--max 30m |
not the run: a time limit for each agent in each round |
--budget counts dollars agents report. Claude Code works out its own cost
at API prices from its built-in price list, whether you pay by subscription
or by API key. Hermes reports what OpenRouter charged. Codex reports only
tokens, so its runs count as nothing. After the first round, the view shows
what a round costs.
hi reads how each agent is paid for from its sign-in on your machine: a
Claude plan (such as Max 20x), a ChatGPT plan, an OpenAI API key, or
OpenRouter. The live view shows it for each box, and the header splits the
dollars: $4.10 at API prices ($0.62 of it billed) means $0.62 was charged
(OpenRouter or an API key) and the rest was subscription usage.
Watching and stopping
The rounds run in a process of their own, so they go on when you close the terminal or the SSH session.
hi agent best-of watch b-7 # the view; q, Esc, or Ctrl+C leave it
hi agent best-of stop b-7 # stop after the current round
hi agent best-of stop b-7 --now # stop now and remove the round's boxes
hi agent best-of resume b-7 --rounds 20 # 20 more rounds from the best so far
hi agent best-of show b-7 --json # everything, for scripts and agents
Leaving the view never stops the run; in the view, s stops it after the
current round. On resume, --rounds, --budget, and --for count from
where the run is. After a reboot, resume picks up a round whose boxes
still exist.
One GPU, one score at a time
A score with a time budget, such as “the best loss after five minutes of
training”, means nothing if boxes share the GPU while they train: each
gets part of it. With --gpu, or "gpu": true in the project’s
devcontainer.json, agents work at once, but hi gives the boxes the turn
to be scored one at a time, after every agent of the round is done. Without
the GPU, boxes are scored at once.
Overfitting
Rounds keep whatever scores best, so after 100 rounds the result is tuned to the data it was scored on. That is fine when the score is the goal (a benchmark on fixed inputs), and a trap when it stands in for something else (a model’s quality, a strategy’s future returns).
- Score on a validation set, not the training data. Agents train or fit on one part and are scored on another.
- Gate every gain on data agents never see.
--validate "make validate" --validate-data ../<folder>-validationscores each round’s winner on a validation window, in a container of its own, and keeps it only if it holds there too. See Keep only gains that hold. - Keep a holdout outside the repository. Boxes only see the repository,
so data kept in
../<folder>-holdout/can’t leak into an attempt. Check the result on it once, at the end. The templates set this up. - Prefer simple changes. Tell agents so in the task; a small gain from a lot of code is usually noise that fits.
When a run ends
--then "<command>" runs a command on your machine, in the project, when
the rounds end for any reason: the last round, a limit, stop, stop
--now, or a failure. HI_BEST_OF_RUN holds the run’s name, and the output
goes to the run’s log. Use it for a report, a notification, or anything
that should follow the run:
hi agent best-of 3 --rounds 40 --score "make score" --lower --edit train.py \
--then "make report" program.md
hi keeps every attempt’s files, kept or not, under
refs/best-of/<run>/, so a command run later can still read them:
git show refs/best-of/b-7/r3-2:train.py.
After a run
git log -p 3f2a91c..best-of/b-7/best # every kept change, oldest first
git checkout best-of/b-7/best && make holdout && git checkout -
git merge best-of/b-7/best
hi agent best-of rm b-7 removes leftover boxes, and the gains’ branch if
the run had no gain.