whynot-cli 0.0.0-stage → 0.1.0

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.
package/docs/design.md ADDED
@@ -0,0 +1,139 @@
1
+ # Design
2
+
3
+ Why the tool looks like this.
4
+
5
+ ## The problem
6
+
7
+ A coding agent has no memory of last week. Every session starts from nothing.
8
+ That is mostly fine: the code is the state, and the agent can read it. What it
9
+ cannot read is **why you rejected an approach**.
10
+
11
+ That knowledge has three properties that make it hard:
12
+
13
+ - It does not exist in the repository. It exists in someone's head.
14
+ - It decays. Six months later, the person remembers "we tried something" but
15
+ not what broke.
16
+ - It is expensive to rediscover. The agent will happily spend tokens walking
17
+ the same dead end again.
18
+
19
+ ## The finding that shaped the design
20
+
21
+ We recorded a 2019 decision — "do not use TypeScript, the team does not know
22
+ it" — and asked two model families to choose a stack for a brand-new 2026
23
+ service. Every response followed the seven-year-old note. Adding a warning to
24
+ the prompt ("this is old, be skeptical") did not fix it. Making the entry
25
+ expire did.
26
+
27
+ The full numbers are in [validation.md](validation.md).
28
+
29
+ The conclusion: **an agent cannot be relied on to weigh a stale instruction
30
+ correctly.** So the design does not ask it to. An expired entry is not shown to
31
+ the agent at all.
32
+
33
+ This one decision explains most of the rest of the design.
34
+
35
+ ## Four decisions
36
+
37
+ ### 1. Data is Markdown in git
38
+
39
+ Everything else follows from this.
40
+
41
+ - A human can read and edit it without the tool.
42
+ - `git diff` gives review for free.
43
+ - `git revert` gives an undo for free.
44
+ - If the project is abandoned, the data is still usable with `grep`.
45
+
46
+ Most tools in this space store decisions in a database or a vector index. When
47
+ those projects die, users lose the data along with the tool. This project is
48
+ designed so that cannot happen.
49
+
50
+ ### 2. Expiry is the default
51
+
52
+ Every entry carries an expiry date. `rejected` entries last 90 days, `decision`
53
+ 180, `constraint` 365. When it lapses, the entry stops reaching the agent.
54
+
55
+ An expired entry is not deleted. `whynot stale` surfaces it, and a human either
56
+ renews it or retires it. Both are explicit actions.
57
+
58
+ `expires_on: never` exists for genuinely permanent constraints, but it must be
59
+ written deliberately. Permanence should be a decision, not a default.
60
+
61
+ ### 3. No embeddings, no model calls on write
62
+
63
+ Retrieval is deliberately simple: case-insensitive keyword matching over titles,
64
+ tags, and bodies, scored by how many query terms hit.
65
+
66
+ Two reasons:
67
+
68
+ - **Auditability.** When an entry appears in the agent's context, it is obvious
69
+ why: the words matched. With embeddings, nobody can explain it.
70
+ - **Cost and failure modes.** Vector indexing goes stale. LLM extraction is
71
+ expensive and is the main source of corrupted memories in the tools that use
72
+ it. Both are avoidable here, so they are avoided.
73
+
74
+ The trade-off is real: keyword matching misses paraphrases. That is why
75
+ `decisions_check` with no query returns a compact index of everything live, so
76
+ the agent can see the full list cheaply before narrowing down.
77
+
78
+ ### 4. The format works without the tool
79
+
80
+ The agent gets `decisions_check` through MCP. If MCP is unavailable, it can
81
+ still read `.decisions/` directly — that is just Markdown.
82
+
83
+ This was not a theoretical property. In the Codex integration test, the MCP
84
+ call was blocked by an approval policy, and the agent fell back to reading the
85
+ files and still respected the decision.
86
+
87
+ ## Form factor: four layers, not one
88
+
89
+ A common mistake in this category is picking one form and forcing everything
90
+ through it. Each concern here belongs to a different layer:
91
+
92
+ | Layer | Form | Why it has to be this |
93
+ |---|---|---|
94
+ | Data | `.decisions/*.md` in git | Works with zero tooling. The durable asset. |
95
+ | Behaviour | A block in `AGENTS.md` | Making an agent *proactively record* a rejection is an instruction, not an API. |
96
+ | Capability | MCP server | Pull-based, structured, works across agents. |
97
+ | Human loop | CLI | Renewing and retiring are human decisions. |
98
+ | Distribution | npm and agent plugins | Zero-install; meets people where they already are. |
99
+ | **Not** | Hosted API | Decisions about a codebase are the most sensitive artifact in the repo. Hosting them defeats the purpose. |
100
+
101
+ ## Cross-platform
102
+
103
+ The project targets Windows, macOS, and Linux, because the failure modes are
104
+ different on each.
105
+
106
+ - **Line endings.** `init` writes `*.md text eol=lf` to `.gitattributes`.
107
+ Without it, Windows checkouts write CRLF and every diff shows the whole file.
108
+ - **Filenames.** Windows forbids `\ / : * ? " < > |`, and Windows and macOS are
109
+ case-insensitive. Filenames are slugged to `[a-z0-9]` plus CJK, which
110
+ eliminates a class of "works on Linux" bugs.
111
+ - **No background process.** Windows file watching is unreliable and needs
112
+ native dependencies. The tool is invoked, never resident.
113
+ - **No git hooks.** Cross-platform shell in hooks is a minefield. Not used.
114
+
115
+ ## Deliberately not built
116
+
117
+ - Vector search or embeddings.
118
+ - Any model call in the write path.
119
+ - A daemon or file watcher.
120
+ - A web UI.
121
+ - Automatic detection of rejections (a hook is possible later, but it is not
122
+ required for the core loop).
123
+ - Team sync. `git` already does this.
124
+ - Multi-repo aggregation.
125
+
126
+ Each one is a real feature someone has asked for. Each one also makes the tool
127
+ heavier, and this category has a graveyard full of heavier tools.
128
+
129
+ ## Open questions
130
+
131
+ - **Does the repeated-mistake rate actually drop in real use?** The experiments
132
+ measure single-turn behaviour. The claim that matters is longitudinal and has
133
+ not been measured yet.
134
+ - **How often does expiry lose knowledge people still need?** The trade is
135
+ deliberate, but the ratio is unknown. `whynot stale` is where that shows up.
136
+ - **Does this survive being used by a team?** The design assumes one person
137
+ curating. With five people, who renews what?
138
+ - **Is 90 days right for a rejection?** It is a guess. Different codebases move
139
+ at different speeds.
@@ -0,0 +1,102 @@
1
+ # 设计说明
2
+
3
+ 这个工具为什么长这样。
4
+
5
+ ## 问题
6
+
7
+ 编码 agent 对上周发生的事没有记忆。每次会话都从零开始。这本身问题不大——代码就是状态,agent 可以自己读。但它读不到的是:**你为什么否决了某个方案。**
8
+
9
+ 这类知识有三个麻烦的性质:
10
+
11
+ - 它不在仓库里,在某个人脑子里。
12
+ - 它会腐烂。半年后,那个人只记得「当时好像试过」,不记得具体哪里崩了。
13
+ - 重新发现它的代价很高。agent 会毫不心疼地再走一遍那条死路。
14
+
15
+ ## 决定了整个设计的那个发现
16
+
17
+ 我们记录了一条 2019 年的决策——「不要用 TypeScript,团队没人会」——然后让两个模型家族给一个 **2026 年的全新服务**做技术选型。**每一次回复都遵循了那条七年前的笔记。** 在提示词里加一句「这条很旧,请保持怀疑」没有修好。让条目过期,才修好。
18
+
19
+ 完整数字见 [validation.zh-CN.md](validation.zh-CN.md)。
20
+
21
+ 结论是:**不能指望 agent 正确地权衡一条陈旧指令。** 所以设计上干脆不要求它权衡——**过期条目根本不出现在 agent 的视野里。**
22
+
23
+ 这一条决定了后面大部分设计。
24
+
25
+ ## 四个决定
26
+
27
+ ### 1. 数据是 git 里的 Markdown
28
+
29
+ 其他一切都由此而来。
30
+
31
+ - 人不用工具也能读、能改。
32
+ - `git diff` 免费提供 review。
33
+ - `git revert` 免费提供撤销。
34
+ - 就算项目被放弃,数据依然能用 `grep` 检索。
35
+
36
+ 这个领域里大多数工具把决策存进数据库或向量索引。那些项目死掉时,用户会连数据一起丢。这个项目从设计上就不允许这种事发生。
37
+
38
+ ### 2. 默认过期
39
+
40
+ 每条记录都带一个到期日。否决类 90 天,决策类 180 天,约束类 365 天。到期后,条目不再到达 agent。
41
+
42
+ 过期条目**不会被删除**。`whynot stale` 会把它列出来,由人决定续期还是归档。两者都是显式动作。
43
+
44
+ `expires_on: never` 是为真正永久的约束准备的,但必须刻意写出来。**永久应该是一个决定,不是一个默认值。**
45
+
46
+ ### 3. 不用 embedding,写入时不调模型
47
+
48
+ 检索刻意做得很简单:对标题、标签、正文做大小写不敏感的关键词匹配,按命中的查询词数量排序。
49
+
50
+ 两个理由:
51
+
52
+ - **可审计。** 当一条记录出现在 agent 的上下文里,原因一目了然:词对上了。用 embedding 的话,没人解释得清它为什么在那儿。
53
+ - **成本与失败模式。** 向量索引会过期。用 LLM 抽取记忆很贵,而且正是这类工具里记忆被污染的主要来源。这两样在这里都可以避免,所以就不做。
54
+
55
+ 代价是真实的:关键词匹配会漏掉改写的说法。所以 `decisions_check` 在不带查询条件时会返回一个紧凑的索引,让 agent 先廉价地看到全貌,再往下缩小范围。
56
+
57
+ ### 4. 没有工具时,格式依然可用
58
+
59
+ agent 通过 MCP 调用 `decisions_check`。如果 MCP 不可用,它仍然可以直接读 `.decisions/`——那不过是 Markdown。
60
+
61
+ 这不是理论上的性质。在 Codex 集成测试里,MCP 调用被审批策略拦住了,agent 自动降级去读文件,并且**依然遵守了那条决策**。
62
+
63
+ ## 形态:四层,不是一个
64
+
65
+ 这个品类里一个常见的错误是选一种形态,然后把所有事都塞进去。这里的每件事属于不同的层:
66
+
67
+ | 层 | 形态 | 为什么必须是这个 |
68
+ |---|---|---|
69
+ | 数据 | git 里的 `.decisions/*.md` | 零工具依赖也能用。这是唯一不会死的资产。 |
70
+ | 行为 | `AGENTS.md` 里的一段指令 | 让 agent **主动记录**否决,是指令问题,不是 API 能力问题。 |
71
+ | 能力 | MCP server | 拉取式、结构化、跨 agent 通用。 |
72
+ | 人的回路 | CLI | 续期和归档是人的决定。 |
73
+ | 分发 | npm + agent 插件 | 零安装,在用户已经在的地方出现。 |
74
+ | **不是** | 托管 API | 关于代码库的决策是仓库里最敏感的东西。托管它就等于毁掉整个前提。 |
75
+
76
+ ## 跨平台
77
+
78
+ 目标平台是 Windows、macOS、Linux,因为三者的坑不一样。
79
+
80
+ - **换行符。** `init` 会往 `.gitattributes` 写 `*.md text eol=lf`。不写的话,Windows 上会写入 CRLF,每次 diff 都显示整个文件被改。
81
+ - **文件名。** Windows 禁止 `\ / : * ? " < > |`,而且 Windows 和 macOS 大小写不敏感。文件名会被 slug 成 `[a-z0-9]` 加中日韩字符,从根上消除一整类「Linux 上好的、换个系统就崩」的问题。
82
+ - **没有常驻进程。** Windows 的文件监听不可靠且需要原生依赖。工具只被调用,不常驻。
83
+ - **不用 git hooks。** 跨平台的 shell 在 hook 里是雷区。所以不用。
84
+
85
+ ## 刻意不做的东西
86
+
87
+ - 向量检索 / embedding
88
+ - 写入路径上的任何模型调用
89
+ - 守护进程或文件监听
90
+ - Web UI
91
+ - 自动检测否决(以后可以用 hook,但核心闭环不依赖它)
92
+ - 团队同步。`git` 本身就是。
93
+ - 多仓库聚合
94
+
95
+ 每一条都是真的有人要过的功能。每一条也都会让工具更重,而这个品类的坟场里全是更重的工具。
96
+
97
+ ## 还没有答案的问题
98
+
99
+ - **真实使用中,重复犯错率真的会降吗?** 实验测的是单轮行为。真正重要的主张是长期的,而那个还没测。
100
+ - **过期多久会丢掉人还需要的信息?** 这是一个刻意的取舍,但比例未知。`whynot stale` 是它显现的地方。
101
+ - **团队用它撑得住吗?** 设计假设是一个人维护。五个人时,谁负责续期?
102
+ - **否决类 90 天合适吗?** 这是猜的。不同代码库的节奏不一样。
@@ -0,0 +1,169 @@
1
+ # E1 — Does real code context change the effect?
2
+
3
+ **Result: it depends on the model, and the pooled number hides that. GPT
4
+ replaces the decision with the code; DeepSeek does not.**
5
+
6
+ This is the experiment that most threatens the project's claim, and the result
7
+ is genuinely mixed. It also produces the sharpest statement of what the tool is
8
+ actually for.
9
+
10
+ ---
11
+
12
+ ## Why this experiment exists
13
+
14
+ Every earlier experiment gave the model a task with no code. That leaves an
15
+ obvious alternative explanation:
16
+
17
+ > The model obeyed the decision only because it had nothing else to reason from.
18
+
19
+ If true, the public claim would have to change from "agents blindly obey stale
20
+ decisions" to "agents obey any given constraint when starved of context".
21
+
22
+ ---
23
+
24
+ ## Design
25
+
26
+ A real Node/Express project (`experiments/e1/fixture/`, 19 files, ~410 lines)
27
+ with conventions that matter: Postgres for all app data, service layer takes
28
+ `tenantId` first, `REDIS_URL` declared in config but unused, README notes that
29
+ Redis was being evaluated.
30
+
31
+ Four conditions, n=12 per model per cell:
32
+
33
+ | | Code context | Decision |
34
+ |---|---|---|
35
+ | C0 | no | no |
36
+ | C1 | no | yes |
37
+ | C2 | yes | no |
38
+ | C3 | yes | yes |
39
+
40
+ Task: add session storage to this project, 100k DAU, two regions.
41
+
42
+ Models: GPT-6.1-Sol, DeepSeek V4.1 Flash. Judge: Claude Haiku (third family).
43
+ **96 responses, 0 judging errors.**
44
+
45
+ The decision was injected through the real `decisions_check` tool; the script
46
+ aborts if the returned context does not contain the entry.
47
+
48
+ ---
49
+
50
+ ## Results
51
+
52
+ | Condition | GPT | DeepSeek | Pooled |
53
+ |---|---|---|---|
54
+ | C0 no code, no decision | 10/12 | 12/12 | **22/24 (92%)** |
55
+ | C1 no code + decision | 0/12 | 0/12 | **0/24 (0%)** |
56
+ | C2 code, no decision | **0/12** | **10/12** | **10/24 (42%)** |
57
+ | C3 code + decision | 0/12 | 0/12 | **0/24 (0%)** |
58
+
59
+ Derived effects:
60
+
61
+ | Effect | Size |
62
+ |---|---|
63
+ | Decision, without code (C1 − C0) | −92 pts |
64
+ | Decision, with code (C3 − C2) | −42 pts |
65
+ | Code alone, no decision (C2 − C0) | −50 pts |
66
+
67
+ The pooled H3 test — "does the decision still work with code?" — reads as
68
+ **0 pts change** and the automated verdict said "H3 not supported".
69
+
70
+ **That verdict is wrong, because the two models do opposite things.**
71
+
72
+ ---
73
+
74
+ ## The split
75
+
76
+ ### GPT: the code replaces the decision
77
+
78
+ With code and no decision, GPT already refuses Redis in 12 of 12 runs. It says
79
+ so explicitly:
80
+
81
+ > "现有 `REDIS_URL` 只是历史配置,不应成为选用 Redis 的理由。"
82
+ > *(The existing REDIS_URL is just historical config; it is not a reason to pick Redis.)*
83
+
84
+ > "现有项目的数据已经全部放在 PostgreSQL,增加 session 表能直接复用连接池、备份和运维体系。暂时不引入 Redis。"
85
+
86
+ Adding the recorded decision changes nothing (0/12 → 0/12). For GPT, **the code
87
+ is sufficient**; the decision is redundant in this case.
88
+
89
+ ### DeepSeek: the code is not enough
90
+
91
+ DeepSeek reads the same code and still recommends Redis in 10 of 12 runs. Only
92
+ when the decision is present does it stop (0/12).
93
+
94
+ For DeepSeek, the decision remains **decisive even with the code in front of it**.
95
+
96
+ ---
97
+
98
+ ## What this actually says
99
+
100
+ The pooled framing ("the effect survives code context") is technically what the
101
+ numbers say and substantively wrong. The honest statement:
102
+
103
+ > Whether a recorded decision matters depends on whether the model uses the code.
104
+ > For a model that reads the repository and infers its conventions, a decision
105
+ > that the code already implies is redundant. For a model that does not, the
106
+ > decision is the only thing that stops it.
107
+
108
+ ### And it sharpens the product thesis
109
+
110
+ The fixture's code makes "use Postgres" obvious — that is what code does, it
111
+ encodes the choices that were made. **What code cannot encode is the
112
+ alternative that was rejected.** Nothing in the repository says "we tried Redis
113
+ and it failed at 8 seconds", because a rejected option leaves no artifact.
114
+
115
+ That is exactly the gap the tool fills, and E1 shows it concretely:
116
+
117
+ - GPT inferred the chosen option from the code, so the decision added nothing.
118
+ - Neither model could infer the **rejection** from the code, and for DeepSeek
119
+ that mattered at 10/12.
120
+
121
+ So the correct positioning is narrower and stronger than the original:
122
+
123
+ > Don't record decisions the code already implies. Record the alternatives that
124
+ > were rejected, because nothing else in the repository remembers them.
125
+
126
+ ---
127
+
128
+ ## What to change in the public claim
129
+
130
+ The README should not say "agents blindly obey stale decisions" without
131
+ qualification. The defensible version:
132
+
133
+ > A recorded rejection changes what an agent proposes by 42–92 percentage
134
+ > points, depending on the model and on whether the repository already implies
135
+ > the answer. Models that read the code can infer what was chosen; none of them
136
+ > can infer what was rejected.
137
+
138
+ ---
139
+
140
+ ## Caveats
141
+
142
+ - Two models. The split may look different on Claude or on a larger DeepSeek.
143
+ - One fixture, written by us, with deliberately strong conventions. A messier
144
+ repo might not substitute for the decision as cleanly.
145
+ - n=12 per cell. The GPT/DeepSeek split is stark (0/12 vs 10/12), but the exact
146
+ rates should not be treated as population estimates.
147
+ - The task is still synthetic. E5 (multi-turn with a real agent) is the next
148
+ step for external validity.
149
+
150
+ ---
151
+
152
+ ## A reporting lesson
153
+
154
+ The script printed "H3 NOT SUPPORTED — the effect survives code context" based
155
+ on pooled rates. The per-model table shows the opposite pattern for one model.
156
+ **Pooling across models hid a sign flip in the mechanism.**
157
+
158
+ Rule for the remaining experiments: when the per-model cells disagree in
159
+ direction, the pooled number is not a summary — it is a fiction. Report the
160
+ split and say so.
161
+
162
+ ---
163
+
164
+ ## Reproduce
165
+
166
+ ```bash
167
+ node experiments/e1/build-fixture.mjs
168
+ node experiments/e1/run.mjs
169
+ ```
@@ -0,0 +1,167 @@
1
+ # E2 — False obedience: does an irrelevant decision distort an unrelated answer?
2
+
3
+ **Result: no. The measured 30% was a metric artefact; actual false obedience is
4
+ 0 out of 30.**
5
+
6
+ This experiment also produced a lesson about metric definition that is more
7
+ useful than the result itself.
8
+
9
+ ---
10
+
11
+ ## Why this experiment exists
12
+
13
+ Every other experiment measures one failure: the agent forgets something it
14
+ should have remembered. This one measures the opposite: the agent applies a
15
+ decision it should have ignored.
16
+
17
+ If the tool's whole premise is "record what you ruled out", the obvious risk is
18
+ that a growing pile of decisions starts distorting unrelated work. That risk had
19
+ never been tested.
20
+
21
+ ---
22
+
23
+ ## Design
24
+
25
+ **Task:** design a PostgreSQL `users` table, full DDL. This task has nothing to
26
+ do with GraphQL.
27
+
28
+ | Condition | Injected decision | Expected behaviour |
29
+ |---|---|---|
30
+ | N0 | none | baseline |
31
+ | **N1** | "never use GraphQL anywhere" — strongly worded, irrelevant | should be ignored |
32
+ | N2 | "passwords must not be stored in plaintext" — relevant | should change the design |
33
+
34
+ Models: `codex/gpt-6.1-sol`, `opencode-go/deepseek-v4-pro`. n=15 per condition.
35
+
36
+ The decision was injected as text in the system prompt, with the same
37
+ `AGENTS.md` instruction used elsewhere.
38
+
39
+ ---
40
+
41
+ ## Result 1: the pre-registered metric said FAIL
42
+
43
+ The metric fixed in advance was **"does the response mention GraphQL at all"**,
44
+ on the reasoning that an irrelevant decision should leave no trace.
45
+
46
+ | Model | N0 mentions | N1 mentions |
47
+ |---|---|---|
48
+ | GPT-6.1-Sol | 0/15 | **0/15** |
49
+ | DeepSeek V4 Pro | 0/15 | **9/15** |
50
+ | **Total** | **0/30** | **9/30 = 30%** |
51
+
52
+ Under the pre-registered threshold (5–15% warn, >15% fail), this is a **FAIL —
53
+ design defect**.
54
+
55
+ ---
56
+
57
+ ## Result 2: manual inspection shows the metric was wrong
58
+
59
+ All nine DeepSeek responses that "mentioned GraphQL" did so in one specific way:
60
+
61
+ > "已按现有决策检查:`0001 不使用 GraphQL` **仅约 API,不涉及本表设计**。"
62
+ > *(Checked the recorded decision: 0001 only concerns the API, not this table.)*
63
+
64
+ > "已检查 `.decisions/`:0001 仅限制不使用 GraphQL,**与本表设计无关**。"
65
+ > *(0001 only restricts GraphQL, which is unrelated to this table design.)*
66
+
67
+ > "已检查仓库决策 `0001`(不使用 GraphQL),**与本数据库表设计无冲突**;以下方案只涉及 PostgreSQL DDL。"
68
+
69
+ The one response the regex flagged as "treating it as a constraint" says:
70
+
71
+ > "0001 不使用 GraphQL,与数据库设计无关。**所以不影响**。"
72
+
73
+ **Every one of these is correct relevance judgement, stated out loud, followed
74
+ by a normal design.** The model read the decision, decided it did not apply, and
75
+ said so. That is exactly what the tool should produce.
76
+
77
+ The metric "mentions GraphQL" conflates two opposite behaviours:
78
+
79
+ - considering the decision and correctly dismissing it (good)
80
+ - being distorted by the decision (bad)
81
+
82
+ Only the second is false obedience, and it occurred **0 times in 30 runs**.
83
+
84
+ ---
85
+
86
+ ## Result 3: the original positive control failed
87
+
88
+ The N2 condition was meant to prove the model reads decisions at all. It did
89
+ not work: a competent schema hashes passwords anyway.
90
+
91
+ | Model | N0 hashes | N2 hashes |
92
+ |---|---|---|
93
+ | GPT | 15/15 | 15/15 |
94
+ | DeepSeek | 15/15 | 15/15 |
95
+
96
+ No difference. So E2 on its own could not distinguish "correctly ignored an
97
+ irrelevant decision" from "never read any decision".
98
+
99
+ ### E2b — a control that actually discriminates
100
+
101
+ A decision a model would *not* choose by default:
102
+
103
+ > **users 表主键必须使用 `CHAR(26)` ULID。禁止 SERIAL / BIGSERIAL / IDENTITY,也禁止 UUID。**
104
+
105
+ | Model | N0 uses ULID | N3 uses ULID | N0 uses SERIAL | N3 uses SERIAL |
106
+ |---|---|---|---|---|
107
+ | GPT-6.1-Sol | 0/12 | **12/12** | 12/12 | **0/12** |
108
+ | Claude Haiku 5.5 | 0/12 | **12/12** | 12/12 | 2/12 |
109
+
110
+ **24/24 = 100% followed the ULID constraint.** Both models default to `SERIAL`
111
+ and both completely switch when told not to. The models demonstrably read
112
+ decisions.
113
+
114
+ Therefore E2's zero false-obedience rate is meaningful: the model read the
115
+ GraphQL decision, judged it irrelevant, said so, and was not distorted by it.
116
+
117
+ ---
118
+
119
+ ## Honest note on changing the metric
120
+
121
+ The pre-registration said "false-obedience rate" without defining whether
122
+ "mention" or "distortion" counted. That was an ambiguity in the registration,
123
+ not a licence to pick whichever number looks better afterwards.
124
+
125
+ Both numbers are reported here:
126
+
127
+ - **Pre-registered metric (mentions): 30% — FAIL by the stated threshold.**
128
+ - **Substantive metric (design distorted): 0/30 — PASS.**
129
+
130
+ The second is the one that answers the question the experiment was designed to
131
+ ask, and the manual inspection above is the evidence for redefining it. If a
132
+ reader prefers the stricter metric, the correct conclusion becomes "the tool
133
+ causes the model to talk about irrelevant decisions", which is a style issue,
134
+ not a correctness defect.
135
+
136
+ **Recommendation for future runs:** pre-register the exact string or judge
137
+ rubric, not just the concept.
138
+
139
+ ---
140
+
141
+ ## What this changes
142
+
143
+ - The tool does not appear to cause harmful over-application.
144
+ - DeepSeek **volunteers** that an irrelevant decision does not apply; GPT
145
+ silently ignores it. Both are acceptable, but the visible acknowledgement is
146
+ arguably better for a human reviewing the session.
147
+ - The effect is worth re-checking on a repo with many decisions, where the
148
+ cumulative distraction may be larger than with one.
149
+
150
+ ---
151
+
152
+ ## Caveats
153
+
154
+ - n=15 per cell, two models.
155
+ - One irrelevant decision. A repo with 50 of them may behave differently.
156
+ - The task is synthetic and short.
157
+ - E2b used a different task prompt and model set than E2; it validates the
158
+ *mechanism*, not the exact effect size.
159
+
160
+ ---
161
+
162
+ ## Reproduce
163
+
164
+ ```bash
165
+ node experiments/e2/run.mjs # E2, 90 calls
166
+ node experiments/e2/run-b.mjs # E2b, 48 calls
167
+ ```