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/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 +246 -2
- package/README.zh-CN.md +204 -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 +64 -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
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
|
+
```
|