Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Autoswarm

Autoswarm is autoresearch with breadth. An iteration builds several candidate arms instead of one change, rejects the ones that cannot be trusted, has the survivors review each other, and keeps at most one.

/autoswarm opens the console. It takes no arguments: the goal, the breadth and everything else are fields on the surface it opens.

/autoswarm

The console

/autoswarm opens one of two surfaces, chosen by whether the current branch has a session.

The launcher

With no session on the branch, /autoswarm opens a centered card over the transcript: the setup form, a Start button and a Save-as row.

┌─ Autoswarm ──────────────────────────────────────────────────────────┐
│ ▸ Goal         what to optimize                                      │
│   Preset      swarm  wide                                            │
│   Breadth     ◂ 3 arms ▸                                             │
│   Models      session model for every arm                            │
│   Attempts    1 ▸                                                    │
│   Certify     ● on                                                   │
│   Iterations  auto ▸                                                 │
│   3 arms × 1 attempt: up to 3 harness runs per iteration. Each arm   │
│   is reviewed by another, and no pair reviews each other.            │
│   Every arm runs on the session model.                               │
│   No autoresearch.sh yet: the first turn writes and validates one    │
│   before anything is measured.                                       │
│   [ Start swarm ]  needs a goal                                      │
│   Save as     preset name                                            │
├──────────────────────────────────────────────────────────────────────┤
│ type the goal · enter starts the swarm   ↑↓ field   esc close        │
└──────────────────────────────────────────────────────────────────────┘

The shortest path is three keys long: open, type the goal, Enter. The card opens with the caret on the Goal row, and Enter on that row starts the swarm once it holds text. Enter on the Start swarm button does the same. The button states why it cannot start while it cannot: needs a goal or no model matches "<spec>".

FieldKeys
GoalType to edit; a click places the caret; ctrl+u clears the row. Enter starts the swarm.
Preset or space pick the next preset; a click picks the one under the pointer. The preset the fields equal is painted as in force; delete removes it when it is a saved one. A row of presets wider than the card scrolls to keep the one in force on it, with where presets are hidden.
Breadth , or a digit, between 2 and 8; a click on or steps.
ModelsType one spec per arm, comma separated; ctrl+u clears the row. Enter moves to Attempts.
Attempts , or a digit, between 1 and 5: retries before an arm is abandoned.
Certify, , space, Enter or a click toggles cross-review.
Iterations or typed digits, appending as typed; backspace drops the last digit; 0 is auto, which leaves the cap to the model.
Save asType a name; Enter saves the current shape as a preset.

, tab and shift+tab move between fields, the wheel walks them, and a click puts the ring on the field under the pointer. Escape closes the card. Closing starts nothing; a field edited on the card is parked for the start, so the next /autoswarm opens with it.

The notes under the fields state what one iteration costs and how the arms are reviewed: 3 arms × 1 attempt: up to 3 harness runs per iteration. is a ceiling, since an arm that succeeds on its first attempt uses one, and Each arm is reviewed by another, and no pair reviews each other. is the review topology for that breadth. Both change with the fields above them. A third note states whether autoresearch.sh was found: the first turn measures with it, or writes and validates one before anything is measured.

The dashboard

Over a session, /autoswarm opens the run dashboard: the ledger on the left, the highlighted row in full on the right, and the actions the swarm’s state allows on single keys along the footer.

┌─ Autoswarm · tokenizer-thro… ┬───────────────────────────────────────────────────┐
│   OVERVIEW ───────────────── │ Best        41ms · -18.0% · from 50ms · run 3 ·   │
│ › Session                    │             arm a2                                │
│   Playbook                   │ Trend       █▆▁                                   │
│   SEGMENT 1 ──────────────── │ Metric      duration · lower is better            │
│   #3 a2 41ms best            │                                                   │
│   #2 a1 47ms kept            │ Goal        make the tokenizer faster             │
│   #1 a0 50ms base            │ Session     tokenizer-throughput                  │
│                              │                                                   │
│                              │ Segment     1 · 3 runs, 3 kept                    │
│                              │ ↓ 6 more                                          │
├──────────────────────────────┴───────────────────────────────────────────────────┤
│ s resume   e setup   enter detail   n new session   x stop   esc close           │
└──────────────────────────────────────────────────────────────────────────────────┘

The footer sheds hints from its end when the card is narrow, esc close last, so the primary action, e setup and enter detail outlive the rest.

KeyAction
sStart, or Resume over a session.
pPause, offered while a turn is streaming.
nNew session: closes the session on the branch, keeps every file and every logged run, prints Closed <name> · N runs kept in the store. Starting a new session., and starts a fresh one with the setup as it stands. Blocked by the same conditions as Start.
xStop, while the mode is on.
cClear session.
rReset worktree, when the session has a baseline commit.
eThe setup form, over the same fields as the launcher, with the primary action as its button.
EnterThe highlighted row at the full width of the card.

An action runs after the dashboard closes, so a confirmation or a turn never opens under it. An action the situation blocks is refused on the footer with its reason. move through the ledger, pgup pgdn page the detail, the wheel scrolls the pane under the pointer and a click selects the row under it. Escape closes the dashboard, or returns to the ledger from the setup form and the detail view. Closing starts nothing and stops nothing; the swarm is exactly as it was.

A field edited on the setup form is written to the session as it is typed. Breadth changed on a live session applies from the next iteration. Enter on the Goal row resumes the swarm.

The dashboard opens on whatever the current branch is already doing, so opening it during a session shows that session’s setup rather than the default. A session started with /autoresearch <goal> is a serial loop and is driven by that command’s subcommands; opening the dashboard over it shows it at breadth 2, and resuming from there widens it from its next iteration.

A terminal too narrow for two panes stacks the ledger over the detail at the full width of the card.

Presets

A preset is the shape of a swarm without its goal: breadth, attempts, certification, per-arm models and the iteration cap. Two are built in:

PresetBreadthAttemptsCertify
swarm31on
wide52on

on the Preset row applies the next one, and a click applies the one under the pointer. The preset the fields currently equal is painted as in force. Save as takes a name and Enter saves the current shape under it; a saved preset is offered in every repository. A built-in name cannot be saved over. Saved presets are kept in presets.json beside the autoresearch databases. A saved breadth outside 2 to 8 loads at the nearer bound.

Everything autoresearch provides is unchanged underneath: the same autoresearch.sh harness, the same metric lines, the same segments, the same scope rules, the same database. Read that page first; this one covers only what breadth adds.

Breadth

Breadth is 2 to 8 and opens at 3, the fewest arms a review ring needs. The status row states N arms, and the run screen states it per session.

Arms share one worktree. They are built one at a time, measured, and reverted, so breadth costs iteration time rather than disk. An arm is a different idea: two arms that produce the same diff are counted once.

One change per iteration, with no arms and no review, is /autoresearch.

Models per arm

The Models row assigns one model to each arm, in arm order, comma separated:

  Models        opus, gpt-5, glm

a0 runs on Opus, a1 on GPT-5, a2 on GLM. The note under the fields reads the assignment back as arms, so an arm that is off by one comma is visible before the run starts:

a0 opus · a1 gpt-5 · a2 glm.

An entry left empty runs that arm on the session model, so , gpt-5 puts a1 on GPT-5 and leaves a0 where the session already is. An arm past the end of the list runs on the session model too, and clearing the row puts every arm there.

Each spec resolves the way --model resolves one: provider/id, a bare id, or a role alias such as @slow. A spec that matches nothing blocks the start: the note reads No model matches "<spec>". and the Start swarm button states no model matches "<spec>" until it is fixed. When an authenticated model’s id or name contains the spec, or a run of three or more letters of it, the note adds up to three as Close: anthropic/claude-opus-4, …, the likeliest first.

start_arm performs the switch. The loop calls it before the first edit of each arm, which is also what puts the arm on the status row (a1 on GPT-5) while it is being built. The session returns to its own model when the arm’s result is logged, so triage, certification and the choice of the next hypothesis run on the model you selected rather than on whichever arm ran last. Turning the mode off or clearing the session mid-arm restores it too.

A round configured this way compares models as much as ideas: each arm is a different model writing a different change, measured by the same harness and reviewed by the ring. Certification still applies, and no arm reviews itself.

Each run records the model in force when run_experiment measured it, and the run screen shows it under the arm as Built on. The arm is the attribution the loop declares; the model is what the session was on while the arm was built. Read one against the other to tell a model comparison from a round that stayed on one model. run_experiment states it in the result when an arm is measured with no arm in flight, or while a different arm is: both mean the diff was written by a model other than the one configured for that arm.

Why arms are reviewed

Breadth searches wider, but that is the smaller half. A loop scored on a number will find ways to move the number that have nothing to do with the work getting faster, and a single agent measuring its own change has no one to catch it.

Four rejections happen mechanically, before a reviewer sees anything:

RejectionWhat it catches
emptyAn arm that changed nothing.
scopeAn arm that edited an off-limits path.
opaqueA diff that cannot be read: a git binary patch, or a run of 512 or more base64 characters.
duplicateAn arm whose diff another arm already produced.

opaque closes a specific hole. A compiled artifact encoded as a base64 string and decoded at import time reads as an enormous speedup, passes an ASCII-only correctness gate, and cannot be reviewed by reading it. A diff nobody can read is rejected rather than measured.

What remains is assigned a reviewer:

SurvivorsReviewer
0none
1 or 2the director reviews each arm
3 or morea ring, where each arm reviews the next and no pair reviews each other

A ring needs three arms. Two arms reviewing each other is a reciprocal pair, which is the arrangement a ring exists to avoid. When breadth is 3 or more but fewer arms survive, review falls back to the director and the fallback is reported rather than applied silently.

A reviewer flags an arm when the metric moved for a reason other than the work getting faster: a hardcoded answer, a cache keyed on the benchmark’s own inputs, a narrowed input space, a weakened check, or work relocated out of the timed region. A flagged arm cannot win, however good its number is. When every improvement is flagged the iteration is a null round, which is a result and is logged as one.

Certification can be turned off for a session, which leaves the director as the only reviewer. It stays on by default.

Relocated cost

A change that moves work out of the timed region lowers the metric without making anything faster. Compiling at import time instead of at call time is the common shape.

Have the harness report what a fresh checkout pays, as a second metric:

python3 bench.py            # prints: METRIC ms=0.10
python3 cold_start.py       # prints: METRIC cold_ms=512.25

Growth above 25ms against the baseline’s own cold metric is stated to the reviewer as a measured fact. Without a cold_ms line nothing is checked, and a 0.10ms result that hides half a second of compilation is indistinguishable from a real one.

What certification does not do

It does not check that the code is still correct. That is the harness’s job, and a reviewer reads a diff rather than running the tests you did not write. The correctness section of the autoresearch page applies with more force here, because breadth produces more candidates and the wrong ones are the fast ones.

It also does not make a reviewer right. An arm is flagged by an agent reading a diff against a hypothesis. The mechanical rejections above hold whatever the reviewer concludes; the judgement on top of them does not.

Session state

Breadth, attempts, certification and the per-arm models belong to the session rather than the installation, so the console sets them per investigation and /settings does not carry them. A preset saves that shape under a name for every repository. A run records which arm produced it and which arm certified it, both stated on the dashboard’s detail of that run.

A winning arm has to beat the segment’s baseline, not merely the other arms of its iteration. An iteration where every arm regressed is a null round.

certify_arms and start_arm attach only while breadth is above 1. A serial /autoresearch session has one candidate, no ring and no arm to open, so there is nothing for either to do.

Regenerating the console captures

proof/scenes/autoswarm-setup.sh opens the launcher, moves through the fields, raises the breadth, assigns a model per arm and types one nothing matches, toggles certification off and back on, sets an iteration cap, switches to the wide preset, and leaves with Escape. It is a stills take that measures under 1 fps of real change, so both arms turn the motion gate off; at the default the recorder rejects the take as a stutter:

SCENE_MOTION_FLOOR=0 proof/record.sh proof/scenes/autoswarm-setup.sh
SCENE_MOTION_FLOOR=0 proof/record.sh --before proof/scenes/autoswarm-setup.sh

proof/scenes/autoswarm-run-resume-keeps-goal.sh opens the dashboard over the seeded session and leaves with Escape. The open frame shows the session’s goal in the detail pane with s resume on the footer; cancelled shows the loop untouched under the command:

SCENE_MOTION_FLOOR=0 proof/record.sh proof/scenes/autoswarm-run-resume-keeps-goal.sh
SCENE_MOTION_FLOOR=0 proof/record.sh --before proof/scenes/autoswarm-run-resume-keeps-goal.sh