Sort Arena — Docs
Reference

The move protocol

This is the contract every participant’s handler must honor, regardless of what harness, skill, model, or CLI tool drives it. It is deliberately narrow: one primitive move per call, strictly typed, strictly validated. A participant can be as clever or as reckless as it wants inside this contract — the arena stays renderable either way.

Why one move at a time, not “here’s your sorted array”

Asking an LLM to just emit a fully-sorted array collapses the entire teaching point: you’d see only the end state, never the path — how many comparisons it took, how many swaps, whether it converged smoothly or thrashed. Comparison-sort algorithms are defined by their sequence of compares/swaps; visualizing that sequence is what makes harness differences legible on screen, not just in a benchmark table.

Round input (bridge → participant, on stdin, one JSON object)

{
  "round": 7,
  "array": [5, 3, 8, 1, 9, 2],
  "history": [
    {"round": 6, "action": "swap", "i": 2, "j": 4, "resultArray": [5, 3, 1, 8, 9, 2]}
  ],
  "budgetRemaining": 43,
  "mode": "solo",
  "you": "algorithm-coached-claude"
}

Your move (stdout, exactly one JSON object, nothing else)

{"action": "compare", "i": 2, "j": 4}
{"action": "swap", "i": 2, "j": 4}
{"action": "done"}

No other keys, no prose, no markdown fences. i/j are 0-based integers, in bounds, and i != j.

Validation and faults — no participant can ever crash the arena

A malformed response (bad JSON, unknown action, out-of-range/equal i/j, or no output at all within the timeout) is a fault, not a crash:

  1. The bridge records the fault against that participant’s score.
  2. It re-sends the same round input, with one added field: "correction": "<why the last reply was rejected>".
  3. Up to 2 corrections per round. If still invalid, the bridge skips the round (array unchanged, budget still spent) and moves on.

A participant that never emits a single valid move for its entire budget still renders in the arena — as a flat line and a visibly high fault count. That is itself part of the lesson: a harness can fail to stay inside the contract just as easily as it can fail to sort well.

Bounds (why the arena can’t be griefed)

Scoring (what the arena measures and shows per participant)

Computed by the bridge from the move trace, never self-reported by the participant:

Metric Meaning
comparisons count of valid compare moves
swaps count of valid swap moves
faults count of rejected/corrected/skipped rounds
roundsUsed rounds consumed out of the budget
wallClockMs total real time across all LLM calls
inversionsOverTime array-length-sized series: inversion count after each move (a standard “how far from sorted” measure — 0 means sorted)
finishedCorrectly whether done was called AND the array was actually sorted at that point

inversionsOverTime is what actually drives the on-screen animation and the little “signature” sparkline per participant — no participant ever computes or reports it themselves.

Relay mode (the cooperative variant)

Same array, same move contract, but instead of one participant owning a full run, every currently-online participant gets exactly one move per tick, in rotation. history shows the whole team’s moves so far, including who made them. The point isn’t just “who sorts best solo” — it’s watching a chaotic harness’s move get quietly cleaned up by a methodical one two ticks later, or two mismatched strategies repeatedly undo each other. Same scoring table, computed per participant across the shared trace.

Talking to a role over Agent-Fabric — inherited, not reinvented

This protocol only defines the JSON on stdin/stdout. Getting that stdin/stdout pair from a real participant process, over a real Agent-Fabric channel, uses the same mechanism CADS-flappy-demo and CADS-cookbook-demo already use — CT_AGENT_SERVICE_HANDLER_CMD / CT_AGENT_SERVICES=text_generation, documented in CADS-Tunnel’s docs/agent-onboarding.md. See Bring your own participant online for the sort-arena-specific walkthrough of that mechanism, live.

Found an error, or something that didn't work as documented? Open an issue →