A learning loop is not proven by the fact that it learns. It is proven by the direction. Mine carried a cross-tier transfer term with the sign inverted: a weak result for the expensive model would have pushed toward escalating to it, and a strong one toward staying cheap. It never routed a live task. A controlled sweep found the direction before it could. No code review would have.
Key facts. Transfer term weighted 0.5, sign inverted. Found by sweep, not by review.
Last updated:
01The verdict, before the switch
The learning loop in my AI workplace was never allowed to route a live task, and that turned out to be the luckiest scheduling decision of the whole build. Before arming it, I ran a controlled sweep of its inputs, and the sweep returned a verdict I am glad I collected while collecting it was free: the loop would escalate work to the expensive model exactly when that model was failing, and pull work away exactly when it was succeeding. The maths pointed backwards. The fix, once found, was one environment variable.
This is the full record: the sweep table verbatim, the mechanism that inverted the direction, one audit claim of mine the sweep refuted, and the loop's honest live status.
02The Gambler, and what arming means
The Gambler is the component that recommends which model gets each job. It keeps a running score per model per task family, and it carries an escalation rule: when a confidence quantity called p25 drops below a bar, 0.60 in the default band, the work should move to the stronger, more expensive model. Poor scores are supposed to belong to the cheap tier, and escalation is supposed to be the remedy.
Arming means connecting that rule to live behaviour. Until then the Gambler only writes advisory rows, a record of what it would have done. The loop had been unarmed its whole life, and arming it was the next item on the plan. Before connecting the wire, I wanted to see the direction of the current.
03The sweep
A controlled sweep holds every input fixed except one, walks that one across its range, and records the decision at each step. Here the cheap model, sonnet, was held at E[theta]=0.818. The expensive model, opus, was swept from 0.005 to 0.995. Five rows, quoted verbatim from the record:
opus E[θ]=0.005 → decision=opus, p25=0.064. opus E[θ]=0.100 → decision=opus, p25=0.262. opus E[θ]=0.500 → decision=opus, p25=0.545. opus E[θ]=0.900 → decision=sonnet, p25=0.850. opus E[θ]=0.995 → decision=sonnet, p25=0.973.
Read the two ends. When opus succeeds one time in two hundred, the decision is opus: give the failing model the work. When it succeeds 199 times in 200, the decision is sonnet: take the work away from the model that earns it. The flip sits between 0.500 and 0.900, pointing exactly the wrong way.
04How can a learning loop learn in the wrong direction?
p25, the quantity compared against the bar, was meant to measure the cheap model's arm. A cross-tier TRANSFER term weighted at 0.5 let the expensive model's record bleed into it. Opus failures dragged p25 down; a low p25 reads as escalate; escalation hands the work to opus. The loop stood ready to reward failure with promotion.
The live consequence was already visible before any sweep. The only escalation-eligible arm in the whole system sat at p25 0.541, under the 0.60 bar, and it returned to 0.690 the moment TRANSFER was set to 0. It was eligible purely because opus had failed at it.
05The audit the sweep refuted
An earlier audit of this loop carried a headline claim: the decision never compares the arms at all, so the loop is dead rather than dangerous. The sweep refutes that as stated. Walk one input and the decisions move; the loop responds. The defect is not deadness, it is direction, and the distinction matters. A dead loop wastes nothing but the effort of building it. A live loop with an inverted sign converts every observation into the opposite lesson, at full confidence.
I find this the healthiest part of the story. The same instrument that convicted the loop also corrected my own audit of it. Re-reads argue. Sweeps measure.
06Live status, plainly
The rejection path of this loop fired exactly once. On 2026-07-29 it wrote four negative marks; it was then measured at precision 0.000 and gated off 74 minutes and 10 seconds later. The one time that path learned anything, it learned the wrong thing.
The decision side never fired at all. The routing hook computes its pick, validates it, logs it, and never assigns it to anything; every decision row on record is advisory. And the fix for the inversion, SGNK_BANDIT_TRANSFER=0, is applied and verified in the live settings file. One variable. The loop is instrumented, not closed, and I will not use the word learning for it until the direction has been proven under load.
TRANSFER=0.5 opus a=40 b=1(opus works) -> stay sonnetTRANSFER=0.5 opus a=1 b=40(opus fails) -> escalate to opusTRANSFER=0 both -> opuslive: SGNK_BANDIT_TRANSFER = 0
07The lesson
You cannot see the sign of a learning loop from its code. The code reads as reasonable; the transfer term looks like a sensible way to share evidence across tiers; every reviewer nods. The direction only shows up when you hold everything fixed, walk one input, and watch which way the decisions move. That test costs minutes and it must run before arming, because afterwards the same defect costs money and teaches your system the opposite of every lesson it observes.
A backwards loop is worse than no loop. No loop leaves things as they are. A backwards one looks busy, healthy, and diligent the entire time. Because it is learning. Just not your lesson.
08How every number here was derived
Both source files are append-only, so re-running these will give larger denominators than the ones quoted.
# the five sweep rows, the fixed sonnet 0.818, the 0.5 TRANSFER term, the refuted-audit note, # and the live arm (p25 0.541 < bar 0.60, returns to 0.690 at TRANSFER=0) sed -n '258,275p' ~/.claude/HANDOFF-aios-2026-08-02-season1-close-and-rl-audit.md # advisory decision rows; re-count before quoting, the journal appends, _pick computed but never assigned, the single reward firing, # precision 0.000, gated off 74m10s later sed -n '133,143p' ~/.claude/HANDOFF-aios-2026-08-01-MASTER.md # the fix, live grep -n 'SGNK_BANDIT_TRANSFER' ~/.claude/settings.json # -> 363: "SGNK_BANDIT_TRANSFER": "0"