An AtCoder heuristic contest is competitive programming where nobody knows the right answer. AHC039 is one of those, “Purse Seine Fishing”: five thousand mackerel and five thousand sardines scattered across a square ocean, and you draw one net, a rectilinear polygon of at most 1,000 corners and perimeter at most 400,000. You score mackerel caught minus sardines.
I ran eight self-improvement agents at it. Each starts from a trivial solver, one rectangle on a coarse grid, then runs ten iterations capped at an hour each: it edits its own C++ program, is re-scored by the official tester on 30 fixed development cases, and commits. Its score is the mean over those 30, and every score below is one of those means unless I say otherwise. Nobody tells it what algorithm to write, and no run log cites an outside source. The starting solver scores 1,851, the best single rectangle a 64-by-64 grid search finds scores 1,871, and a net around the whole ocean scores 1, the floor: you catch every sardine too.
The two things I varied
The first is a document I call the run’s disposition: half a page on the judgement calls the task leaves open. Four of them, quoted from the files; the names are my labels, which the agent never sees:
- control takes “the smallest, most-verified step”, and treats “a solved problem” as “done, bank it and stop”.
- anti fits the solver “to the instances, sizes, and distributions you can actually measure”, because “generality you cannot measure is speculation”.
- nietzsche goes “straight at the hardest resistance the bench offers, the binding constraint, the worst case”.
- tzeentch never commits “to a single line of attack” and treats “good enough” as the failure state.
The second is whether the run may rewrite that document while it runs. In the frozen runs the disposition is fixed; in the radical runs the agent owns it and can throw it out mid-run. Underneath both sits a block of honesty invariants the loop re-injects verbatim every iteration and no run can edit: never claim something works until you ran it, never make a check pass by weakening it.
One run per cell, eight runs. All four self-modifying runs used the freedom: a values block of 15 to 23 lines became one of 149 to 247. Not one frozen block changed by a line. Every run beat the baseline, finishing between 3,100 and 3,787 against 1,851, a gain of 67% to 105%.
Self-modification helped the runs holding themselves back

Letting a run rewrite its own values is worth +474 to cautious control and
+186 to myopic anti. It costs resistance-seeking nietzsche 90 points
and transformation-seeking tzeentch 227. Not regression to the mean:
frozen nietzsche and anti sit 26 points apart and move opposite ways.
Self-modification is not a general upgrade but a corrective whose sign depends on the stance: if a run’s values contain something limiting, the pen lets it delete that; if not, it is just overhead.
What the rewrites actually said
The radical control run rewrote its own values in iteration one:
The seed disposition (“smallest step, bank it and stop […]”) is calibrated for engineering tasks where correctness is binary and verification is expensive. Here “smallest step” is not caution, it is leaving score on the table; and “bank it and stop” is wrong because there is no done, only a number that can go up.
The next iteration jumped from 3,048 to 3,486, 438 points in one step, and the run climbed +526 overall, the largest of the eight. For nietzsche and tzeentch, already told to keep going and try the bolder thing, there was nothing self-limiting to escape, and the iteration they spent rewriting never paid for itself.
anti is the disposition I wrote to overfit. Given the pen
here, it deleted its own defining instruction:
Generality is not speculation here, it is a named, scheduled evaluation […]. So “only what I can measure counts” is simply false for this task.
No overfitting here, for a boring reason
Every run also scored 1% to 3% higher on 150 fresh cases it never saw than
on its development set. No overfitting gap, and not a virtue of the method: that
held-out set comes from the same official generator, so it tests seed
robustness, not distribution shift, and a general algorithm lands where it
started. I wrote that prediction into the mission notes two days before the run.
It is also why anti does not collapse here and did on 2048: this split never
asks it for anything structurally new.
The record I am not quite claiming
ALE-Bench, which packages these contests for AI systems, reports Performance: an Elo-like per-problem rating, roughly 0 to 3,500+. Sum your score over the 150 official private cases (not the fresh 150 above), insert that total into the real contest standings for a rank, convert rank to rating.
When my top solver came out at Performance 3390, rank 1, I assumed a flattering measurement error and wrote down why: certified numbers use an AWS C6i and I ran on an Apple M1 Max.
Then I measured it. Five solvers from the public AHC039 standings, whose official totals are known, run on this laptop under the same compiler and budget, reproduce those totals to within 0.35%, mean ratio 1.0008. No measurable speed gap: the hardware explanation was wrong.
The real artifact was elsewhere. The official judge compiles with gcc 15.2.0,
and my top scorer, tzeentch frozen at 3,787, builds only under clang, so on
the real judge it does not compile, and scores zero.
With gcc-validity as a hard gate, seven of the eight runs clear it and the
leader is anti self-modifying, the run that talked itself out of its
instruction. It totals ≈570,981 over the 150 seeds, 0.70% ahead of the
best entry ever recorded, 566,997: rank 1 either way at Performance 3390. I
submitted it to the official judge after the contest and the score held.
Everything checkable locally was checked first: it compiles under gcc and, rebuilt under gcc rather than the clang it was scored with, reproduces 570.2k to 570.4k across two deterministic reruns, still 0.6% clear of the record. It solved all 150 cases in about 1.3 seconds of CPU each against a 1.9-second budget with nothing cut off by the clock, and an independent per-case check matched the pipeline’s score. On a 0.70% margin none of that would have settled it, which is why the judge run is the one that counts.
Two things still sit between this and beating the other AI systems. The evaluation surface is uneven: the agent tuned against 30 development cases, the rank comes from 150 private ones. And those systems were doing a different job. ShinkaEvolve’s AHC039 result warm-started from ALE-Agent’s rank-5 solver and refined it about 2.3% over 50 generations, staying “algorithmically close” to that initialization; ALE-Agent searches around 1,000 candidate programs. Mine starts from one rectangle: not the same experiment, so the numbers go side by side and never say “beat”. Nor is this an ALE-Bench score: that averages Performance over 40 problems, 10 in its Lite subset, and I ran one.
One run per cell, and a version skew
One run per cell. Eight runs, eight cells, no replicates: directional, not statistically powered. I say it with feeling, because yesterday I published a post about a one-run-per-cell result that a powered replication took apart, on this exact variable: on a 2048 task, freezing the values reproduced the whole capability gap, and self-modification added no significant benefit on top of the disposition. So the interaction above is a hypothesis with a mechanism and a legible transcript, not a finding.
A version skew. The nietzsche and tzeentch runs used values generation v7; anti and control exist only at v6. Each of the four differences above compares a run against its own twin at the same generation, so the pairs are internally matched. What the skew clouds is the comparison the headline rests on: a v6 pair next to a v7 one. Documented and unavoidable, and it makes the ordering of the four dispositions softer than it looks.
A self-limiting spec looks fine from outside
If it survives replication, the awkward part is that a self-limiting spec is the
hardest to notice from outside. The frozen control run looks excellent in its
logs: careful hypotheses, honest reverts, every number checked. Nothing in that
transcript says “this agent is leaving 474 points on the table”. The only thing
that said so was the copy of it allowed to disagree with its instructions, and
did.