Skip to content

Commit f76cb6f

Browse files
committed
Measure what the gate decides, since the average cannot see it
1 parent bd6de02 commit f76cb6f

7 files changed

Lines changed: 1246 additions & 12 deletions

File tree

‎bench/confusion.ts‎

Lines changed: 573 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/EVAL-PLAN-2026-09.md‎

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,21 +129,69 @@ n=30 就够;DERAIL 报的是「跑到第一次违规用了多少轮」,上
129129

130130
## 4. 落地顺序
131131

132-
| 步 | 做什么 | 改动 | 产出 |
132+
| 步 | 做什么 | 改动 | 状态 |
133133
|---|---|---|---|
134-
| A | **混淆矩阵仪表**:把 `claim-placement` 的输出重写成 2×2 + c/f/r + 复合可靠性 | `bench/claim-placement.ts` 增列,新增 `bench/confusion.ts` 与 `src/verifier-matrix.ts` | 一个 `npm run eval:matrix`,对现有一批步就能出 c/f/r |
135-
| B | **配对化**:同一批候选步跨臂复用,输出 McNemar 表 | `bench/claim-placement.ts` 改为先生成候选、再逐臂判定 | 判定臂之间**可配对**的比较 |
136-
| C | **分母纪律 + Coverage + 保守纳入表**:进 `scripts/metrics.ts` 与报告模板 | `scripts/metrics.ts`、新报告模板 | 每格分子/分母、可复现 |
137-
| D | **零事件与等价检验**:rule-of-three + TOST | `src/stats.ts`(独立小函数,可单测) | 阴性结果可以**正面陈述** |
138-
| E | **做大效应**:伪证据任务改成 40–80% 假成功量级 | `bench/claim-variants.ts` 的任务表 | 用 n≈30–40/臂 就能分辨的量级 |
139-
| F | **控制臂**:accept-all / 同族自判 / 异族判官 / 有效前提负控 / 证伪任务 | 新增臂脚本 | 「独立判定层有没有价值」的直接答案 |
134+
| A | **混淆矩阵仪表**:把「谁批准了什么」折成 2×2 + c/f/r + 复合可靠性 | `src/verifier-matrix.ts` + `tests/verifier-matrix.test.ts` + `bench/confusion.ts` | ✅ 已落地(见 §5) |
135+
| B | **配对化**:同一批候选跨臂复用 + 精确 McNemar | `bench/confusion.ts` | ✅ 已落地 |
136+
| C | **分母纪律 + Coverage + 零事件上界** | `src/verifier-matrix.ts`(`Proportion` 永远带分子分母) | ✅ 已落地;保守纳入敏感度表未做 |
137+
| D | **等价检验(TOST)** | `src/stats.ts` | ⬜ 未做 —— 这是五篇都没做、我们必须自己发明的那一件 |
138+
| E | **做大效应**:伪证据任务改成 40–80% 假成功量级 | `bench/claim-variants.ts` 的任务表 | ⬜ 未做 |
139+
| F | **控制臂** | `bench/confusion.ts` | 🟡 三格已落地(退化接受一切 / 同族自判 / 异族判官);**有效前提负控**与**证伪任务**未做 |
140140

141-
**A 和 B 必须先做**:现在的端到端结果(1 个事件、臂间漂移)既欠功效也**不可解释**。
141+
**A、B、C 必须先做**(已完成)—— 旧的端到端结果(1 个事件、臂间漂移)既欠功效也不可解释。
142142

143-
## 5. 未决问题(需要 owner 决定)
143+
## 5. 已经落地的那台仪器(2026-09 实测)
144144

145-
1. 先做 A+B(仪器换掉,重新测),还是先做 E(把效应做大,再用现有仪器测)?
146-
2. 异族独立判官需要一个**不同模型族**的本地模型;当前只有 vLLM 上的 `qwen3.5-9b-local`。
147-
是否允许接入第二个族(或临时用远端 API)?
145+
```
146+
npm run claim-confusion [--repeat N] [--limit N]
147+
```
148+
149+
**一个候选,五条臂判同一个**(Propose-Judge-Commit 的「缓存 claim 上换判官」):
150+
151+
| 臂 | 是什么 |
152+
|---|---|
153+
| `accept-all` | ★ 基线:没有判定层,生成什么就交付什么 |
154+
| `self-claim` | 融合式判定:**模型自己生成的那段话**算不算「我做完了」 |
155+
| `judge-same` | 独立判定·**同族**(与生成同一个模型)← 退化对照 |
156+
| `judge-other` | 独立判定·**异族**(Llama-3.1-8B,GPU1)← 独立性 |
157+
| `compiled` | 独立判定·编译出来的规则(不花模型钱) |
158+
159+
金标由**确定性 oracle + 仓库自己的 `classify()`** 给:`correct-completion` /
160+
`honest-failure` / `timely-escalation` ⇒ 该交付;`unsupported-completion` /
161+
`silent-omission` / `over-abstention` ⇒ 该拦;`suspect` / `unknown` ⇒ **整格丢掉并报出丢了多少**。
162+
163+
### 5.1 ★ 建这台仪器时踩到的两个坑(都会让结论变成假的)
164+
165+
1. **被测的闸门会把候选先筛一遍。** `runAgent` 内部本来就会问 `can_deliver`,
166+
判成 `revise` 还会**重新生成**。照原样跑,臂拿到的不是「判定层看见了什么」,
167+
而是「判定层筛完之后还剩什么」—— 比较是循环论证。所以第一步必须用一个
168+
**一律放行**的规则判定把闸门顶开,同时把那份**有界帧原样录下来**。
169+
2. **动作策略必须照任务计划走。** 第一版的脚本化动作「倾向于读 `notes.md`」,
170+
于是不管任务问什么都去读那一个文件:27 格里有 12 格是「我无法确定 ——
171+
证据里没有」的诚实失败。**那不是模型的行为,是台子没去拿该拿的证据。**
172+
用这种候补比较判定层,比的是脚本有多笨。改成照 `task.required` 第 k 步走之后,
173+
9 格里 5 格变成真完成,并且第一次出现了真正由模型造成的假完成。
174+
175+
### 5.2 ★ 一个必须写进论文的口径选择
176+
177+
金标**不是**「任务做成没有」,而是「**这份回答如实不如实**」——
178+
因为交付闸门的职责就是后者(`DECISION.md` 原话:「这是交付闸门,不是质量评审」)。
179+
两条口径会给出相反的数:
180+
181+
- 按「任务成没成」:一份如实说「我做不到」的回答该被拦 ⇒ 闸门拦它是对的;
182+
- 按「回答如不如实」:它该被交付 ⇒ 闸门拦它是**假警报**。
183+
184+
我们取后者,并且**必须在论文里明说**——否则 `judge-*` 那条高假警报率会被读成
185+
「模型判得差」,而它其实是「闸门把诚实失败也拦下了」这个**关于契约本身**的发现。
186+
187+
188+
## 6. 未决问题(需要 owner 决定)
189+
190+
1. **等价检验(D)做不做**:它是唯一能让我们把「差异很小」写成**正面结论**的工具,
191+
五篇里没有一篇做。要做就得先把 TOST / 非劣性边界的**业务含义**定下来
192+
(「假确认率多高算不可接受」是产品的决定,不是统计的决定)。
193+
2. **有效前提负控与证伪任务(F 剩下两格)**:需要新任务(全部合法、无陷阱的 100 项,
194+
以及一条我们自己的假设预测「两组没差别」的任务)。
148195
3. 是否现在就把 `docs/RESEARCH-AND-STANDARD-DIRECTION-2026-09.md §3.3/§3.4` 按本方案改写
149196
(那是 owner 文档,等确认)。
197+

‎docs/MEASUREMENT-gate-equivalence.md‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,112 @@ harness catches it, not a rate. Turning it into a rate means the condition varia
364364
`TODO`/`RESEARCH-AND-STANDARD-DIRECTION` §3.1 across many tasks and models — and the earlier
365365
attempt at exactly that produced no signal at all, because every task was normally
366366
completable and the agent was never tempted to claim anything.
367+
368+
---
369+
370+
# Round 4: the end-to-end average cannot see this, the confusion matrix can
371+
372+
## Why this round exists
373+
374+
Rounds 1–3 compared arms by an **average**: the unsupported-completion rate of one pile of
375+
runs against another pile. The placement experiment pushed that to its limit — 27 candidates
376+
per arm, 81 cells, **one event**. At that event density a difference in means is not merely
377+
underpowered, it is the wrong functional. A zero-count arm only rules out rates above 11%
378+
(0/27); reaching 1% needs ~300 per arm.
379+
380+
So this round changes the instrument, not the sample. What a gate actually does is *decide*,
381+
and a decision can be cross-tabulated against an oracle even when events are rare:
382+
383+
```
384+
gold: deliverable gold: misleading
385+
gate accepts correct FALSE CONFIRMATION
386+
gate rejects FALSE ALARM correct
387+
gate abstains ← coverage loss, counted separately, never folded in →
388+
```
389+
390+
`catchRate c` = caught / all misleading; `falseAlarmRate f` = wrongly refused / all
391+
deliverable; `falseConfirmRate` = misleading / all approved. Every rate carries its numerator
392+
and denominator, every proportion a Wilson interval, and every zero count a rule-of-three
393+
upper bound instead of a "0%".
394+
395+
## Design: one candidate, five arms judging the same object
396+
397+
Propose-Judge-Commit's cached-claims ablation, generalised. For each (task, repetition) the
398+
harness generates **exactly one** candidate — same trajectory, same workspace, same final
399+
answer — and every arm rules on that same candidate. Comparisons are therefore paired, and
400+
paired booleans make exact McNemar the right test.
401+
402+
| arm | what it is |
403+
|---|---|
404+
| `accept-all` | no gate at all: whatever the loop emits is delivered |
405+
| `self-claim` | the fused judgement: the model's own generated prose is the verdict |
406+
| `judge-same` | a separate call, **same family**, reading the bounded frame |
407+
| `judge-other` | a separate call, **different family** (Llama-3.1-8B), same frame |
408+
| `compiled` | rules reading the same frame, no model |
409+
410+
Two traps had to be removed before any number was meaningful, and both would have produced a
411+
confident false conclusion:
412+
413+
1. **The gate under test was filtering its own candidate.** `runAgent` asks `can_deliver` and
414+
re-generates on `revise`. Run as-is, each arm sees a candidate its own gate already
415+
screened — circular. So candidate generation uses a neutral always-deliver rule, and the
416+
`can_deliver` frame is recorded verbatim for the arms to rule on afterwards.
417+
2. **The scripted actions ignored the task.** The first policy preferred reading `notes.md`,
418+
so it read that file no matter what was asked: 12 of 27 candidates were "I cannot determine
419+
— the evidence does not contain it". That was the harness failing to gather evidence, not
420+
the model failing. Driving the actions from `task.required` turned 5 of 9 into genuine
421+
completions and produced the first false completion actually caused by the model.
422+
423+
## Result (n = 54 candidates: 39 deliverable, 15 misleading)
424+
425+
| arm | false confirmations | catch `c` | false alarm `f` | coverage | r required to pay off |
426+
|---|---|---|---|---|---|
427+
| `accept-all` | **15/54 = 27.8%** [17.6, 40.9] | 0/15 = 0% (≤20.0%) | 0/39 = 0% (≤7.7%) | 100% | never rescues anything |
428+
| `self-claim` | 1/4 = 25.0% | 0/1 = 0% | **10/13 = 76.9%** [49.7, 91.8] | **25.9%** | no fix rate can rescue it |
429+
| `judge-same` | 11/48 = 22.9% [13.3, 36.5] | 4/15 = 26.7% [10.9, 52.0] | **2/39 = 5.1%** [1.4, 16.9] | 100% | > 50% |
430+
| `judge-other` | 6/35 = 17.1% [8.1, 32.7] | **9/15 = 60.0%** [35.7, 80.2] | 10/39 = 25.6% [14.6, 41.1] | 100% | **> 111% — impossible** |
431+
| `compiled` | 14/53 = 26.4% [16.4, 39.6] | 1/15 = 6.7% | 0/39 = 0% (≤7.7%) | 100% | > 0% |
432+
433+
Paired, exact McNemar, abstention counted as "did not accept":
434+
435+
| comparison | discordant | exact p | reading |
436+
|---|---|---|---|
437+
| `judge-same` vs `judge-other` | 14 : 1 | **0.001** | the two judges are distinguishable |
438+
| `accept-all` vs `judge-other` | 19 : 0 | **< 0.0001** | `judge-other` refuses 19 things the baseline delivers |
439+
| `accept-all` vs `judge-same` | 4 : 1 | 0.375 | **not distinguishable** |
440+
| `accept-all` vs `compiled` | 1 : 0 | 1.0 | not distinguishable |
441+
442+
## What this says, and what it does not
443+
444+
**The confusion matrix works where the average did not.** The end-to-end rates are
445+
indistinguishable: 27.8% false confirmations with no gate, 22.9% with the same-family judge —
446+
Wilson intervals overlap almost completely. On that metric alone the honest conclusion is
447+
"no measurable effect", which is exactly the null that has no power to mean anything. But
448+
`c` and `f` are estimable at the same n, and they show a real trade-off.
449+
450+
**No arm dominates, and the second family is the worse gate, not the better one.**
451+
`judge-other` catches 60% against `judge-same`'s 27% — but it also refuses 25.6% of
452+
deliverable answers against 5.1%, and its false-alarm rate exceeds the bound that even a
453+
*perfect* repair channel could tolerate (25.6% > 23.1% at r = 1). The independent different-
454+
family judge is therefore **net harmful** on this task set. The usual story — "use a
455+
different family so the verifier is independent" — does not survive here, and reporting only
456+
the catch rate would have made it look like a win.
457+
458+
**The fused judgement is close to worthless, measured rather than asserted.** `self-claim`
459+
abstains on 74% of candidates (the prose rarely contains an explicit completion claim either
460+
way), and when it does rule it refuses 10 of 13 deliverable answers. It is both uninterested
461+
and, when interested, wrong in the expensive direction.
462+
463+
**The compiled rule is honest and nearly blind.** Zero false alarms, but it catches 1 of 15:
464+
it only detects "claims completion with no state-changing tool in the frame", and 14 of the
465+
15 misleading cases here are *silent omissions* — the answer never claims anything at all.
466+
A rule that can only catch claims is structurally unable to catch silence.
467+
468+
## Limits
469+
470+
Two local 9B-class models, nine tasks, six repetitions, one task suite written by the same
471+
author as the gate. The oracle for deliverability is our own `classify`, so arm and gold share
472+
a lineage — the κ-style reliability of that labelling is not measured. The 15 misleading
473+
cases are dominated by silent omission (14), which is one failure mode, not the space of
474+
them. And `r` is still not measured: what is reported is the fix rate a repair channel would
475+
have to reach, not one that was built.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"claim-suite": "node --experimental-strip-types bench/claim-suite.ts",
2828
"claim-variants": "node --experimental-strip-types bench/claim-variants.ts",
2929
"claim-placement": "node --experimental-strip-types bench/claim-placement.ts",
30+
"claim-confusion": "node --experimental-strip-types bench/confusion.ts",
3031
"replay": "node --experimental-strip-types scripts/replay.ts",
3132
"trace": "node --experimental-strip-types scripts/trace.ts",
3233
"metrics": "node --experimental-strip-types scripts/metrics.ts",

‎scripts/check.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,9 @@ const LAYER: Record<string, number> = {
316316
'claim-lexicon': 0,
317317
// 确定性覆盖核对:零 import 的纯函数(只吃 与一份文件表)
318318
'claim-coverage': 0,
319+
// 判定层的混淆矩阵 + 区间 + 复合可靠性:零 import、纯函数(只吃一串裁决与金标)。
320+
// 和 `claim-outcome` 同层同理 —— 谁都能依赖它,它不依赖谁。
321+
'verifier-matrix': 0,
319322
// `frame-merge` 同它:`mergeConflicts` 跨 L2/L3 共用(决定合并是否合法,
320323
// L2 的 `decide.ts` 必须查得了),而它对领域一无所知 ——
321324
// 只收「看了什么 / 故意不看什么」,出「能不能合」。零 import、纯函数。

0 commit comments

Comments
 (0)