# The Hive Cascade: detect → repair → escalate
**A gold-free verification-and-repair loop for local language models.**
A model that is wrong is rarely *uncertain* - it is confidently wrong, and the
confidence is indistinguishable from the confidence of a right answer. Every
LLM application inherits this problem: a wrong answer costs the same as a
right one, unless something checks it.
The Hive Cascade is a serving loop that checks every answer before it is
served, repairs what can be repaired, and escalates what cannot - using
**verifiers that never see the answer key**.
This is the paper: the method, the protocol, the measured results, and the
negative results I kept. The code is in
[the repository](https://github.com/sky-is-green/hive); the task sets and
the run reports are in the
[hive-cascade dataset](https://huggingface.co/datasets/SkyIsNotGreen/hive-cascade);
the visual companion is
[the Space](https://huggingface.co/spaces/SkyIsNotGreen/hive-cascade-paper).
The measurements are current to October 2026; the execution checker, the
API tier, the scripted controller and the API-in-the-27B-slot path are measured
(checker: 6/7 errors, 4/4 at `p >= 0.97`, 0/21 false rejects, 0.23 ms median;
API: 28/28, 2.6 s median; scripted controller: 28/28, 7 escalations, 0/21
correct sent up, 9.24 s mean / 17.22 s conditional; API in the 27B slot:
28/28, 7 API escalations, 8.57 s / 14.54 s). The API-driven controller is
unmeasured; grounding is not pursued. The live status is in the [STATUS document](../docs/STATUS.md).
**Contents**
- [The claim in one page](#the-claim-in-one-page)
- [1. The problem](#1-the-problem)
- [2. The method](#2-the-method)
- [The gold-free runtime contract](#the-gold-free-runtime-contract)
- [3. The taxonomy: what a hive is made of](#3-the-taxonomy-what-a-hive-is-made-of)
- [4. The measured results](#4-the-measured-results)
- [4.1 The tiered refresh](#41-the-tiered-refresh-the-two-scions-measured-side-by-side)
- [4.2 The escalation tier](#42-the-escalation-tier-the-176b-buys-nothing-the-27b-buys-five)
- [5. The learned gate: a negative result I kept](#5-the-learned-gate-a-negative-result-i-kept)
- [6. The escalation lane: the harvest](#6-the-escalation-lane-the-harvest)
- [7. The reporting](#7-the-reporting)
- [8. The router is the product](#8-the-router-is-the-product-the-cascade-is-symmetric)
- [9. What is not claimed](#9-what-is-not-claimed)
- [10. Reproduce](#10-reproduce)
- [11. License](#11-license)
## The claim in one page
The harness, not the model, is the lever on this class.
| the measurement | what it compares | the result |
|---|---|---|
| the 35B, three draws (84 items per arm) | no verifier vs the verifier and the repair leg | 64/84 (76.2%) → **67/84 (79.8%)** |
| the 35B, one draw (28 tasks) | the model vs the loop, same draw | 23/28 (82.1%) → **24/28 (85.7%)** |
| the escalation candidate (28 tasks) | the 35B vs the 27B alone | 21/28 (75.0%) → **26/28 (92.9%)**, +5 net |
| the generation arm (28 tasks) | the 35B vs the 176B answering again | 21/28 (75.0%) → 21/28 (75.0%), net 0 |
The 23/28 and the 21/28 are different draws; the three-draw arms are the
robust comparison. The 27B's +5 is 6 errors fixed and 1 correct answer
broken. The generation arm's net 0 is 2 fixed and 2 broken on the 35B's
answers; the 176B alone was 19/28 in the head-to-head.
Three results carry the paper:
1. **Verification needs repair.** A verifier alone is a tax: 63/84 against
64/84 without one. The repair leg makes verification pay: 67/84, +3.6
points over no verification, with 8 of the 9 catches cleared. It catches
8 wrong answers for every 1 correct answer it drops.
2. **The tier is not the model.** The 176B's generation is net zero at
20.68 s an item. A 27B dense model fixes 6 of the 7 errors for +5 net at
8.92 s. The 176B's head is a cheap judge (2.52 s a verdict, 0 of 21
correct answers sent up), and the 27B is the escalation generator.
3. **Checkability comes before competence.** The head gives four of the
seven errors `p >= 0.97`; they are arithmetic and code, which a sandbox
can settle and a reader cannot. The execution checker is now measured: it
catches 6 of 7 errors, all 4 at `p >= 0.97`, with 0 of 21 false rejects,
at 0.23 ms a verdict.
What is not claimed: that this generalises beyond this class, or that the
API-driven controller has been measured. It has not; the execution checker,
the API tier, the scripted controller and the API slot have. Grounding is not
pursued.
---
## 1. The problem
Self-verification in language models is usually a model asking itself whether
its answer is right, and a judge asking the same. Both are unreliable in the
same direction: the failure modes of a generator and a judge trained on the
same distribution are correlated, so the cases that are hardest for the
generator are usually the cases the judge waves through.
Two facts make the problem sharper:
1. **The detector is not the generator.** A wrong answer that is internally
inconsistent (the arithmetic does not add up) or whose code does not pass
the task's own tests is detectable by *running something*, not by asking a
model. A 176B model's arithmetic slip is caught by a calculator.
2. **The check must not be the answer key.** If the verifier compares the
answer to the expected answer, the loop is a benchmark, not a system. It
cannot run in production, where there is no expected answer, and it
cannot measure anything, because the detector is then trivially perfect.
The cascade is built around those two facts.
## 2. The method
Per item, the loop is four stages:
```mermaid
flowchart TB
solve["solve
one sample at T"] --> detect{"detect
gold-free"}
detect -->|clean| accept["accept"]
detect -->|finding| repair["repair
T=0, finding quoted"]
repair --> recheck{"detect"}
recheck -->|clean| repaired["accept repaired"]
recheck -->|still flagged| escalate["escalate"]
escalate --> human["human gate"]
escalate --> api["API tier
only if named"]
escalate --> error["error / persistent flag"]
classDef ok fill:#dcfce7,stroke:#15803d,color:#14532d
classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
classDef bad fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
classDef neutral fill:#f3efe7,stroke:#a8a29e,color:#171614
class accept,repaired ok
class detect,recheck,escalate warn
class error bad
class solve,repair,human,api neutral
```
*Figure 1. The loop. Detect and repair are the local path; escalation is the exception, not the default.*
**solve.** One request to the local model at the run's temperature. A draft
that hits the token cap is **not an answer**: it retries once at double the
budget, and a still-truncated draft escalates rather than being scored as a
wrong answer.
**detect.** Gold-free findings, reported as *findings* (the false equation,
the traceback) rather than as a probability:
| detector | what it checks | needs |
|---|---|---|
| `consistency` | the answer contradicts itself (the arithmetic in the derivation does not add up) | nothing |
| `code_exec` | the code does not pass **the task's own tests** | a test block in the task |
That is the whole detector set. `numeric_gold` and `choice_made_gold` exist
for scoring, and the loop never calls them.
**repair.** One re-attempt with the finding quoted, at temperature 0 (the
strongest local re-attempt; a greedy re-ask is not a second sample, it is a
correction). Three rules keep the loop honest:
- a truncated attempt retries once at double the budget and is otherwise
**never accepted** as the answer;
- a byte-identical attempt **stops** the loop (the model is not going to
change its mind);
- an attempt that clears the detectors is re-framed to the task's interface
(`\boxed{…}`, `Final answer: n`, a letter, a fenced block) and becomes the
served answer - framing only, never content.
**escalate.** Still flagged, or nothing consumable extracted → the human gate
(verbose on the first trigger, compact afterwards) → solve locally again
(`keep` is a sub-option of solve) or escalate to the API tier. There is no
API tier until a provider is named: the escalation lane is a policy, not a
default.
Verdicts: `accepted`, `accepted_repaired`, `escalated`, `error`.
### The gold-free runtime contract
This is the part that makes the rest measurable.
Every task spec carries a `checker`. For a gold-dependent task that checker
has an `expect` field - **the answer key**. The loop is never allowed to see
it. Specs pass through one function:
```python
def runtime_spec(spec: dict) -> dict:
"""Everything the loop is allowed to know: the answer frame and the task's own tests."""
ck = spec.get("checker") or {}
rs = {"kind": ck.get("type")}
for key in ("tests", "prelude", "footer"):
if key in ck:
rs[key] = ck[key]
if any(word in key for key in rs for word in GOLD_WORDS):
raise AssertionError(f"gold-shaped key reached the loop: {sorted(rs)}")
return rs
```
The implementation is [`runtime_spec` in `experiments/cascade/cascade.py`](../experiments/cascade/cascade.py). `expect` is dropped here, and the function raises if a gold-shaped key ever
reaches the loop. So no stage - detect, repair, canonicalise, gate - can
read the answer key even by accident. Gold-based scoring is a **separate
post-loop step** ([`--score`](../experiments/cascade/run_cascade.py)), never
consulted by the loop.
```mermaid
flowchart LR
spec["task spec
+ answer key"] --> gate{"runtime_spec()"}
gate -->|tests| serving["loop
no key"]
gate -.->|expect| score["score
--score"]
serving --> answer["answer"]
answer --> score
classDef ok fill:#dcfce7,stroke:#15803d,color:#14532d
classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
classDef neutral fill:#f3efe7,stroke:#a8a29e,color:#171614
class serving,answer ok
class spec,score neutral
class gate warn
```
*Figure 2. The gold-free contract. The loop gets the frame and the tests; only the post-loop score sees `expect`.*
The consequence: the loop is the same code on a task whose answer key you
have and on one you do not, and the measured improvement is a property of
the serving loop, not of the benchmark.
## 3. The taxonomy: what a hive is made of
A serving system for local models is a set of *roles* and the *paths* between
them. The cascade ships the taxonomy explicitly (22 roles, A1-H1) in
[`harness/cascade/roles.py`](../harness/cascade/roles.py):
| group | roles | what they do |
|---|---|---|
| **A** - perception | A1 embedding, A2 vision | encode the input |
| **B** - knowledge | B1 retrieval, B2 reranking, B3 late interaction, B4 curation | find and prune what matters |
| **C** - control | C1 routing, C2 gating, C3 next-step, C4 scoring | decide what happens next |
| **D** - verification | D1 consistency, D2 accept/reject | check the answer |
| **E** - generation | E1-E4, by size | write the answer |
| **F/G/H** - escalation, latency, audit | the ladder, the budgets, the trail | keep the record |
and the seven **paths** (P0-P6) in
[`harness/cascade/paths.py`](../harness/cascade/paths.py) that compose them:
| path | trigger | shape |
|---|---|---|
| P0 | a near-duplicate prompt | cache lookup only, no generation |
| P1 | easy prompt, high route confidence | small generator, escalate to P3/P5 |
| P2 | normal question | route → gate → retrieve → rerank → generate → verify |
| P3 | low confidence, or the verifier rejects | P2's stages, then a larger generator |
| P4 | code or tool request | the tool loop, verification pipelined |
| P5 | hard reasoning | the largest local model, capped |
| P6 | image input | vision encode → generate → verify |
The point of the taxonomy is not the diagram. It is that **each role has a
measured latency band and a device class**, so a path can be costed before
it is run, and a slow role can be moved off the hot path (the router and the
consistency check are async in P2).
## 4. The measured results
### The 66-item organic set
The organic set: 66 items, sampled at T=0.7, live against the local 176B
(A6B ternary, MoE); the run bundle is in
[`experiments/cascade/results/cascade-e2e-sampled-20261018/`](../experiments/cascade/results/cascade-e2e-sampled-20261018/).
The bench: GSM8K-style arithmetic, HumanEval-style code, and a multiple-choice
set.
| | items | served-correct | false rejects (ledger) | note |
|---|---|---|---|---|
| no cascade (sampled) | 66 | 58 | - | the baseline |
| **+ detect + repair** | 66 | **62** | **0** | 6 of the 9 wrong answers detected; 4 of the 5 seen live repaired |
| no cascade (greedy) | 66 | 65 | - | the greedy regime is nearly clean |
| **+ detect + repair (greedy)** | 66 | 65 | 0 | 0 detections - the loop is a no-op |
**Reading the greedy row honestly.** At temperature 0 the bench is nearly
clean, so the loop has nothing to do. Sampling is the cascade's dial: the
cascade earns its keep exactly where the model's own distribution is
uncertain. A deployment that runs greedy gets nothing from this machinery -
that is a result, not a failure, and it is why the sampled regime is the
one reported first.
**The escalation ledger's false-reject count is zero.** The 66-item runs
record no false reject in the runner's ledger: the `false_reject` field only
exists once something escalates, and nothing did. That is not the verifier's
own rate - a rejected answer that was right - which the later three-draw
campaign measures at 1, 1 and 3 across its arms
([§5](#5-the-learned-gate-a-negative-result-i-kept)). The trade is still worth
making: the verifier catches 8 wrong answers for every 1 correct answer it
drops.
**Repairs do not harm correct answers.** The repair leg is only entered on a
rejection, and a cleared attempt is re-framed, never rewritten.
**Truncation is caught by the ladder.** The runner's own
[sampled](https://huggingface.co/datasets/SkyIsNotGreen/hive-cascade/blob/main/results/runner-cascade-sampled-20261018-report.json)
and [nothink](https://huggingface.co/datasets/SkyIsNotGreen/hive-cascade/blob/main/results/runner-cascade-sampled-nothink-20261018-report.json)
reports recorded 3 truncations caught and flagged, and 0 ledger false rejects
in both.
### 4.1 The tiered refresh: the two Scions, measured side by side
The 2026-10-18 numbers above come from the 176B alone. The question the
tiered design actually poses is *which tier does the work*, so both
[Scion models](../docs/AB-RESULTS.md#1a-the-two-scions-head-to-head) were served at
once - on one card, sharing it - and run over the same
[28 organic tasks](../experiments/cascade/tasks-organic-28.json).
#### The head-to-head
The two models, one draw each (T=0.7, 640 tokens, 62 minutes wall, 0 errors;
[`experiments/cascade/results/dual-scion-20261020/full28/`](../experiments/cascade/results/dual-scion-20261020/full28/)):
| | the 35B-A3B | the 176B-A6B |
|---|---|---|
| correct (the task's own checker) | **21/28** | 19/28 |
| the weight | 11.3 GB | ~132 GB |
| latency, median (under co-hosting) | 84.9 s | 93.9 s |
| latency, p90 | 189.5 s | 213.5 s |
They are **equivalent on this class within noise** - the split on the
disagreements is 8 to 6 - and the 176B is twelve times the weight. They
also **disagree on 16 of 28**, so neither dominates: the disagreement is
the raw material for an arbiter, and a router that knows *which* model to
trust per item is worth more than either model alone.
#### The cascade over the 35B
The same 28 tasks, a fresh draw, the deterministic judge and the repair leg
on (T=0.7):
| | |
|---|---|
| the model's own accuracy | 0.821 (23/28) |
| **the cascade's served-correct** | **0.857 (24/28)** |
| the repair leg | 1 attempted, **1 cleared** |
| **false rejects (ledger)** | **0** |
| false accepts | 4 (0.143) |
| the mean per-item latency | **6.9 s** |
The loop does its job on the small tier: one wrong answer caught and
repaired, nothing false-rejected in the ledger, and the served answer is
better than the model's own.
#### The multi-sample arms
The same 28 tasks and the 35B, three draws each - 84 items per arm, because
a single draw is a direction and not a measurement:
| arm | served-correct | the verifier rejected | of which false | the repair leg |
|---|---|---|---|---|
| no verifier, no repair | 64/84 (76.2%) | - | - | - |
| the deterministic verifier, no repair | 63/84 (75.0%) | 6 | 1 | - |
| the verifier + the repair leg | 67/84 (79.8%) | 9 | 1 | 8/9 cleared |
| + the learned gate | 70/84 (83.3%) | 12 | 3 | 8/11 |

*Figure 3. The A/B arms. The verifier alone is a tax; the repair leg pays. The learned gate improves the number but stays off by its pre-registered rule. The full table is [`AB-RESULTS.md` §3A-3E](../docs/AB-RESULTS.md#3a-3e-the-cascades-own-arms-measured-on-the-35b).*
**A verifier on its own is a tax: -1.2 points.** It catches the wrong
answers and, with no repair leg, they are simply not served. **The repair
leg is what makes verification pay: +4.8 points over the verifier alone,
+3.6 over no verification at all** - 8 of the 9 catches cleared. The gate
adds +3.5 more, at the cost of 3 false flags against its pre-registered
budget of 2 (so it stays off - see
[§5](#5-the-learned-gate-a-negative-result-i-kept)).
And the trade is worth making: the verifier catches **8 wrong answers for
every 1 correct answer it drops**.
#### The comparable pair
The same [10 organic tasks](../experiments/cascade/tasks-organic-10.json), the
same cascade, the same verifiers, the two models:
| | the 35B-A3B | the 176B-A6B |
|---|---|---|
| the model's own accuracy | 0.800 | 0.800 |
| **the cascade's served-correct** | **0.800** | **0.800** |
| the false rejects (ledger) | **0** | **0** |
| the accepted-and-wrong | 2 | 2 |
| the repairs | 0 | 0 |
| **the mean per-item latency** | **5.36 s** | **229.67 s** |
| the mean tokens | 329 | 745 |

*Figure 4. The same answer, 42.5× the wall clock. The 35B and the 176B serve the same 0.800 on the 10 tasks; the 176B spends 42.5× the seconds. The table is [`AB-RESULTS.md` §2B](../docs/AB-RESULTS.md#2b-the-membranes-operating-points-hypothesis-h).*
**Identical outcomes, 42.5x the wall clock.** The same served-correct
(0.800 both), the same ledger false rejects (0 both), the same wrong-accepted (2
both), the same repairs (none needed in either) - and the 176B spends
2.3x the tokens thinking and 42.5x the seconds getting there (5.4 s an
item against 229.7 s).
On this class the small tier is not a compromise: **it is the same
answer, faster and twelve times lighter.** What the large tier buys is
the 8 items of the head-to-head where it was right and the small one was
not - which is what an arbiter would be for, and not what a bigger model
is for. The tiering design says exactly this: the small tier takes the
work, and the large one is the exception, not the default.
**What this does and does not say.** The tier comparison is one draw per
task and 28 tasks; the multi-sample arms above are three draws each. One
class (arithmetic, code, multiple choice), and the latencies are measured
under co-hosting - so they are the numbers a tiered deployment sees, not
the isolated ones. The tiering claim that survives this measurement is
narrow and worth stating exactly: **on this class the small tier can take
the work outright**, and the value of the large tier is not accuracy but
the 8 items where it was right and the small one was not - which is
exactly what an arbiter would be for.
### 4.2 The escalation tier: the 176B buys nothing, the 27B buys five
The 229.7 s above is not a property of the model. The same engine's log
says the 176B decodes at **67.7 tok/s** over the 4,424 generations it has
served. It is bimodal, and the engine says which mode it is in: a fresh
prompt read costs 5 tok/s, a reused prefix 71 tok/s, and the cold phase
is the OS pulling the model's expert weights off the NVMe ("the OS read
96842 MB from storage"). Warm and alone, the same 28 tasks cost **26.9 s
an item** - so the 42.5x gap was about 7x contention and a cold cache and
about 6x the tier (7 × 6 ≈ 42).
That leaves the question the campaign never asked: **what is the
escalation tier worth?** The tier asked the 176B to *answer again*. Both
arms were then measured on the same 28 tasks, the same 35B answers, the
same 176B - the arms separately, because interleaving them makes each
inherit the other's expert cache:
| | the generate arm | the judge arm |
|---|---|---|
| what the 176B does | answers again | reads the 35B's answer |
| the cost for 28 items | 579.1 s | **129.8 s** |
| the mean per item | 20.68 s | **4.64 s** |
| the errors it fixes | 2 of 7 | 2 of 7 (found) |
| the correct answers it breaks | 2 of 21 | **0 of 21** |
| the net | **+0** | - |
**The generation arm is worth nothing on this class.** Scored with the
tasks' own checkers, the 176B's fresh answer fixes 2 of the 35B's 7
errors and breaks 2 of its 21 correct ones: net zero, at 20.68 s an item.
**The generator, not the tier.** The same generate arm on a 27B dense model
([Bonsai 2](https://huggingface.co/prism-ml/Ternary-Bonsai-2-27B-gguf),
ternary, 7.2 GB) fixes 6 of the 7 errors and breaks 1 of the 21 correct:
**net +5 at 8.92 s an item**, against the 176B's net zero at 20.68 s. The
176B is a judgement model; its generation is not better than the 35B's. The
tier was never worthless - the 176B was the wrong occupant. Gated by the
head at its best threshold, the same loop reaches 23/28 (3 escalations, all
errors, none correct) against the oracle gate's 27/28; the head's four
misses are plausible answers at `p >= 0.97`, which is why the next tier is a
*checker*, not a better judge
([`AB-RESULTS.md` §1B-sexies](../docs/AB-RESULTS.md#1b-sexies-the-escalation-generator-a-27b-dense-not-the-176b)).

*Figure 5. The tier results. The 27B is the generator; the head is the gate. The 176B alone is the head-to-head; its generation arm is net zero.*
**The judge arm is the same model, reading itself.** The 176B has a
decision head - the last prompt token's hidden state, read through the
engine's `emb=` as a *prefill* - and the head is in my own published
dataset
([`SkyIsNotGreen/decision-heads`](https://huggingface.co/datasets/SkyIsNotGreen/decision-heads)).
It was re-verified before use:
the AUC on its own frozen test split recomputes to 0.9941 (the published
0.9941), and the live engine agrees with the stored states to within
0.027. On the 35B's answers the head reads at AUC 0.864, and at p < 0.5
it flags 2 of the 7 errors while keeping 21 of the 21 correct answers.
So the tier's honest options are: **ask the 176B to answer** (20.68 s an
item, net zero), **ask it to judge** (4.64 s an item, 2 errors found and
none broken), or **ask the 35B and stop** (6.68 s an item, the 35B's own
accuracy). The judge arm is 4.5x cheaper than the generation arm and is
the only one of the three with a positive expected value on the errors.
**The caveat is the filter's, not the head's.** It found 2 of 7 errors,
not 7. The right shape is the two together - judge first, generate only
for what the judge sends up - which on this set is 2 generations out of
28. And the head's own dataset says the *in-domain* community head
should beat the frozen one on this body; on this task set it does not
(AUC 0.782 against 0.864, and it keeps five fewer correct answers at
0.90), so the frozen head is the one to ship here - the dataset's own
"validate per model" caveat, in action.
**What this changes.** The cascade's escalation tier was paying 20.68 s
(230 s co-sited) for an answer that was no better than the one it
replaced. It is now a 4.64 s judgement, and the runner carries the option
([`--verdict-url`](../experiments/cascade/run_cascade.py)).
## 5. The learned gate: a negative result I kept
The deterministic detectors have a measured blind spot: answers that are
*internally consistent and wrong*. A third trigger - a small learned head
over the draft - was built, fitted, wired and measured
([`harness/cascade/learned_gate.py`](../harness/cascade/learned_gate.py)).
It works: the intern-decision-0.8B readout on the CPU reproduces the stored
fitted probability exactly from the committed states/heads, and the live
extraction cross-check reproduced the flagship catch at p=0.073751.
And it **stays off by default**, by its own pre-registered rule:
| measurement | result |
|---|---|
| fresh silent-wrong catches | +1 |
| false flags | 3 (budget: ≤2) |
| wall clock | +8.7% |
| verdict | **3 false flags > the pre-registered ≤2 budget → the gate is off** |
Three independent measurements agreed that no 0.8B head separates on the
silent distribution. A gate that fires more often than it should is a tax on
every answer to catch one mistake; at this precision it is not worth it. The
implementation is tested and available as
[`--gate intern-0.8b`](../harness/cascade/learned_gate.py), and the default is
off. **A negative result, pre-registered, is a result.**
### The later measurement
The [A/B campaign](../docs/AB-RESULTS.md#3c-the-learned-gate-on-the-35b) (2026-10-20;
the 35B-A3B, the same 28 organic tasks, three draws each, 84 items per arm)
confirms it:
| arm | served-correct | the gate | the mean |
|---|---|---|---|
| the gate off | 67/84 (79.8%) | - | 6.6 s |
| **the gate on (intern-0.8B)** | **70/84 (83.3%)** | fired 7: 4 true catches, **3 false flags** | 8.1 s |
The gate is worth **+3.5 points** - and it false-flags 3 times against
the pre-registered budget of 2, so **by its own rule it stays off**. The
single-draw arm of the same configuration had looked like a clean win (0
false flags in 56 items); at 84 items the false flags are back over the
budget. A single draw would have shipped it.
What is constant across both measurements: the gate **catches real
silent wrongs** (4 here, 1 there) and its false flags are exactly what
the budget exists to bound. The mechanism works; the head does not
separate well enough on this distribution to pay for itself.
### The correction to the zero
The same campaign corrected the earlier claim in
[§4](#4-the-measured-results): the "0 false rejects"
there is the *escalation's* bookkeeping, not the verifier's - the runner's
`false_reject` field only exists once something escalates. The verifier's
own false rejects (a rejected answer that was right) are **1, 1 and 3**
across the arms, and it catches **8 wrong answers for every 1 correct
answer it drops**. The trade is still worth making; the zero was never the
verifier's number.
## 6. The escalation lane: the harvest
Every run writes `escalation.jsonl` next to its report: one line per item
the ladder resolved - the task, the full draft, the structured detector
findings, the repair attempt, and the ladder's resolution (human answer, API
answer, or a persistent flag).
The serving loop appends and forgets; **nothing in the runtime reads it**.
That is deliberate: the gold-free runtime contract stays intact while the
lane feeds offline training. It is the seed for three consumers:
1. the **gate discriminator** - escalate a task, sample fresh local drafts on
it, checker-label each against the escalated answer, and re-audit any head
on the silent distribution before pre-registering a restart;
2. **repair SFT** on `(prompt, draft, finding, fixed answer)` triples;
3. **local-tier distillation** of API and human answers.
The lane is what turns a serving loop into a training-data pipeline without
touching the runtime's contract.
## 7. The reporting
A verdict nobody can trace is a rumour. Every run therefore leaves three
run artifacts, plus a model card generated from the manifest. They are
published in this repository.
**`report.json`** - the per-item record. For every item: the verdict
(`accepted`, `accepted_repaired`, `escalated`, `error`), the detector
findings (the false equation, the traceback), the repair attempt and its
outcome, the generations it took, the timing, and the ladder's
resolution. The summary block carries the served-correct count, the
repaired set, the blind spot and the detector tallies.
**`manifest.json`** - the provenance. The model's sha256, the stack, the
git commit, the task-set sha and the decode settings, plus the per-task
and per-benchmark aggregates with both the sample-accuracy and the
task-macro. A run can be reproduced because the manifest says what to
reproduce.
**`escalation.jsonl`** - the harvest: one line per item the ladder
resolved, carrying the task, the full draft, the structured findings, the
repair attempt and the resolution. The serving loop appends and forgets;
nothing in the runtime reads it, so the gold-free contract is untouched
while the lane feeds offline training.
**`MODEL-CARD.md`** - generated from the manifest, for the model page.
The point of the separation is that the *runtime* evidence (what the
verifiers caught, what the repair fixed, what the ladder did) and the
*evaluation* evidence (whether the answers were right) never mix. The
first is produced by the serving path with no access to the answer key;
the second is produced afterwards, with the key, and is never consulted
by the loop.
## 8. The router is the product: the cascade is symmetric
The protocol above is written local-first - the local model answers, the
judge gates, the API is the last resort - because that is the order the
campaign measured. The order is a *deployment* choice, not the
architecture. The same tiers can be driven from the other end: an API
orchestrator can call the local models as tools, and the routing table
is identical. What the system owns is the table - which tier can settle
which kind of task, at what cost and with what measured error - not the
direction the requests flow.
### The measured table
The same [28 organic tasks](../experiments/cascade/tasks-organic-28.json)
throughout:
| the tier | what it settles | the measured number | the cost |
|---|---|---|---|
| the 35B | the bulk | 21/28 (0.750) | the cheapest local |
| the 27B ([Bonsai 2](https://huggingface.co/prism-ml/Ternary-Bonsai-2-27B-gguf)) | the 35B's repairable errors | 26/28 alone (0.929); +5 net | 8.9 s/item |
| the 176B [head](https://huggingface.co/datasets/SkyIsNotGreen/decision-heads) | the *plausible* errors | AUC 0.864; 3 of 7 errors, 0 of 21 correct | 2.5 s/verdict |
| the [checker](HARNESS-CONTROLLER.md) (execution) | the *executable* errors | **6 of 7 errors; 4 of 4 at p >= 0.97; 0 of 21 false rejects** | **0.23 ms median; 30 ms p95** |
| the API (DeepSeek V4.1 Flash Free, free OpenCode) | what nothing local settles | **28/28 (1.000)** on the 28 tasks | 2.6 s median; free tier (paid unmeasured) |
| the head only + live 27B/API | the whole task | 24/28; 3 escalations; **0/21** correct sent up | 10.21 s mean; 17.81 s conditional |
| **the scripted controller** (checker + head + 27B + API) | the whole task | **28/28 (1.000); 7 escalations (5 27B, 2 API); 0/21** correct sent up | **9.24 s mean; 17.22 s conditional** |
| the API in the 27B slot (DeepSeek V4.1 Flash Free) | the whole task | **28/28 (1.000); 7 API escalations; 0/21** correct sent up | **8.57 s mean; 14.54 s conditional** |
| the API in the 27B slot (GLM 5.3 Flash Free) | the whole task | 27/28 (0.964); 7 API escalations; **0/21** correct sent up | 7.80 s mean; 11.45 s conditional |
The GLM row is one sample of an unverifiable choice. On ten original-order
runs of `truthfulqa-0015`, DeepSeek chose A (ambiguous) 10/10; GLM chose A
4, B (no cost) 5, E 1. A swapped order moved GLM to the no-cost option 8/10;
a third order returned provider failures with zero tokens, not answers.
Architecture alone does not explain it: the Qwen-based 35B also chose B, while
the 27B and DeepSeek chose A. The split is choice calibration and order
sensitivity, not a code or arithmetic gap; the six executable API tasks are
correct and the 4/4 and 0/21 stand. The API-slot rows are one sample per
task; the five-run stability repeat is not claimed.
The table says routing is not one axis. **Checkability comes before
competence**: four of the seven errors the 176B's head gave `p >= 0.97`
were arithmetic and code answers - a better *reader* cannot see them, and
a sandbox can. The measured checker catches those four, and two more, at
0.23 ms a verdict: 6 of 7 errors, 0 of 21 correct answers failed. A router
that sends every task to the strongest model is buying tokens to solve
problems that a `python -c` would have settled.
```mermaid
flowchart TB
task["task"] --> small["35B
the bulk"]
small --> head{"176B head
is it plausible?"}
head -->|confident| serve["serve the 35B answer"]
head -->|uncertain| kind{"what kind of error?"}
kind -->|executable| sandbox["checker
run the code or arithmetic"]
kind -->|residue| api["API
the residue"]
sandbox -->|fails| repair["27B
repair with the finding"]
ground -->|unsupported| repair
sandbox -->|passes| serve
ground -->|supported| serve
repair --> serve
api --> serve
classDef ok fill:#dcfce7,stroke:#15803d,color:#14532d
classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
classDef neutral fill:#f3efe7,stroke:#a8a29e,color:#171614
classDef design fill:#f8fafc,stroke:#64748b,stroke-dasharray:4 3,color:#334155
class serve ok
class head,kind warn
class task,small,repair,sandbox,api neutral
```
*Figure 6. The routing map. The head, the execution checker, the API tier and the scripted loop are measured; the API-driven controller is a design, not a run. Grounding is not pursued.*
### The two drivers
- *The local driver* (the human in the Studio, or the local judge): start
at the 35B; send up when a check fails or the judge is uncertain; end
at the API when neither local tier settles it.
- *The API driver*: the frontier model plans and calls the local tiers
as tools (`ask_local`, `run_checker`, `get_verdict`); the locals carry
the bulk and the verification; the API pays for the plan and the
residue.
The API driver needs exactly two things the local driver does not: a
tool schema over the existing endpoints, and a controller that can call
the checker. Neither is a new model. The execution checker exists now;
the `--verdict-url` tier is already a judge as a service. The contract for the four tools, the invariants and the
bounded loop is in [`cascade/HARNESS-CONTROLLER.md`](HARNESS-CONTROLLER.md);
the survey of what other harnesses do is in
[`cascade/CONTROLLER-PRIOR-ART.md`](CONTROLLER-PRIOR-ART.md).
### What is not yet claimed
The execution checker, the API tier, the scripted controller and the
API-in-the-27B-slot path are measured on the 28 tasks (checker: 6/7, 4/4 at
`p >= 0.97`, 0/21, 0.23 ms; API: 28/28, 2.6 s; scripted loop: 28/28, 7
escalations, 0/21, 9.24 s mean / 17.22 s conditional; API slot: 28/28, 7 API,
0/21, 8.57 s / 14.54 s; the head-only path is 24/28, 3 escalations, 10.21 s /
17.81 s). The API-driven controller is not; grounding is not pursued. The
paid tier's key is expired, so the paid cost is unmeasured. The table's local
rows, the execution checker, the API tier, the scripted controller and the API
slot are measured; the API-driven controller is the programme.
## 9. What is not claimed
- **Not** that the cascade fixes most wrong answers. In the 176B's
66-item sampled run it caught 6 of the 9 wrong answers; in the greedy
regime it caught 0.
- **Not** that a learned gate is useless. It is off because of *this* head on
*this* distribution, at a *pre-registered* false-flag budget.
- **Not** that the measurements generalise. The first campaign is 66 organic
items on the 176B; the tier campaign is 28 organic tasks on the 35B, the
176B and the 27B, with one draw per task for the tier comparison and
three draws per arm in the A/B. The task class is arithmetic, code and multiple
choice. The priority plan is an evaluation floor first - a held-out split
plus a real benchmark slice (GSM8K / MATH / HumanEval / TruthfulQA, local)
at 3-5 samples per task, thresholds fitted on train and reported on test.
- **Not** that the taxonomy is complete. 22 roles is a map of what the
measurements needed so far.
## 10. Reproduce
```bash
git clone https://github.com/sky-is-green/hive
cd hive && python -m pip install -r requirements-dev.txt
python -m pip install -e splinter
# the deterministic parts, no model needed
pytest tests/unit tests/integration -q
# the live loop against a local model
.venv/bin/python experiments/cascade/run_cascade.py \
--tasks experiments/cascade/tasks-bench.json \
--judge deterministic --repair auto \
--api-fallback http://127.0.0.1:4000/v1 # optional; escalate, not needed to repair
# the console: every knob, one form
open http://127.0.0.1:8799/cascade
```
The runner is [`experiments/cascade/run_cascade.py`](../experiments/cascade/run_cascade.py);
the bench is [`experiments/cascade/tasks-bench.json`](../experiments/cascade/tasks-bench.json).
Runs land under
[`experiments/cascade/results//`](../experiments/cascade/results/):
`manifest.json`
(provenance - model sha256, stack, git commit, task-set sha, decode
settings - plus per-task and per-benchmark aggregates with sample-accuracy
and task-macro), `streams.json` (the full traces, regenerable), and a
generated `MODEL-CARD.md`. Manifests and cards are tracked in git; the large
stream traces are ignored.
## 11. License
MIT.
```
@misc{hive-cascade-2026,
title = {The Hive Cascade: detect, repair, escalate - a gold-free
verification-and-repair loop for local language models},
author = {sky-is-green},
year = {2026},
url = {https://github.com/sky-is-green/hive},
note = {Paper: https://github.com/sky-is-green/hive/blob/main/cascade/CASCADE-PAPER.md}
}
```
**Community invitation.** The protocol is written to be challenged: the
verdicts, the budgets and the pre-registration rules are all in the
repository, including the ones that failed. If your hardware produces a
different number, that is a result - open an
[issue](https://github.com/sky-is-green/hive/issues) with the run bundle.