ADR-020: L4 built — triage → classify → critic with a one-bounce backward edge¶
Status: Accepted — verdict recorded 2026-07-25: hypothesis confirmed, pipeline declined as configured Date: 2026-07-25 Deciders: San Lee
Related: spec (Accepted, all forks resolved — this ADR records the build against it) · ADR-006 (governance primitives, here made concrete) · ADR-013 (the constraint: no premium-tier escalation anywhere) · ADR-014 (the named error cluster the critic targets)
Context¶
L4 is the ladder's last level: multiple agents coordinating, with the honesty test being a
critic that can bounce a label backward for reclassification. The spec resolved the three
design forks (classify blind to triage; bounce cap 1; all-axes critic) and set the honest
hypothesis: the seven gold rows where region=global was pulled to a specific theater by
US-actor inference are rubric-checkable misses — critic-shaped by nature.
Decision¶
src/l4_pipeline.py implements the spec as written. The build decisions worth recording
beyond the spec:
- The critic's charter embeds the live region rubric via
extract_region_block(SYSTEM_PROMPT)— never retyped, so a prompt-side rubric change moves the critic automatically. A test pins the embedding. - The fail-closed gate is deterministic code, not prompt hope.
challenge_violationsdiscards any challenge lacking a named axis, a substantive rubric-rule citation, and a stated evidence gap; a discarded challenge never moves a label and never spends the bounce. Same pattern family as rung 1's region-rubric freeze and rung 2'svalidate_experiment. - The bounce cap is structural.
process_rowhas exactly one re-classify site; the second critic review can only producefixedorcontested, never another bounce. The five terminal statuses (accepted/fixed/contested/fail_closed, plus sentinel salvage inside classify) are each covered by an offline test. - Classify's token usage is not surfaced (its return contract is pinned by the contract tests), so the cost axis is calls-per-row, recorded per row in the predictions CSV — the number ADR-013 taught us to lead with anyway.
- The scale do-no-harm baseline reuses ADR-019's fresh same-prompt arm
(
evals/exemplar_scale_baseline.csv) — a fair anchor at zero re-spend. Region is not scored on scale (no answer key until v3.1.0), stated in the report. - Audit trail: per-run append-only JSONL under
evals/l4/(gitignored) recording every triage note, label, challenge (with cited rule + gap), bounce, and verdict — the append-only audit log from the governance primitives, and the replay viewer's third data source.
Not decided here: whether the pipeline pays. The verdict — named-cluster accounting, three-axis McNemar on gold, do-no-harm on scale, challenge rate and measured calls-per-row — is amended below after San drives the live runs. Expected challenge rate on gold is ~13% (7/54); a rate far above that is itself a red flag the report must surface.
Consequences¶
- The autonomy ladder is fully built, L1 through L4. What remains for the ladder story is L4's measured verdict and the portfolio cascade.
- ~750 workhorse calls of one-time spend when the runs happen (gold ~120 extra, scale ~630), zero premium-tier calls.
- The shipped classifier, its API, and the contract artifact are untouched — verified by an
identity test on
SYSTEM_PROMPTand by nothing in the pipeline importing mutation paths. - Risk stated: the critic reviews 100% of rows, so its false-challenge behavior is the main
way L4 could lose — every needless bounce is two extra calls and a chance to break a
correct label. The
fail_closedandcontestedcounts in the report exist to make that cost visible rather than averaged away.
Verdict (2026-07-25)¶
San ran both live passes the day the code merged (evals/l4_eval.txt + the two prediction
CSVs are the committed record). The result splits cleanly in two, and both halves matter:
The hypothesis was CONFIRMED. Of the 7 named cluster rows (gold global pulled to a
specific region by US-actor inference), the critic challenged and the bounce fixed 6
(g013, g017, g019, g026, g048, g054; g047 still missed). The one rubric-checkable error
class the pipeline was aimed at, it corrected almost completely. The backward edge works.
The pipeline as configured is DECLINED. The critic's charter did not hold in practice:
| Gold (n=54) | Scale (n=300) | |
|---|---|---|
| challenge rate | 57.4% (expected ~13%) | 51.0% |
| category | 92.6 → 90.7 (1 fixed / 2 broke) | 90.0 → 88.0 (20/26, p=0.46) |
| domain | 92.6 → 81.5 (1 fixed / 7 broke) | 91.3 → 86.7 (8/22, p=0.016) |
| region | 87.0 → 75.9 (6 fixed / 12 broke) | not scored (no answer key) |
| calls per row | 4.15× | 4.02× |
The spec's own red-flag rule fired: a challenge rate 4× the expectation means the
prompt-level narrowing ("rubric-checkable evidence claims only") leaked — the critic
challenged judgment calls it was chartered to leave alone, and every needless bounce was a
chance to break a correct label. Region tells the story in one line: the critic fixed the 6
cluster rows and broke 12 others, mostly by over-applying the no-guessing rule to rows whose
evidence it second-guessed. The scale domain regression clears significance (p=0.016) — the
first statistically significant harm any experiment in this repo has produced. Half the
gold rows ended contested (27/54): the critic frequently disputed even the re-classified
label, which is the trigger-happiness made visible exactly as the audit design intended.
Standing verdict: the L4 honesty test is passed — a critic that bounces labels backward, built and demonstrated, with the targeted error class measurably fixed — but the whole-pipeline configuration is a measured negative result: an all-axes critic whose restraint lives only in its prompt does net damage at 4× cost. The shipped classifier remains the production path, unchanged.
What this does and does not license next: fork 3 chose an all-axes critic precisely so
the do-no-harm claim would be non-vacuous — it was, and it measured harm. The obvious
follow-up (a structurally narrowed critic: challenge routing gated in code on triage's
none stated signal rather than on prompt discipline, region axis only) is a NEW
experiment with its own measurement if ever picked up — recorded here as an option, not a
rescue of this verdict. The ladder's build story is complete either way: L4's demo is the
backward edge catching the named cluster, and the writeup tells both halves.
Downstream surfaces¶
Touched by this change (all in this PR):
src/l4_pipeline.py,tests/test_l4_pipeline.py(new);.gitignore(audit logs);CHANGELOG.md[Unreleased];decisions/README.mdindex row;docs/specs/autonomy-ladder.mdL4 row flips to Built.
To sweep when the verdict lands, deliberately NOT in this PR:
- This ADR's Verdict +
evals/l4_eval.txtand the two prediction CSVs (committed as the record); the README ladder narrative; the portfolio cascade (project page L4 section, ladder visual, replay wiring for the audit log) — result, not promise; SYS-019 markers if numbers are quoted on guarded surfaces.