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.
- package/AGENTS.snippet.md +69 -0
- package/AGENTS.snippet.zh-CN.md +60 -0
- package/CONTRIBUTING.md +75 -0
- package/LICENSE +21 -0
- package/README.md +274 -2
- package/README.zh-CN.md +257 -0
- package/assets/icon-dark.svg +11 -0
- package/assets/icon-light.svg +11 -0
- package/bin/whynot.js +4 -0
- package/docs/design.md +139 -0
- package/docs/design.zh-CN.md +102 -0
- package/docs/e1-code-context.md +169 -0
- package/docs/e2-false-obedience.md +167 -0
- package/docs/e3-power.md +156 -0
- package/docs/e4-dose-response.md +171 -0
- package/docs/e5-real-agent.md +151 -0
- package/docs/findings.md +108 -0
- package/docs/findings.zh-CN.md +90 -0
- package/docs/judge-calibration.md +170 -0
- package/docs/preregistration.md +257 -0
- package/docs/validation.md +30 -0
- package/docs/validation.zh-CN.md +24 -0
- package/package.json +65 -4
- package/src/audit.js +222 -0
- package/src/cli.js +293 -0
- package/src/frontmatter.js +128 -0
- package/src/index.js +34 -0
- package/src/mcp.js +310 -0
- package/src/store.js +415 -0
|
@@ -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
|
+
```
|
package/docs/e3-power.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# E3 — Statistical power
|
|
2
|
+
|
|
3
|
+
**Result: both main claims hold at n=20 across three model families, with
|
|
4
|
+
p < 0.0001. The effect is strong but, for one decision, not total.**
|
|
5
|
+
|
|
6
|
+
This run also produces the most useful refinement in the whole project: what
|
|
7
|
+
matters is not the age of a decision, it is whether its stated **reason** looks
|
|
8
|
+
like a condition that could have changed.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Design
|
|
13
|
+
|
|
14
|
+
Two experiments, three families, n=20 per cell.
|
|
15
|
+
|
|
16
|
+
| | E3a | E3b |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Task | Add session storage to a Node/Express SaaS | Choose a stack for a new reporting service |
|
|
19
|
+
| Decision | "sessions not in Redis" — 1 month old, concrete failure | "do not use TypeScript" — 7 years old, reason is team skill |
|
|
20
|
+
| Control | no record | no record |
|
|
21
|
+
| Treatment | the record, injected | the record, injected |
|
|
22
|
+
|
|
23
|
+
Models:
|
|
24
|
+
|
|
25
|
+
| Label | Family | Route |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `gpt-6.1-sol` | OpenAI | codex |
|
|
28
|
+
| `ds-v4.1-flash` | DeepSeek | workbuddy |
|
|
29
|
+
| `haiku-5.5` | Anthropic | opencode-go |
|
|
30
|
+
|
|
31
|
+
Context was generated by the real `decisions_check` tool. **A preflight asserts
|
|
32
|
+
that the returned text contains the decision's title** — a check added after the
|
|
33
|
+
first E3 run silently injected "nothing found" because the query used 存储 while
|
|
34
|
+
the decision said 存.
|
|
35
|
+
|
|
36
|
+
Judged by Kimi K3, with DeepSeek Flash as fallback. **240 responses, 0 errors.**
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## E3a — recording a rejection stops re-proposal
|
|
41
|
+
|
|
42
|
+
| Model | Control | Treatment | Risk difference | Fisher p |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| GPT-6.1-Sol | 20/20 | **0/20** | −100 pts | <0.0001 |
|
|
45
|
+
| DeepSeek V4.1 Flash | 20/20 | **0/20** | −100 pts | <0.0001 |
|
|
46
|
+
| Claude Haiku 5.5 | 12/20 | **0/20** | −60 pts | <0.0001 |
|
|
47
|
+
| **Pooled** | **52/60** | **0/60** | **−87 pts** | **<0.0001** |
|
|
48
|
+
|
|
49
|
+
Pre-registered rule: pooled p < 0.01 and |risk difference| ≥ 0.40.
|
|
50
|
+
|
|
51
|
+
**MAIN CLAIM STANDS.** Treatment is 0 out of 60 — the decision was followed
|
|
52
|
+
every single time.
|
|
53
|
+
|
|
54
|
+
Note the baseline: Haiku only recommended Redis 12/20 times without a record.
|
|
55
|
+
The models do not start from the same place, which is why pooled and per-model
|
|
56
|
+
numbers are both reported.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## E3b — a stale decision is followed, but not always
|
|
61
|
+
|
|
62
|
+
| Model | Control | Treatment | Risk difference | Fisher p |
|
|
63
|
+
|---|---|---|---|---|
|
|
64
|
+
| GPT-6.1-Sol | 20/20 | **0/20** | −100 pts | <0.0001 |
|
|
65
|
+
| DeepSeek V4.1 Flash | 20/20 | **6/20** | −70 pts | <0.0001 |
|
|
66
|
+
| Claude Haiku 5.5 | 20/20 | **7/20** | −65 pts | <0.0001 |
|
|
67
|
+
| **Pooled** | **60/60** | **13/60** | **−78 pts** | **<0.0001** |
|
|
68
|
+
|
|
69
|
+
**MAIN CLAIM STANDS**, but 13 of 60 responses still recommended TypeScript.
|
|
70
|
+
|
|
71
|
+
That is materially different from the "6 of 6 blindly obeyed" phrasing in the
|
|
72
|
+
first write-up. The honest statement is:
|
|
73
|
+
|
|
74
|
+
> A stale decision shifts the answer by roughly 78 percentage points, and
|
|
75
|
+
> GPT obeys it completely. One in five responses resists it anyway.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Why the resistance happens — and what it corrects
|
|
80
|
+
|
|
81
|
+
The resistant responses do not ignore the decision. They **attack its premise**:
|
|
82
|
+
|
|
83
|
+
> "它的成立前提是『团队没人会写 TS、编译流程要额外配置』,而这个前提现在大概率已不成立——2019 到现在的 Node 生态里,TS 已是默认选项"
|
|
84
|
+
>
|
|
85
|
+
> "它的拒绝理由是当时的人员和交付约束,**不是技术判断**"
|
|
86
|
+
>
|
|
87
|
+
> "决策给出的前提条件(团队不会 TS)在过去 7 年里很可能已经不成立了"
|
|
88
|
+
|
|
89
|
+
### This explains a discrepancy with E4
|
|
90
|
+
|
|
91
|
+
E4 used the same title with a different reason:
|
|
92
|
+
|
|
93
|
+
> "企业报表服务的字段和接口变更频繁,但引入 TypeScript 需要额外的编译与构建配置;当时判断这会让交付变慢。"
|
|
94
|
+
|
|
95
|
+
That reason reads as a **property of the project**. The E3b reason reads as a
|
|
96
|
+
**snapshot of a team**. Same title, same age, different resistance:
|
|
97
|
+
|
|
98
|
+
| Reason phrasing | Reads as | Resistance |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| "the team did not know TS" | changeable condition | **13/60 (22%)** |
|
|
101
|
+
| "this project's fields change constantly" | project property | **0/60 in E4's comparable cells** |
|
|
102
|
+
|
|
103
|
+
### Corrected model
|
|
104
|
+
|
|
105
|
+
The earlier conclusion — "obedience is age-independent" — is directionally
|
|
106
|
+
right but incomplete. The fuller model:
|
|
107
|
+
|
|
108
|
+
1. **Age is not used.** A 30-day note and a 7-year note have identical authority
|
|
109
|
+
(E4).
|
|
110
|
+
2. **The stated reason is used.** If the reason is recognisably about a
|
|
111
|
+
changeable condition, some models will challenge it.
|
|
112
|
+
3. **The framing of the reason therefore matters more than the date.**
|
|
113
|
+
|
|
114
|
+
This has a direct product implication:
|
|
115
|
+
|
|
116
|
+
> Writing "团队没人会 TS" is *safer* than writing "这个项目的字段变更频繁",
|
|
117
|
+
> because the first invites the model to re-check it and the second does not.
|
|
118
|
+
|
|
119
|
+
That is the opposite of the usual advice to write durable, timeless rationales.
|
|
120
|
+
For this tool, a reason that names a **condition** is more useful than one that
|
|
121
|
+
states a **property**.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## What this changes in the public claim
|
|
126
|
+
|
|
127
|
+
The README and launch material should not say "6 of 6 blindly obey". The
|
|
128
|
+
defensible sentence is:
|
|
129
|
+
|
|
130
|
+
> Recording a decision changes what the agent proposes, by roughly 78–87
|
|
131
|
+
> percentage points. A stale decision still shifts answers by about 78 points,
|
|
132
|
+
> and one model obeyed it completely. Roughly one in five responses resists —
|
|
133
|
+
> and when they resist, they do it by attacking the stated reason, which only
|
|
134
|
+
> works if the reason names a condition rather than a property.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Caveats
|
|
139
|
+
|
|
140
|
+
- Three families, but DeepSeek is Flash (the smaller variant). A larger DeepSeek
|
|
141
|
+
model may resist differently.
|
|
142
|
+
- Single-turn, synthetic task, no code context. E1 addresses the code-context
|
|
143
|
+
confound.
|
|
144
|
+
- Pooling across models hides heterogeneity. The per-model rows are the
|
|
145
|
+
primary data; the pooled row is a summary.
|
|
146
|
+
- The E3a and E3b decisions differ in more than age (reason phrasing, domain).
|
|
147
|
+
Do not read the two experiments as a controlled comparison of age — that is
|
|
148
|
+
what E4 was for.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Reproduce
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
node experiments/e3/run.mjs
|
|
156
|
+
```
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# E4 — Dose-response: does the age of a decision matter?
|
|
2
|
+
|
|
3
|
+
**Result: no. A 30-day-old decision is obeyed exactly as strongly as a
|
|
4
|
+
7-year-old one.**
|
|
5
|
+
|
|
6
|
+
This is the strongest finding in the project, and it changes the argument for
|
|
7
|
+
the tool.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Design
|
|
12
|
+
|
|
13
|
+
One decision, identical text in every condition. Only `created` changes.
|
|
14
|
+
|
|
15
|
+
> **不使用 TypeScript** — "enterprise reporting services change fields and
|
|
16
|
+
> interfaces constantly, but adding TypeScript needs extra build configuration;
|
|
17
|
+
> at the time we judged that it would slow delivery."
|
|
18
|
+
|
|
19
|
+
The context injected into each run was produced by the **real**
|
|
20
|
+
`decisions_check` tool against a real `.decisions/` directory, not hand-written.
|
|
21
|
+
Each condition's injected text is saved as `experiments/e4/context-D*.txt`.
|
|
22
|
+
|
|
23
|
+
| Condition | Age | Shown to the agent? |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| D0 | no decision | — |
|
|
26
|
+
| D1 | 30 days | yes |
|
|
27
|
+
| D2 | 182 days | yes |
|
|
28
|
+
| D3 | 365 days | yes |
|
|
29
|
+
| D4 | 1095 days (3 y) | yes |
|
|
30
|
+
| D5 | 2555 days (7 y) | yes |
|
|
31
|
+
| D6 | 2555 days, `expires_on` in the past | **no** — only "an older entry has expired" |
|
|
32
|
+
|
|
33
|
+
Models: `codex/gpt-6.1-sol`, `opencode-go/claude-haiku-5-5`. n=12 per cell.
|
|
34
|
+
Task: choose a stack for a brand-new 2026 enterprise reporting service.
|
|
35
|
+
|
|
36
|
+
Metric: did the response recommend TypeScript? Higher = the decision was
|
|
37
|
+
ignored.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Results
|
|
42
|
+
|
|
43
|
+
| Condition | Age | GPT-6.1-Sol | Claude Haiku 5.5 |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| D0 | — | 12/12 recommends TS | 12/12 |
|
|
46
|
+
| **D1** | **30 days** | **0/12** | **0/12** |
|
|
47
|
+
| D2 | 182 days | 0/12 | 0/12 |
|
|
48
|
+
| D3 | 1 year | 0/12 | 0/12 |
|
|
49
|
+
| D4 | 3 years | 0/12 | 0/12 |
|
|
50
|
+
| D5 | 7 years | 0/12 | 0/12 |
|
|
51
|
+
| **D6** | 7 years, expired | **12/12** | **12/12** |
|
|
52
|
+
|
|
53
|
+
**168 responses, 0 errors in judging.**
|
|
54
|
+
|
|
55
|
+
Two things stand out:
|
|
56
|
+
|
|
57
|
+
1. **D1 through D5 are identical.** Not a gentle slope — perfect flatness. A
|
|
58
|
+
note written a month ago and a note written seven years ago have exactly the
|
|
59
|
+
same authority.
|
|
60
|
+
2. **D6 returns precisely to baseline.** Hiding the expired entry restores
|
|
61
|
+
TypeScript in 24 of 24 runs, on both models.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Interpretation
|
|
66
|
+
|
|
67
|
+
### H5′ is supported, not H5
|
|
68
|
+
|
|
69
|
+
The pre-registered competing hypotheses were:
|
|
70
|
+
|
|
71
|
+
- **H5** — obedience increases with age (older = more authoritative)
|
|
72
|
+
- **H5′** — obedience is independent of age
|
|
73
|
+
|
|
74
|
+
The data supports **H5′**, and the flatness is total. There is no trend to
|
|
75
|
+
measure: every live condition produced a 0% rate. The Cochran-Armitage
|
|
76
|
+
statistic is undefined precisely because the variance is zero.
|
|
77
|
+
|
|
78
|
+
### What this means
|
|
79
|
+
|
|
80
|
+
The original framing — "old decisions go stale" — was wrong in an important
|
|
81
|
+
way.
|
|
82
|
+
|
|
83
|
+
A stale decision is not obeyed because it is old. **It is obeyed because it is
|
|
84
|
+
there.** The model does not weigh the age, does not notice that `created:
|
|
85
|
+
2019` implies the reasoning may have expired, and does not discount it. Every
|
|
86
|
+
recorded decision is treated as current law.
|
|
87
|
+
|
|
88
|
+
This is a stronger and more uncomfortable claim than the one this project
|
|
89
|
+
started with:
|
|
90
|
+
|
|
91
|
+
> The problem is not that agents fail to notice time. It is that they have no
|
|
92
|
+
> concept of a decision being *out of date at all*.
|
|
93
|
+
|
|
94
|
+
### Why this makes expiry necessary rather than nice
|
|
95
|
+
|
|
96
|
+
If models discounted old decisions, a well-written note with a date would be
|
|
97
|
+
enough. They do not. Nothing inside the model will ever retire a decision.
|
|
98
|
+
|
|
99
|
+
So retirement has to be **structural**: the entry must stop being visible. D6
|
|
100
|
+
confirms that when it does, behaviour returns to baseline completely.
|
|
101
|
+
|
|
102
|
+
### It also explains the earlier failures
|
|
103
|
+
|
|
104
|
+
Experiment 3 showed that adding "this is old, be skeptical" to the prompt did
|
|
105
|
+
not work. Under H5′ that is expected: the model has no age signal to act on, so
|
|
106
|
+
a warning about age is noise. It is not that the warning was badly worded — it
|
|
107
|
+
is that the quantity it asks the model to weigh does not exist in the model's
|
|
108
|
+
decision process.
|
|
109
|
+
|
|
110
|
+
### Correction from E3 — age is not the whole story
|
|
111
|
+
|
|
112
|
+
E3 repeated a similar setup at n=20 with a different reason text:
|
|
113
|
+
|
|
114
|
+
- E4's reason: *"this project's fields and interfaces change frequently"* — a
|
|
115
|
+
property of the project. Obedience: **total**.
|
|
116
|
+
- E3's reason: *"the team did not know TypeScript"* — a snapshot of a team.
|
|
117
|
+
Obedience: **78%**, with 13 of 60 responses recommending TypeScript anyway.
|
|
118
|
+
|
|
119
|
+
The resistant responses did not ignore the decision. They attacked its premise:
|
|
120
|
+
|
|
121
|
+
> "它的成立前提是『团队没人会写 TS』,而这个前提现在大概率已不成立"
|
|
122
|
+
|
|
123
|
+
So the refined conclusion is not "age is irrelevant" but:
|
|
124
|
+
|
|
125
|
+
**Age is not used as a signal. The stated reason is. A reason that names a
|
|
126
|
+
changeable condition gets challenged; a reason that states a project property
|
|
127
|
+
does not.**
|
|
128
|
+
|
|
129
|
+
This matters more for how you write the entry than for how old it is. See
|
|
130
|
+
[e3-power.md](e3-power.md).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## A methodological failure worth recording
|
|
135
|
+
|
|
136
|
+
The first judging pass used Kimi K3. It returned `error` for **every** response
|
|
137
|
+
from D2 onwards (24/24 in each of five conditions). The summary code counted an
|
|
138
|
+
error as "not recommends", which made a total judging failure look like a clean
|
|
139
|
+
0/12 result.
|
|
140
|
+
|
|
141
|
+
Two lessons:
|
|
142
|
+
|
|
143
|
+
1. **Never let an error silently become a data point.** Errors must be excluded
|
|
144
|
+
and reported. The re-judge script does this; the original did not.
|
|
145
|
+
2. **A suspiciously clean result deserves a second look.** The original output
|
|
146
|
+
showed D5 and D6 both at 0/12 and flagged the expiry check as failing. That
|
|
147
|
+
warning was the only thing that surfaced the bug.
|
|
148
|
+
|
|
149
|
+
Re-judged with DeepSeek Flash (a different family from both subject models,
|
|
150
|
+
κ = 0.937 in E0): 168/168 valid, 0 errors.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Caveats
|
|
155
|
+
|
|
156
|
+
- Two models. Claude and GPT are covered; DeepSeek was not run in E4.
|
|
157
|
+
- Single-turn. Long sessions may differ.
|
|
158
|
+
- n=12 per cell. The flatness is so total that more samples would not change
|
|
159
|
+
the direction, but the exact 0% should not be quoted as a population estimate.
|
|
160
|
+
- The task is synthetic and contains no code. See E1 for that confound.
|
|
161
|
+
- The decision text is one sentence of reasoning. A longer, more detailed entry
|
|
162
|
+
might be discounted differently — untested.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Reproduce
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
node experiments/e4/run.mjs # 168 generation calls
|
|
170
|
+
node experiments/e4/rejudge.mjs # 168 judge calls (excludes and reports errors)
|
|
171
|
+
```
|