Partition mode — when every piece is sorted and the whole still isn’t
The first four tutorials were all about handler-level harness design: what a participant’s own code does with the array it’s handed. This one is about a mode-level harness decision instead — something The move protocol defines once, for everyone, that no participant’s own strategy can opt out of. It produces a result that looks like a bug the first time you see it and isn’t one: every participant finishing its own piece perfectly, and the arena still correctly reporting the whole thing unsorted.
What partition mode actually does
Race mode (used for every head-to-head table in the earlier tutorials) gives each participant its
own full, independent copy of the same starting array. Partition mode does something different:
it splits one array by position into contiguous, non-overlapping segments — one per
participant — and each one sorts only its own slice. From inside a handler, this is
indistinguishable from an ordinary solo run against a shorter array; the only visible difference
is two extra fields on every round event, segmentStart/segmentLength, that let a client map a
segment-local i/j back to the whole array’s real position.
A real run against the live arena, verbatim
The run below was recorded against three participants that were live at the time. Do not paste
it verbatim — the hosted roster changes, and two of these three are no longer on it. Measured
2026-08-27: this exact command now answers HTTP 404,
{"error":"unknown participant \"bubble-sort-claude\""}.
Read the roster first and substitute live ids:
curl -s https://sort.bunsenbrenner.org/participants # who is actually live right now
curl -s -N -X POST "https://sort.bunsenbrenner.org/partition?ids=<id-a>,<id-b>,<id-c>&len=18"
The transcript that follows is kept as a recorded run, because what it demonstrates does not depend on which participants produced it:
# recorded 2026-08-14; ids valid then, not now
curl -s -N -X POST "https://sort.bunsenbrenner.org/partition?ids=reference-sorter,bubble-sort-claude,algorithm-coached-claude&len=18"
An 18-element array splits three ways into 6/6/6. Real output, trimmed to the parts that matter
(every intermediate round streamed too — this is the start and final events only):
{"stage":"start","initialArray":[65,42,98,98,61,81,89,76,27,61,34,56,57,2,21,60,44,68],
"segments":[{"you":"reference-sorter","start":0,"length":6},
{"you":"bubble-sort-claude","start":6,"length":6},
{"you":"algorithm-coached-claude","start":12,"length":6}]}
{"stage":"final",
"finalArray":[42,61,65,81,98,98, 27,34,56,61,76,89, 2,21,44,57,60,68],
"wholeArraySorted":false,
"perParticipant":[
{"you":"reference-sorter", "finishedCorrectly":true,"roundsUsed":7, "comparisons":0,"swaps":6, "faults":0},
{"you":"bubble-sort-claude", "finishedCorrectly":true,"roundsUsed":14,"comparisons":2,"swaps":11,"faults":0},
{"you":"algorithm-coached-claude","finishedCorrectly":true,"roundsUsed":5, "comparisons":0,"swaps":4, "faults":0}]}
Look at finalArray split at the same 6/6/6 boundaries: [42,61,65,81,98,98],
[27,34,56,61,76,89], [2,21,44,57,60,68]. Every single segment is internally sorted — check
each one, they’re all non-decreasing. All three participants reported finishedCorrectly: true
with zero faults. And the array as a whole — 42, 61, 65, ..., 98, 27, 34, ... — very obviously
is not sorted: 98 is immediately followed by 27.
This is exactly the outcome The move protocol says to expect, not a bug this run happened to trigger: “splitting by position is not the same as splitting by value range, and concatenating locally-sorted slices only yields a globally sorted array when each slice already happened to hold the right value range.” What’s new here is seeing it actually happen against the live arena with real participants, rather than taking the docs’ word for it.
Why this is a harness lesson, not a participant bug
Nothing about this is any participant’s fault, and nothing in a smarter handler could have
prevented it. reference-sorter, bubble-sort-claude, and algorithm-coached-claude were each
handed a 6-element slice with no visibility into the other two segments’ values — from inside a
single handler invocation, sorting [57, 2, 21, 60, 44, 68] into [2, 21, 44, 57, 60, 68] is a
completely correct, completely finished job. The gap lives entirely in what the mode itself
promises: partition mode splits by array position, and a globally sorted result requires
splitting by value range instead (imagine handing segment 1 every value that belongs in
positions 0-5 of the final sorted array, segment 2 the next sixth, and so on) — which requires
information no single segment’s handler has, by the mode’s own design. Real parallel/external sort
algorithms solve exactly this with a merge phase after the segments finish; partition mode
deliberately doesn’t implement one, because the point of this mode is watching segments sort
concurrently on screen, not shipping a working parallel sort.
Compare this to every earlier tutorial’s failures: Non-adjacent
swaps’s infinite loop and Coaching a
strategy’s historical fault were both
things a better handler could fix. wholeArraySorted: false here is not that kind of gap — it’s
the harness telling you, correctly, what its own scoring actually measures per segment versus what
a human glancing at the picture might assume it measures overall. Knowing the difference before
you look at a partition-mode run is the actual point of this tutorial.
The per-participant numbers still tell the usual story
Equal segment lengths (6 each) make the three perParticipant rows directly comparable, and they
land exactly where the earlier tutorials predict: algorithm-coached-claude’s direct-placement
strategy finishes fastest (5 rounds, 0 comparisons — Coaching a
strategy’s at-most-n-1-swaps bound in
miniature), reference-sorter’s insertion sort is respectable (7 rounds), and
bubble-sort-claude’s adjacent-only sweep is the slowest of the three (14 rounds, and the only one
to spend any rounds on compare). Partition mode doesn’t change how a strategy’s own efficiency
gets measured — only what “the array” that efficiency is measured against actually means.
Try it yourself
Any set of currently-live participant ids works — check GET
/participants for the current roster, then:
curl -s -N -X POST "https://sort.bunsenbrenner.org/partition?ids=<id-a>,<id-b>,<id-c>&len=<N>"
Watch for the same shape: finishedCorrectly: true across the board, wholeArraySorted: false
anyway, unless the values happened to already fall into position-ordered ranges — which, for a
random array, they usually won’t.
No live arena needed, either: Run the arena locally brings up the same bridge on your own machine in about a minute — handy when the hosted deployment is down, or you just don’t want to depend on it.
Two things change locally, and this page used to claim otherwise. Only the ids you configured
in SORT_PARTICIPANTS_JSON exist, so the ids above return the same 404 there; and a participant
that ships in the repo without a generated handler fails its own selftest until you run
generate.sh for it:
$ ./participants/bubble-sort-claude/handler.sh --selftest
SELFTEST FAIL: .../generated/handler.py does not exist yet -- run generate.sh first
So locally: configure three ids of your own, and use those. The lesson is about the partition, not about which handlers fill it.