OculusMind-ToolCall-8B-v2 / APPENDIX-looping.md
OculusMind-AI's picture
Documentation update: Claude comparisons move to APPENDIX-claude-comparison.md; training-data, held-out and lineage notes corrected; Ollama download command updated for huggingface_hub 2.x; adapter_config.json without local paths
3a3d1b3 verified
|
Raw History Blame Contribute Delete
13.2 kB
# Appendix: the tool-call loop defect, and the seed sweep against it
*Every number here is measured and its artifact is kept.*
## What the defect is
A turn ends with the model emitting the same tool call over and over instead of
stopping β€” sometimes the identical call, sometimes the same tool with varied
arguments. The worst example we recorded ran **313 consecutive tool calls** in a
single turn. In production this is not a quality problem, it is an availability
one: the turn never terminates, and it consumes the budget of whatever called it.
## Scope of the claim β€” read this before quoting any number here
Everything below was measured on **one checkpoint**:
`mistralai/Ministral-3-8B-Instruct-2512-BF16` at revision
`f6fae9795746f63c9be8344932f01275f3c63734`, served as Q8_0 GGUF through
llama.cpp `c812c543f`, on **our** production-derived suite.
**This is not a claim about "Mistral models".** A family name predicts neither the
behaviour nor the licence of its next release, and this project has an expensive
worked example: `Ministral-8B-Instruct-2410` is research-licensed while
`Ministral-3-8B-Instruct-2512` is `apache-2.0` β€” same product name, opposite
answers. We have not measured any other Mistral checkpoint, so we say nothing
about them. What we can say is that **our pinned base exhibits the defect**, and
that fine-tuning moves the rate in both directions depending on nothing but the
seed.
## The base already loops
| population | loop rate | worst response |
|---|---:|---:|
| **full faithful set (1,133 tool-turns, 1,965 items)** | **πŸ”΄ 0.09 %** (1 turn) | **111 calls** |
| `select-vera` (146 tool-turns) | πŸ”΄ 0.68 % (1 turn) | 111 calls |
| `screen-vera` (212 tool-turns) | πŸ”΄ 0.47 % (1 turn) | 111 calls |
**The first row is the number to quote**, and all three rows are the same single
event seen through different denominators β€” one looping turn, 111 calls, a single
tool name repeated 108 times. On 146 tool-turns that one turn is 0.68 %; on 1,133
it is 0.09 %. Nothing about the base changed between those measurements, and
quoting the largest of them because it is the most convenient would be reporting a
sample size as a finding.
The scan completed 2026-10-02 over **1,965 of 1,965 collectable items** (Q8_0,
temperature 0, cap 4096), and its scorer output is kept at
`benchmarks/base-scans/faithful-all-q8-cap4096/scores.json`. The name-repeat
histogram is the useful shape: 1,059 turns repeat a tool name once, 52 call no
tool, a thin tail reaches 10 β€” and then one turn sits at **108**, with nothing in
between. **The defect is rare and catastrophic, not a gradient**, which is why a
threshold-based gate works at all and why an aggregate rate understates it: the
cost of the one turn is an unterminated production request, not 0.09 % of a
quality score.
So the defect is **not introduced by fine-tuning**. That matters for reading
everything below: a trained arm with a non-zero rate has not necessarily been
damaged, and an arm at zero has genuinely *removed* something the base does.
## How a loop is detected
Three independent readings, any one of which condemns a turn:
| rule | threshold | what it catches |
|---|---:|---|
| `maxIdenticalCallRepeat` | β‰₯ 8 | the identical `(name, arguments)` signature repeated |
| `toolCallCount` | β‰₯ 200 | a runaway trajectory of any shape |
| `maxToolNameRepeat` | β‰₯ 24 | the same tool NAME repeated, **arguments ignored** |
The third rule exists because a model that loops while varying one argument field
looks like progress to a signature-based check and is the same failure.
**The name threshold was calibrated against production traffic, not guessed.**
At 24 it is deliberately generous: a stricter setting of 12 condemns a real
production turn that called `web_search` 15 times with distinct `site:` queries β€”
legitimate work. A detector that cannot tell a loop from a thorough search is
worse than none, because it teaches you to distrust it.
## The sweep
Ten runs, seeds 42–51. **Identical** dataset (`l2-3f301662871f`), recipe (LoRA
rank 8, dropout 0.05, scale 1.0, attention projections only, lr 2e-5 cosine),
checkpoint (150), quantisation and scoring population. The seed is the only
difference.
A seed is not a quantity β€” it is the initial state of a pseudo-random generator,
and here it controls three things: LoRA `A` initialisation, every dropout mask,
and data order. We checked that consecutive seeds are not secretly similar:
across MLX's RNG and numpy's permutation, mean |r| between adjacent seeds (0.0037
and 0.0181) sits at or below the noise floor and is no higher than between
distant values like 1009 and 999983. **42–51 samples the same distribution that
scattered values would.**
## Results
`validFirstToolMatchPct` on `select-vera`, 146 tool-turns, cap 4096, temperature 0.
| seed | score | Ξ” vs base | loop rate | worst response | gate |
|---|---:|---:|---:|---:|---|
| base (untrained) | 80.82 ‑ | β€” | πŸ”΄ 0.68 % | 272 | β€” |
| **46** | **96.58** | **🟒 +15.76 pts (+19.5 %)** | **0.00 %** | 13 | **passes** |
| 48 | 96.58 | 🟒 +15.76 pts (+19.5 %) | πŸ”΄ 0.68 % | 116 | excluded |
| 43 | 95.21 | 🟒 +14.39 pts (+17.8 %) | πŸ”΄ 0.68 % | 292 | excluded |
| 42 | 92.92 † | not comparable | 0.00 % | 13 | passes |
| 45 | 93.15 | 🟒 +12.33 pts (+15.3 %) | 0.00 % | β€” | passes |
| 50 | 93.15 | 🟒 +12.33 pts (+15.3 %) | πŸ”΄ 1.37 % | β€” | excluded |
| 49 | 92.47 | 🟒 +11.65 pts (+14.4 %) | πŸ”΄ 5.48 % | β€” | excluded |
| 44 | 91.10 | 🟒 +10.28 pts (+12.7 %) | πŸ”΄ 6.85 % | 313 | excluded |
| 47 | 90.41 | 🟒 +9.59 pts (+11.9 %) | 0.00 % | β€” | passes |
| 51 | 94.52 | 🟒 +13.70 pts (+17.0 %) | 0.00 % | 15 | passes |
‑ The base was collected directly on these 266 items at the same cap and
temperature, three times with identical results; this is the figure the card
quotes. (Re-scoring the base's 404-item screen responses on this population gives
74.66 instead β€” a re-score of a different run, not a measurement of this one.)
† **Seed 42 is not measured on this population.** It was the original arm and was
scored on `screen-vera` β€” 404 items, 212 tool-turns β€” before the sweep standardised
on `select-vera`'s 266, so 92.92 is its screen-vera figure and is **not comparable
to the rest of this column**.
Seed 42's artifact-backed figures are: `report-vera-sealed` **95.45** (66 turns,
tying seed 46), production temperature 0.35 **95.21** (146 turns), and the
loop-provocation slice **100.0**. Those are the numbers to quote for it.
**Five of the ten seeds loop at least once.** The rate ranges from 0.00 %
to 6.85 % β€” ten of 146 tool-turns β€” from one recipe on one dataset. This is real
variance in what gets trained, not measurement noise: the loop VERDICT agreed
20/20 across two collections of the same artifact (see "Is it the sampler?").
## Is it the sampler, or the model?
The screen is **not** byte-deterministic. Re-collecting the 20 busiest items
against the same artifact gave 14/20 identical tool-call sequences and 19/20
identical text; one item ran 119 calls one time and 158 the next.
That is the right alarm to raise and it does **not** make the gate unsound,
because the gate is a **binary** per-item verdict and a 119-call and a 158-call
runaway are the same verdict. Measured per item with the real scorer, over the
same 20 items:
| fact | agreement |
|---|---|
| `looped` | **20/20** |
| `firstToolMatch` | 20/20 |
| `validFirstToolMatch` | 20/20 |
Those 20 were chosen as the **busiest** β€” adversarially, where instability
compounds β€” which makes the agreement stronger evidence than a random sample.
**So the difference between seed 42's zero and seed 44's 6.85 % is a property of
the models, not of the sampling.**
## The gate, and what it cost
Looping is a **gate**, not a ranking dimension. Any non-zero rate excludes the
seed outright; survivors are then ranked on score. There is no ranking *within*
non-zero, because 3 loops versus 4 is noise while zero versus non-zero is not.
For most of the sweep this was free β€” the best score happened also to be clean.
Then seed 48 scored **96.58, identical to seed 46**, 141 of 146 tool-turns for
both, and loops once. It is excluded and seed 46 ships. Two models
indistinguishable on the ranking metric, and the tie broken by the defect rather
than by noise in the third decimal.
## "You ran ten and shipped the best one" β€” yes, and here is why the headline survives
This table invites a fair objection: ten runs were made, the one that scored
highest on `select-vera` is the one being published, so the published selection
score is the maximum of ten draws and is biased upward by construction. That is
correct, and it is why **the headline number is not a `select-vera` number.**
The populations have different jobs, fixed before the sweep ran:
| population | role | may inform selection? |
|---|---|---|
| `screen-vera`, `select-vera` | screening and ranking the sweep | **yes β€” that is their purpose** |
| `report-vera-sealed` (138 items, 66 tool-turns) | the reported figure | **no β€” held back, scored once** |
Seed 46 was chosen on `select-vera` at **96.58**, and that figure is reported here
as what it is: the selection score, the maximum of ten, not a performance claim.
The published claim is **95.45 on `report-vera-sealed` against the base's 86.36**,
measured after selection was already settled, on items that informed no seed or
checkpoint choice (they were part of the earlier screen on which training recipes
were compared β€” README Β§2).
Two consequences worth stating plainly, because they cut against us:
- **The sealed figure is lower than the selection figure** (95.45 against 96.58),
which is the direction selection bias predicts. We report the lower one.
- **Seed 42 ties on the sealed population** (95.45, the same 63 of 66 tool-turns)
while scoring 2 points lower on the selection population. With 66 tool-turns one
turn is 1.5 points, so the sealed measurement cannot separate the top two β€” and
the card says so rather than presenting the selection ordering as a ranking.
**What a reader cannot check, and should know they cannot.** Nine of the ten seeds
are not published, and neither are the evaluation suites β€” they are built from
production captures. So every row in the table above except seed 46's rests on our
word. The scorer outputs for all of them are kept, and the exact code, data and
artifact behind each is recorded, but that record is internal. We state the rows
because omitting them while claiming "selected under a loop gate" would be the
weaker position, not because a reader can independently verify them.
## The runner-up, and why seed 46 was chosen anyway
Seed 42 ties this model on the reported population (95.45, 63 of 66) and on the
loop-provocation slice (100.0). It was separated on three other measurements:
| | seed 46 (this model) | seed 42 | Ξ” (46 βˆ’ 42) |
|---|---:|---:|---:|
| `report-vera-sealed`, temp 0 (66 turns) | 95.45 | 95.45 | Β±0 |
| **production temperature 0.35** (146 turns) | **97.26** | 95.21 | **🟒 +2.05 pts (+2.2 %)** |
| worst response on sealed | **6 calls** | 12 calls | 🟒 βˆ’6 calls |
| loop-provocation slice | 100.0 (worst 13) | 100.0 (worst 10) | Β±0 |
| `select-vera`, temp 0 (146 turns) | **96.58** | **never measured** | β€” |
Seed 42 has no `select-vera` temperature-0 run under the published recipe: it was
the original arm, scored on `screen-vera` β€” 404 items, 212 tool-turns, **92.92** β€”
before the sweep standardised on `select-vera`'s 266 items, so that row cannot be
filled with a comparable number and is left empty rather than approximated.
**The reported figure alone could not choose between them.** Saying so is the
point: with 66 tool-turns, one turn is 1.5 points, and presenting a tie as a
ranking would be presenting a coin-flip as a measurement. The separation that
survives is production temperature, where the two were measured on the same 146
tool-turns.
## What these numbers do not establish
- **"No loop observed on 266 items" is not "never loops."** A dedicated
provocation slice, built from turns that actually provoked loops, is the
stronger test and is reported separately.
- **A gate verdict belongs to the population it was measured on.** Seed 45 scored
0.00 % on `select-vera` and passed, then logged **1.52 % with a 124-call
response** on `report-vera-sealed`. "No loop observed on 266 items" is the
honest wording, and this is what it was protecting against.
- **At production temperature 0.35, none of the three shortlisted seeds looped**
(worst responses 12–19 calls), against 0.00–1.52 % at temperature 0 on the same
items. This is suggestive and **not a finding**: the base has not been run at
0.35, so whether sampling suppresses the defect or this population simply does
not provoke it either way is unmeasured.
- **Run-to-run variance of the aggregate score is unbounded** on this evidence β€”
20 items were re-collected, not 266 β€” so a seed ordering separated by a
fraction of a point is not trustworthy. It is sound for a coarse cut.
- **One checkpoint, one suite, one quantisation.** See "Scope of the claim".