Skip to content

Knobs, arms and the pin ledger

A knob is a thing you vary on purpose. An arm is one setting of it. The whole point of the generator is that two arms of one knob differ in the knob and in nothing else, and the pin ledger is what makes that a measurement rather than a hope.

poisson-tasks generate runs the pin ledger over the arms of your plan and says what it found; python.md shows the same check from Python.

Nine are named, and the generator makes tasks for all nine.

depth — how many chained stage calls the goal is away. Levels 1, 2, 3, 5, 8. A depth-d task needs d calls plus a reply turn, and the chain token makes them genuinely sequential: no model can skip to the end. Every depth level offers the whole eight-stage ladder, so below the top level a model can also carry the record past the goal; a calibration card reads depth on reaching the goal and shows overshoot beside it (calibration).

distractor — how many of the sixteen tools in the block are irrelevant. Levels 0, 2, 4, 8, 14. Every arm needs the same number of calls, so a difference in outcome across these arms is a difference in selection, not in reasoning depth. Experimental: on both calibrated models it does not make tasks harder, so it is not a difficulty setting for current models (calibration). A distractor tool is named for its ledger class and stage, so choosing the right tool is close to a string match with the class the prompt names.

The seven newer knobs each vary one thing about a small base task: two chained stage calls in a sixteen-tool block (shape, which makes four stage calls at every width, varies how they are arranged, and its base is its one-chain width).

knobvarieslevelsarm idscontrol arm
similarityhow many digits each lookalike ledger’s four-digit number shares, in place, with the target’s0, 1, 2, 3similarity:s3none
faultshow many calls on the way answer unavailable and must be made again0, 1, 2, 4faults:f2faults:f0+len(f4)
historyhow many tokens of earlier tool work sit in the conversation0, 300, 600, 1200, 2400history:t600none
blockhow many tools are offered8, 16, 32, 64block:k32block:k8+len(k64)
argumentshow many values each stage call must carry2, 4, 6, 8arguments:a6arguments:a2+len(a8)
shapehow many records four stage calls advance side by side1, 2, 4shape:w2shape:w1+len(w4)
spreadhow many tool answers the entry code is split across1, 2, 4spread:p4none

A level outside a knob’s candidates can still be an arm (history:t12800); a level the knob’s design has no task for cannot, and ArmSpec refuses it: a similarity level outside 0 to 3, a negative faults or history level, a block under 8 or over 258 (the two tools the task needs and every read name the table can make), an arguments level outside 2 to 8, a shape width other than 1, 2 or 4, a spread level outside 1 to 4.

levels(knob) returns the candidate levels. Which of them a study actually sweeps is a pre-registration decision, not a property of the generator.

The seven newer knobs were each measured once, on gpt-5-nano and on Qwen3-8B, at their lowest and highest levels and at the control arm where there is one: 60 tasks, two attempts each, strict pass@1, and the drop from the base arm to the top arm with a task-clustered 95% interval. A knob bites on a model when the interval excludes zero and the drop is at least 0.15; faults, block and arguments need the same drop from their control arm to the top, and shape counts either direction, as long as its length control differs from the base by less than 0.15. Cannot tell replaces “does not bite” where the base arm passed below 0.15 on that model, so a drop of 0.15 could not show; a base near 1 can still drop by 0.15, so it is read as it came out. It also replaces “top easier” where the top arm passed above 0.85. A knob ships when it bites on at least one model and is easier at the top on neither.

faults, history and shape shipped (arms.SHIPPED_KNOBS), and the recipes that measure them are offered. The other four did not ship, and are labelled Available · retested in v2: every knob stays in the generator and on the command line, and its arms work whether or not it shipped. These are two models, measured once, and the four are retested in v2, on current models. The table, with every contrast, is poisson_tasks.arms.knob_status(). In each cell Δ is the base arm’s strict pass@1 minus the top arm’s, with its interval; “control” is the control arm’s minus the top arm’s; for shape, “length control” is the base’s minus the length control’s.

knobshippedgpt-5-nanoQwen3-8B
similaritynodoes not bite: Δ −0.050 [−0.150, 0.042]does not bite: Δ 0.000 [0.000, 0.000]
faultsyesdoes not bite: Δ 0.400 [0.300, 0.500], control 0.142 [0.033, 0.250]bites: Δ 0.825 [0.742, 0.900], control 0.308 [0.150, 0.458]
historyyesbites: Δ 0.683 [0.608, 0.750]bites: Δ 0.308 [0.217, 0.417]
blocknodoes not bite: Δ −0.067 [−0.142, 0.000], control −0.008 [−0.058, 0.042]does not bite: Δ 0.000 [0.000, 0.000], control 0.000 [0.000, 0.000]
argumentsnodoes not bite: Δ 0.092 [0.000, 0.183], control 0.000 [−0.108, 0.100]does not bite: Δ 0.150 [0.067, 0.242], control 0.100 [0.017, 0.183]
shapeyesdoes not bite: Δ −0.300 [−0.425, −0.175], length control 0.158 [0.058, 0.258]bites: Δ 0.358 [0.233, 0.483], length control 0.000 [−0.033, 0.033]; shape:w4 harder
spreadnotop easier: Δ −0.250 [−0.358, −0.142]cannot tell: base passed 0.008; Δ 0.008 [0.000, 0.033]

Three things about how the probe ran are disclosed wherever its outcome is published. Each is short; the numbers are the records’ own.

1. Phase 1’s check changed after its first result (gpt-5-nano). As registered, phase 1 stopped the probe if any base-arm episode made no tool call, and gpt-5-nano’s first phase 1 stopped on 1 of 21 (the model asked for the record id in plain text; nothing on our side failed). After seeing that, on 2026-10-13, the check was changed to the published cards’ own rule: stop when more than 10% of the 21 base episodes make no tool call (3 or more). The recorded phase 1 was read again once under it, with no episode run again: 1 of 21, so it passed, and the probe went on in the same folder within the same $4.00 cap. Qwen3-8B’s phase 1 passed under the first rule.

2. A crash at the start, and one arm run again (gpt-5-nano). The run crashed at its start on macOS’s limit on open files, with its first arm, similarity:s0, in flight. The limit was raised and the run resumed. Episodes in flight at the crash failed on our side, so that arm was void (instrument rate 0.104; every other arm 0.000) and was run once more, as the pre-registration allows once for a cause on our side. The void attempt is kept. In all, gpt-5-nano cost 1.5981ofthe1.5981 of the 4.00 cap.

3. More time for Qwen3-8B. The Qwen3-8B run on Colab reached its two-hour allowance with 14 of its 18 arms done and stopped cleanly. It was given more time and resumed, the 14 finished arms kept and the last four run with the same settings; it used 2.56 hours in all.

ArmSpec(knob, level, control, strictness, split, generator_version)

arm_id(spec) renders it: depth:d3, distractor:r8@k16, depth:d1+h8, faults:f2.

Control arms. A control arm answers the obvious objection to each knob.

  • A depth arm is longer as well as deeper, so a drop at d8 could just be length. depth:d1+h8 is the control: a depth-1 task whose prompt is seeded with the history a depth-8 episode would have produced. It is as long as d8 and as deep as d1. If the model drops at d8 but not at d1+h8, the drop is depth.

  • A distractor arm has a different tool block, so a drop at r14 could just be a longer block. distractor:r0+len(r8)@k16 holds the block’s token count where r8 puts it while keeping r0’s tools.

  • Faults, block, arguments and shape each lengthen something as the level rises: the conversation, the block, or the request. Each has one control format, <arm>+len(<higher arm>), which keeps the lower level’s task and adds the higher level’s length. Similarity and spread hold length equal across their levels, and length is history’s own factor, so those three have no control arm and refuse one.

control holds the level being stood in for, so it must be higher than level; an arm spec that cannot be rendered cannot be built, and the constructor raises.

A sweep of a knob without its control arm is a pre-registration error, and validate_preregistration refuses the registration rather than letting the study run.

spec.reference_turns is the minimum: the calls the arm needs, plus the reply turn. Depth d needs d + 1; faults level f needs 3 + f, one more call per failure; shape needs 5 at every width and spread 7 at every level; every other arm needs 3. spec.max_turns(slack) adds the slack constant a study chooses.

A registration’s horizon is the largest max_turns over its arms, and it is checked rather than declared: validate_preregistration recomputes it and refuses a document that states a different number. The horizon is what Prove uses to cut an episode short, so a wrong one silently truncates the deepest arm.

Thirty rows: N1 to N13, then N19 to N35 for the seven newer knobs. Every row is held across the arms of one contrast, measured on the generated bundles. check_pins({arm_id: bundle, ...}) runs them and returns a PinReport.

rowholds across armswho
N1the scenario — every seed-drawn fieldevery knob
N2where the required tools sit in the blockevery knob
N3the system prompt; a distractor or block control may add a filler at its endevery knob
N4the tool block itselfdepth, faults, history, arguments, spread
N5the block’s token countdistractor — reported, not refused
N6the user prompt: its template and its slot token counts, leaving out the slot that is the knob (the goal stage for depth, the stated values for arguments, the goal lines for shape)every knob
N7the overlap between the prompt’s words and the tool namesevery knob — reported, not refused, for shape
N8the observation sizeevery knob; checked by the engine’s own tests
N9the seeded history, as a token band over the contrastdepth controls
N10the serving pin: model, revision, vLLM version, samplingat run time
N11verifier strictnessat run time
N12the engine, vocabulary and generator hashesat run time
N13realised first-request input tokens, per modelat analysis
N19twelve lookalike tools in the blocksimilarity
N20the block’s token countsimilarity
N21each of six lookalike ledgers’ numbers shares exactly the level’s digits, in place, with the target’s; none starts with 0similarity
N22the two right tools’ names, descriptions and parameterssimilarity
N23the seeded history’s tokens are the level, within twohistory
N24every seeded call is a read tool in the block, answered okfaults, history
N25the planned unavailable answers are the level, split over the right tools, the first taking the odd onefaults
N26the control’s seeded tokens match the higher arm’s failed calls, within twofaults control
N27the block holds exactly the level’s number of toolsblock
N28the right tools sit in the same slots, inside the first eightblock
N29the block’s token count — reported; the control’s filler matches the higher arm’s extra block, within two, or it refusesblock
N30the stage tools’ schemasarguments
N31each stage call carries the level less two extra values, the same on each, each stated in the request and carried by no tool namearguments
N32the control’s request is as long as the higher arm’s, within two tokensarguments control
N33four stage calls and sixteen tools at every width; the block’s token count is reportedshape
N34the control’s request is as long as the widest arm’s, within two tokensshape control
N35the same four reads at every level; as many answer with a part as the level, and the parts join, in order, to the entry codespread

N10 and N13 cannot be measured from a bundle — there is no server and no trace at generation time — so measure_pins puts each in unmeasured with the reason, rather than leaving it out. Every other row is measured on every bundle, whatever its knob, and every row is in exactly one of the two, which is what makes “a row nobody checks” impossible to write. Which rows a contrast is held to is the “who” column.

A violated row refuses the contrast before any model runs. A reported row does not: N5 only ever reports, N7 reports on a shape contrast, and N29 and N33 report the block’s growth beside whatever they refuse. The distinction matters and is why a report lists violations and reported apart rather than one boolean: a block’s token count should move with the distractor level or the block size, so it is a number to record, not a failure.

No record-id word is a substring of any tool name: both 256-word lists are checked against all 112 names. It was not always so. An earlier vocabulary carried six words that some tool name contains, and four of them — city, disc, grad, port — made row N7, which compares the prompt’s words with the tool names, refuse a decoy-tools contrast that held the r14 arm, the one level whose block keeps no read tool at all. The row is measured by substring over the user instruction’s tokens, the record id is in the instruction, and the id is an injective function of the task seed, so a seed whose first word was one of the four had no way out. Depth was unaffected at every level throughout. The current vocabulary draws the lists disjoint from every tool name, which closes it outright, and a test holds it.