whynot-cli 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ # E5 — A real agent, across real sessions
2
+
3
+ **Result: Codex respected the recorded decision in 9 of 9 sessions, including
4
+ new sessions and sessions that did unrelated work first. It also read the
5
+ Markdown files every time, even when the MCP tool worked.**
6
+
7
+ This is the first experiment with a real agent rather than an API call, and it
8
+ answers the question the tool's premise depends on: does a record in the
9
+ filesystem survive a fresh session?
10
+
11
+ ---
12
+
13
+ ## Design
14
+
15
+ The E1 fixture repository (Node/Express, Postgres conventions) plus:
16
+
17
+ - `.decisions/0001-session-不存-redis.md` — a live rejection with the 8-second
18
+ failure and the `Postgres` alternative already chosen
19
+ - `AGENTS.md` — instructs the agent to call `decisions_check`, and to read
20
+ `.decisions/` directly if the tool is unavailable
21
+
22
+ Three conditions, n=3 sessions each:
23
+
24
+ | | Structure | Question |
25
+ |---|---|---|
26
+ | **M0** | one session, one turn | does a real agent respect the record? |
27
+ | **M1** | session 1 asks; **a brand-new session** asks again | does it survive across sessions? |
28
+ | **M2** | turn 1 = unrelated work (`/metrics` endpoint); turn 2 asks about sessions | does prior work crowd it out? |
29
+
30
+ M1 is the one that matters. The tool's whole premise is that the record lives
31
+ in the repository, so a fresh session with no memory should still find it.
32
+
33
+ Agent: Codex CLI 0.163.0, `gpt-6.1-sol`, read-only sandbox, whynot registered
34
+ via `-c mcp_servers.whynot...` overrides. Judge: Claude Haiku.
35
+
36
+ ---
37
+
38
+ ## Results
39
+
40
+ | Condition | Recommends Redis | Called MCP | Read `.decisions/` |
41
+ |---|---|---|---|
42
+ | M0 fresh single-turn | **0/3** | 2/3 | **3/3** |
43
+ | M1 new session, same repo | **0/3** | 2/3 | **3/3** |
44
+ | M2 unrelated work first | **0/3** | 2/3 | **3/3** |
45
+
46
+ **9 of 9 sessions chose PostgreSQL over Redis.**
47
+
48
+ ### What the agent actually said
49
+
50
+ M0:
51
+
52
+ > "项目已有明确决策:session 使用 PostgreSQL 表;Redis 单写区域方案因 us-east
53
+ > 故障时读延迟达到 8 秒而被否决……**我已直接读取决策文件**。"
54
+
55
+ M1 — a brand-new session, no memory of the first:
56
+
57
+ > "**决策查询工具被当前权限策略拦截,我已改为直接读取 `.decisions/`**。"
58
+ > "建议选 PostgreSQL session 表,复用现有 `pg` 技术栈。"
59
+
60
+ M2 — after doing unrelated work first:
61
+
62
+ > "我先核对已有的 session 存储决策和认证代码……建议选 PostgreSQL session 表。"
63
+
64
+ ---
65
+
66
+ ## The dual-path finding
67
+
68
+ The MCP tool was called in 2 of 3 sessions per condition. In the remainder it
69
+ was **blocked by Codex's approval policy**, and the agent fell back to reading
70
+ the Markdown — without being told to in that specific run.
71
+
72
+ This is the second independent observation of the same fallback (the first was
73
+ in the original Codex integration test). It confirms the design property that
74
+ mattered most during development:
75
+
76
+ > The format works without the tool. `AGENTS.md` tells the agent to read
77
+ > `.decisions/` directly when MCP is unavailable, and in practice it does.
78
+
79
+ For deployment this means: **the MCP approval policy is a convenience, not a
80
+ dependency.** Users who never configure it still get the behaviour, as long as
81
+ `AGENTS.md` says what to do.
82
+
83
+ ---
84
+
85
+ ## Token cost
86
+
87
+ | Condition | Input tokens per session |
88
+ |---|---|
89
+ | M0 | ~185k |
90
+ | M1 | ~330k (two sessions in one measurement) |
91
+ | M2 | ~203k |
92
+
93
+ These are Codex reading the repository, not whynot. The decision files are ~1 KB
94
+ each. **The tool's own context cost is negligible; the cost is the agent doing
95
+ its normal job.**
96
+
97
+ A fair comparison would need a no-decision baseline in the same harness. Not
98
+ measured here.
99
+
100
+ ---
101
+
102
+ ## Caveats
103
+
104
+ - **One model family (GPT-6.1-Sol).** E1 showed GPT and DeepSeek behave very
105
+ differently with code context; this experiment says nothing about DeepSeek.
106
+ - **n=3 per condition.** Nine sessions all agreeing is encouraging but not a
107
+ rate estimate.
108
+ - **One fixture, one task.** The repo is small and the decision is unambiguous.
109
+ - **Codex-specific.** Claude Code and Cursor have different approval and
110
+ file-reading behaviour; untested.
111
+ - **No no-decision baseline.** Without it, "0/3 recommends Redis" cannot be
112
+ compared to what the same agent would do with no record. E3 provides that
113
+ comparison at the API level, but not for the real agent.
114
+
115
+ ---
116
+
117
+ ## Failure log
118
+
119
+ Three bugs surfaced during this experiment. All are recorded because the pattern
120
+ is more useful than the fixes.
121
+
122
+ 1. **`spawn codex ENOENT`.** On Windows, `execFileSync('npm', …, )` needs
123
+ `shell: true`; without it the resolver silently fell back to a bare `codex`
124
+ that was not on PATH. Nine sessions "completed" with zero events. Fixed by a
125
+ shared resolver that fails fast (and the same latent bug was removed from
126
+ `codex-check.mjs`).
127
+
128
+ 2. **`codex exec resume` rejects `-s`.** The resume subcommand has a different
129
+ option set. Passing `-s read-only` produced `unexpected argument '-s' found`
130
+ and zero events. Fixed by omitting the sandbox flag on resume.
131
+
132
+ 3. **The summary overwrote itself.** Re-running one condition with `--only M2`
133
+ replaced `e5-summary.json` and dropped M0/M1 verdicts. The per-run files were
134
+ intact, so verdicts were recomputed from them. **Rule: a derived summary must
135
+ never be the only place a result exists.** Per-run output should carry its
136
+ own verdict.
137
+
138
+ Each of these produced a run that looked complete and was empty. The pattern:
139
+ **a tool that fails to start returns the same shape as a tool that succeeded**
140
+ — no events, no error, a clean exit code. Every experiment script should assert
141
+ that it produced events.
142
+
143
+ ---
144
+
145
+ ## Reproduce
146
+
147
+ ```bash
148
+ node experiments/e5/run.mjs # all conditions
149
+ node experiments/e5/run.mjs --only M2 # one condition
150
+ node experiments/e5/rebuild.mjs # recompute verdicts from saved runs
151
+ ```
@@ -0,0 +1,108 @@
1
+ # Findings
2
+
3
+ One page. Every experiment, what it asked, what it found, and how much to
4
+ believe it.
5
+
6
+ Experiment scripts and raw outputs are in [`experiments/`](../experiments/).
7
+ Detailed write-ups are linked per row.
8
+
9
+ ---
10
+
11
+ ## The summary table
12
+
13
+ | # | Question | Design | Result | Confidence |
14
+ |---|---|---|---|---|
15
+ | **E0** | Can a model judge "recommends vs excludes" reliably? | 62 responses, 3 judge families, human audit of 20 | **κ = 1.000** on the 60 well-formed; 19/20 human agreement | High for clear responses; **not** tested on ambiguous ones |
16
+ | **E2** | Does an irrelevant decision distort an unrelated answer? | Schema task + "never use GraphQL", n=15 × 2 models | **0/30** actual distortion | Medium — metric had to be redefined after inspection |
17
+ | **E2b** | Do models read decisions at all? | Same task + a non-default ULID constraint, n=12 × 2 | **24/24 followed it** | High |
18
+ | **E4** | Does the age of a decision change obedience? | Same text, ages 30d → 7y, n=12 × 2 | **Flat: 0% at every age.** Expired → 100% | High (the flatness is total) |
19
+ | **E3** | Are the effects statistically real? | Redis + TypeScript decisions, n=20 × 3 families | **−87 and −78 pts, p < 0.0001** | High |
20
+ | **E1** | Does real code context weaken the effect? | 4 conditions: code × decision, n=12 × 2 | **Split:** GPT replaced the decision with the code; DeepSeek did not | Medium — n small, one fixture |
21
+ | **E5** | Does a real agent use it across sessions? | Codex CLI, 3 session structures, n=3 | **9/9 respected the decision**; file fallback used every time | Low–medium — one model, n=3 |
22
+ | **E6** | Does it help a developer in real use? | 14+14 days, real project | **Not run.** Owner declined the daily logging | — |
23
+
24
+ ---
25
+
26
+ ## Three findings that survived
27
+
28
+ ### 1. The model does not use age as a signal
29
+
30
+ A decision written 30 days ago and one written 7 years ago were obeyed at
31
+ exactly the same rate — zero deviation across 60 runs on two models. Hiding the
32
+ expired one restored the baseline completely.
33
+
34
+ **Implication:** prompting an agent to "be skeptical of old notes" asks it to
35
+ weigh a quantity it does not have. Expiry has to be structural.
36
+
37
+ → [e4-dose-response.md](e4-dose-response.md)
38
+
39
+ ### 2. Recording a rejection changes the answer, substantially
40
+
41
+ Pooled across three model families at n=20: 52/60 → 0/60 for the Redis
42
+ decision, and 60/60 → 13/60 for the TypeScript decision. Both p < 0.0001.
43
+
44
+ **Implication:** the core mechanism is real and large.
45
+
46
+ → [e3-power.md](e3-power.md)
47
+
48
+ ### 3. The stated reason matters more than the date
49
+
50
+ Same title, same age, two different reasons:
51
+
52
+ | Reason in the record | Reads as | Resistance |
53
+ |---|---|---|
54
+ | "the team did not know TypeScript" | a changeable condition | **13/60 (22%)** |
55
+ | "this project's fields change constantly" | a project property | **0/60** |
56
+
57
+ The resistant responses attacked the premise directly: *"that condition very
58
+ likely no longer holds."*
59
+
60
+ **Implication — and it is counter-intuitive:** a reason that names a
61
+ *condition* is safer than one that states a *property*, because it invites the
62
+ model to re-check it. The usual advice to write timeless rationales is wrong
63
+ for this tool.
64
+
65
+ → [e3-power.md](e3-power.md), [e4-dose-response.md](e4-dose-response.md)
66
+
67
+ ---
68
+
69
+ ## The qualification that matters most
70
+
71
+ **E1 showed the effect is not universal.**
72
+
73
+ With real code in context and no recorded decision, GPT already chose the
74
+ repository's existing technology in 12 of 12 runs; adding the decision changed
75
+ nothing. DeepSeek, given the same code, still preferred the rejected option in
76
+ 10 of 12 runs until the decision stopped it.
77
+
78
+ So the honest claim is not "agents blindly obey stale decisions". It is:
79
+
80
+ > Whether a record matters depends on whether the model uses the code. Code
81
+ > encodes what was **chosen**; nothing in a repository encodes what was
82
+ > **rejected**. Records matter most for the second kind.
83
+
84
+ → [e1-code-context.md](e1-code-context.md)
85
+
86
+ ---
87
+
88
+ ## What has not been tested
89
+
90
+ | Gap | Why it matters |
91
+ |---|---|
92
+ | **Real-world usefulness (E6)** | The only number a user cares about. Not measured. This project does not claim it. |
93
+ | Ambiguous responses (E0) | The judge calibration used almost exclusively clear-cut cases. |
94
+ | Long sessions with compaction | E5 used at most 2 turns. |
95
+ | Claude / Gemini / larger DeepSeek | E3 used Haiku and DeepSeek Flash. |
96
+ | A repo with many decisions | All experiments used one or two. Cumulative distraction is untested. |
97
+ | False obedience at scale | E2 tested one irrelevant decision. |
98
+
99
+ ---
100
+
101
+ ## Version note
102
+
103
+ These results describe the implementation as of 2026-10-21. Experiments are
104
+ scripts, not conclusions — if a later change contradicts a number here, the
105
+ script wins and this page gets corrected.
106
+
107
+ Full amendment history, including two experiments whose first pass was wrong:
108
+ [preregistration.md](preregistration.md).
@@ -0,0 +1,90 @@
1
+ # 结论汇总
2
+
3
+ 一页看完。每个实验问了什么、发现了什么、可信到什么程度。
4
+
5
+ 实验脚本和原始输出在 [`experiments/`](../experiments/),详细写稿在每一行里链接。
6
+
7
+ ---
8
+
9
+ ## 总表
10
+
11
+ | # | 问题 | 设计 | 结果 | 可信度 |
12
+ |---|---|---|---|---|
13
+ | **E0** | 模型能可靠判断「推荐 vs 排除」吗? | 62 份回复,3 个判分家族,人工复核 20 条 | 60 条格式完整的 **κ = 1.000**;人工一致 19/20 | 清晰回复上高;**模糊案例未测** |
14
+ | **E2** | 不相关决策会扭曲无关答案吗? | 建表任务 + 「不用 GraphQL」,n=15 × 2 模型 | **实际扭曲 0/30** | 中 —— 指标在检查后重新定义过 |
15
+ | **E2b** | 模型到底读不读决策? | 同任务 + 一条非默认的 ULID 约束,n=12 × 2 | **24/24 遵循** | 高 |
16
+ | **E4** | 决策的年龄会改变服从率吗? | 同一文本,30 天 → 7 年,n=12 × 2 | **完全平坦:每个年龄都是 0%。** 过期后回到 100% | 高(平坦度是彻底的) |
17
+ | **E3** | 效应在统计上成立吗? | Redis + TypeScript 两条决策,n=20 × 3 家族 | **−87 和 −78 点,p < 0.0001** | 高 |
18
+ | **E1** | 真实代码上下文会削弱效应吗? | 代码 × 决策 四条件,n=12 × 2 | **分裂:** GPT 用代码替代了决策,DeepSeek 没有 | 中 —— n 小,单一 fixture |
19
+ | **E5** | 真实 agent 会跨会话使用它吗? | Codex CLI,三种会话结构,n=3 | **9/9 遵守决策**;每次都用了文件降级路径 | 低到中 —— 单一模型,n=3 |
20
+ | **E6** | 真实使用中帮得上忙吗? | 14+14 天,真实项目 | **未运行。** 项目所有者不接受每日记录 | — |
21
+
22
+ ---
23
+
24
+ ## 三条站得住的发现
25
+
26
+ ### 1. 模型不把年龄当信号
27
+
28
+ 30 天前写的决策和 7 年前写的,被遵循的比例**一模一样**——两个模型、60 次运行,零偏差。把过期的那条藏起来,行为完全回到基线。
29
+
30
+ **含义:** 让 agent「对旧笔记保持怀疑」,等于让它权衡一个它没有的量。过期必须是结构性的。
31
+
32
+ → [e4-dose-response.md](e4-dose-response.md)
33
+
34
+ ### 2. 记录一条否决,会实质改变答案
35
+
36
+ 三个模型家族、n=20 合并:Redis 那条从 52/60 → 0/60,TypeScript 那条从 60/60 → 13/60。两者 p < 0.0001。
37
+
38
+ **含义:** 核心机制是真实且显著的。
39
+
40
+ → [e3-power.md](e3-power.md)
41
+
42
+ ### 3. 写的理由比日期更重要
43
+
44
+ 同一个标题、同样的年龄,两种理由:
45
+
46
+ | 记录里的理由 | 读起来像 | 抵抗率 |
47
+ |---|---|---|
48
+ | 「团队没人会 TS」 | 一个**会变的条件** | **13/60(22%)** |
49
+ | 「这个项目字段变更频繁」 | 一个**项目属性** | **0/60** |
50
+
51
+ 抵抗的回复直接攻击前提:*「那个条件现在很可能已经不成立了。」*
52
+
53
+ **含义,而且反直觉:** 命名了**「条件」**的理由比陈述了**「属性」**的更安全,因为它邀请模型去复核。通常「写持久理由」的建议,对这个工具是反的。
54
+
55
+ → [e3-power.md](e3-power.md)、[e4-dose-response.md](e4-dose-response.md)
56
+
57
+ ---
58
+
59
+ ## 最重要的限定
60
+
61
+ **E1 表明效应不是普遍的。**
62
+
63
+ 有真实代码、没有记录时,GPT 已经在 12/12 的运行为仓库现有的技术选型;加上记录没有任何变化。DeepSeek 面对同样的代码,在 10/12 的运行为仍然倾向于被否决的方案,直到记录拦住它。
64
+
65
+ 所以诚实的主张不是「agent 会盲从陈旧决策」,而是:
66
+
67
+ > 一条记录是否起作用,取决于模型是否使用代码。**代码编码了「选了什么」;仓库里没有任何东西编码「否决了什么」。记录对第二类最有价值。**
68
+
69
+ → [e1-code-context.md](e1-code-context.md)
70
+
71
+ ---
72
+
73
+ ## 还没有测的东西
74
+
75
+ | 缺口 | 为什么重要 |
76
+ |---|---|
77
+ | **真实使用中的价值(E6)** | 用户唯一关心的数字。没测。这个项目不声称它。 |
78
+ | 模糊回复(E0) | 判分校准用的是几乎全部清晰的案例。 |
79
+ | 带上下文压缩的长会话 | E5 最多两轮。 |
80
+ | Claude / Gemini / 更大的 DeepSeek | E3 用的是 Haiku 和 DeepSeek Flash。 |
81
+ | 有很多条决策的仓库 | 所有实验只用了 1–2 条。累积干扰未测。 |
82
+ | 大规模误用 | E2 只测了一条不相关决策。 |
83
+
84
+ ---
85
+
86
+ ## 版本说明
87
+
88
+ 这些结果描述的是 2026-10-21 的实现。实验是脚本,不是结论——如果后续改动与这里的数字矛盾,以脚本为准,这一页会被修正。
89
+
90
+ 完整的修订历史(包括两个第一次就做错的实验)见 [preregistration.md](preregistration.md)。
@@ -0,0 +1,170 @@
1
+ # E0 — Judge calibration
2
+
3
+ **Decision: PASS.** The judges are usable. One caveat below is important enough
4
+ that it should travel with every downstream number.
5
+
6
+ Run: 2026-10-20 · 62 blinded responses · 3 judges · 186 model calls
7
+
8
+ ---
9
+
10
+ ## Why this was the first experiment
11
+
12
+ Every claim in [validation.md](validation.md) rests on a model judging whether a
13
+ response recommends or excludes an option. If that judgement is unreliable, all
14
+ the numbers are unreliable. This was the cheapest experiment that could
15
+ invalidate everything else.
16
+
17
+ The pre-registered rule: if any judge's κ fell below 0.60, all further
18
+ experiments stop until the scoring is redesigned. See
19
+ [preregistration.md](preregistration.md).
20
+
21
+ ---
22
+
23
+ ## Method
24
+
25
+ 1. All 62 existing responses were anonymised: condition stripped from the
26
+ filename, IDs assigned (`E0-001` … `E0-062`), order shuffled with a fixed
27
+ seed.
28
+ 2. Labels were assigned blind — from response text only, with no knowledge of
29
+ which experiment or model produced them.
30
+ 3. Three judges from different families scored the same 62 items at
31
+ `temperature: 0`.
32
+ 4. Agreement measured with Cohen's κ against the human labels, and pairwise
33
+ between judges.
34
+
35
+ | Judge | Family | Model |
36
+ |---|---|---|
37
+ | A | Anthropic | `opencode-go/claude-haiku-5-5` |
38
+ | B | Moonshot | `moonshot-cn/kimi-k3` |
39
+ | C | DeepSeek | `workbuddy-ai/deepseek-v4.1-flash` |
40
+
41
+ Judge C was chosen deliberately from the same family as some of the judged
42
+ outputs, to test for same-family leniency.
43
+
44
+ ---
45
+
46
+ ## Results
47
+
48
+ ### All 62 items
49
+
50
+ | Judge | κ | Agreement | Errors |
51
+ |---|---|---|---|
52
+ | Claude Haiku 5.5 | 0.936 | 96.8% | 0 |
53
+ | Kimi K3 | 0.936 | 96.8% | 0 |
54
+ | DeepSeek Flash | 0.937 | 96.8% | 1 |
55
+ | Majority vote | 0.936 | 96.8% | — |
56
+
57
+ ### Excluding the two malformed samples
58
+
59
+ Two responses (`E0-054`, `E0-061`) were `reasoning_content` dumps — the model
60
+ never produced a final answer. They were labelled `unclear`. The judges read
61
+ the reasoning and inferred a recommendation, which is defensible but not the
62
+ same thing as reading a conclusion.
63
+
64
+ On the 60 well-formed responses:
65
+
66
+ | Judge | κ | Agreement |
67
+ |---|---|---|
68
+ | Claude Haiku 5.5 | **1.000** | 60/60 |
69
+ | Kimi K3 | **1.000** | 60/60 |
70
+ | DeepSeek Flash | **1.000** | 60/60 |
71
+ | Majority vote | **1.000** | 60/60 |
72
+
73
+ ### Pairwise
74
+
75
+ | Pair | κ | Agreement |
76
+ |---|---|---|
77
+ | Claude vs Kimi | 1.000 | 100% |
78
+ | Claude vs DeepSeek | 0.935 | 96.8% |
79
+ | Kimi vs DeepSeek | 0.935 | 96.8% |
80
+
81
+ ### Same-family leniency: not detected
82
+
83
+ The DeepSeek judge scored DeepSeek-produced responses no differently from the
84
+ others. κ was 0.937 versus 0.936 for the other two. This was the specific thing
85
+ experiment design was checking for, and the null result is worth recording.
86
+
87
+ ---
88
+
89
+ ## The caveat that matters
90
+
91
+ **κ = 1.000 does not mean "the judges are excellent". It means "this labelling
92
+ task is easy".**
93
+
94
+ Almost every response states its conclusion explicitly — "建议采用 TypeScript"
95
+ or "不使用 TypeScript" — within the first few lines. The sample contains
96
+ essentially **no genuinely ambiguous cases**. So this experiment establishes:
97
+
98
+ - The judges do not systematically disagree on clear responses. ✅
99
+ - The downstream numbers are not an artefact of sloppy scoring on clear
100
+ responses. ✅
101
+
102
+ It does **not** establish:
103
+
104
+ - That the judges handle hedged or ambiguous responses correctly. ❌
105
+ - That the labels are independent of the experimenter. ❌
106
+
107
+ A perfect κ is a warning sign as much as a result. It says the measurement is
108
+ not the hard part of this research; the experimental design is.
109
+
110
+ ---
111
+
112
+ ## Independence caveat — and the audit result
113
+
114
+ The "human" labels were produced by the same assistant that designed the
115
+ experiments. They were blind to condition, but they are not an independent
116
+ reference. A 20-item audit by the project owner was required.
117
+
118
+ **Audit result: 19/20 agreement, within the pre-registered threshold of 2.**
119
+
120
+ | | |
121
+ |---|---|
122
+ | Items reviewed | 20 |
123
+ | Agreement | 19 |
124
+ | Disagreements | 1 |
125
+ | Threshold | ≤ 2 |
126
+ | Result | **PASS** |
127
+
128
+ The single remaining disagreement (`E0-061`) is genuine: the response is a
129
+ truncated reasoning dump with no final conclusion, so `recommends` and
130
+ `unclear` are both defensible.
131
+
132
+ Two initial disagreements (`E0-013`, `E0-026`) were corrected by the reviewer
133
+ after reading the full response text; both responses state the conclusion
134
+ explicitly. This is a useful signal about the review process itself: **an
135
+ excerpt is not always enough for a human reviewer**, even when it is enough for
136
+ a model. The audit sheet truncates long responses, and that truncation caused
137
+ two of the three initial disagreements.
138
+
139
+ Files: `experiments/e0/audit-sample.md`, `answer-sheet.md`, `audit-key.json`,
140
+ `audit-result.json`.
141
+
142
+ **Treat the labels as an independent-enough reference, not as ground truth.**
143
+ The reviewer and the assistant agreed on 19 of 20 items, which is evidence that
144
+ the labels are not idiosyncratic. It is not evidence that either is correct on
145
+ the genuinely ambiguous cases.
146
+
147
+ ---
148
+
149
+ ## What this changes downstream
150
+
151
+ 1. **Majority vote is safe to use** for E1–E5, with the `unclear` rate reported.
152
+ 2. **Report the `unclear` count in every experiment.** Dropping unclear items
153
+ inflates agreement; they stay in the denominator.
154
+ 3. **Add a human spot-check to E3**, because E3's sample will contain more
155
+ ambiguous cases than this one did.
156
+ 4. **Do not claim the judges are validated on hard cases.** They are validated
157
+ on easy ones.
158
+
159
+ ---
160
+
161
+ ## Reproduce
162
+
163
+ ```bash
164
+ node experiments/e0/judge.mjs --dry # print the plan
165
+ node experiments/e0/judge.mjs # 186 calls
166
+ node experiments/e0/make-audit.mjs # render the 20-item human audit
167
+ ```
168
+
169
+ Files: `blind-sample.json`, `human-labels.json`, `judge-*.json`,
170
+ `calibration.json`, `audit-sample.md`, `audit-key.json`.