pi-multi-viewers 0.2.2 → 0.4.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.
@@ -0,0 +1,374 @@
1
+ <!-- 存档:docs/reviews/2026-09-12-e2e17-thinking-level-analysis.md
2
+ 来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
3
+ 分析场次目录已随 --cleanup 删除;文中的消息编号(如 `效率/0003`)不可再核验,
4
+ 仅作溯源线索(与代码注释引用约定一致:行为以自描述为准)。 -->
5
+
6
+ # 存档说明
7
+
8
+ - **主题**:thinking 档位降到 high 对讨论速度与质量的影响(含 d=f×S 成本模型)
9
+ - **场次**:`mv-mv-main-20260912-163730`(3 视角;真实 pi 讨论)
10
+ - **抓到的问题**:把 thinking 从 max 降到 high 只有 −3.6% 墙钟(上界 −23%);质量**无任何判据**(全仓无质量落点)→ 真正的杠杆是**请求数**(每请求固定成本占 47%)与 provider 失败重试(11–19% 墙钟);报告缺少按谓词分组的字段,spec 的 variant 槽默认值有歧义
11
+ - **落地**:`3c49d90`(报告加 usage_totals / requests_by_stopReason / seconds_by_stopReason / effective_levels + 档位对照行 + spec 恒显式 variant + fork 源剔除旧档位条目)
12
+
13
+ ---
14
+
15
+ # 分析结论:把 thinking-level 降到 `high`,对讨论速度与质量有什么帮助
16
+
17
+ **分析形式**:3 视角协同分析(性能 / 简单 / 铁律),13 条消息,2026-09-12。
18
+ **分析对象**:主 pi 把 thinking level 从 `max` 调到 `high` 之后,多视角讨论
19
+ (pi-multi-viewers)的速度与质量会有什么变化。
20
+ **结论强度**:速度有实测量级 + 硬上界;质量为「无判据」;并顺带量出**本轮
21
+ 真正的最大成本项**(此前零记录)。
22
+
23
+ ---
24
+
25
+ ## 一句话答案
26
+
27
+ > **速度:实测约 −3.6%/轮(44 分钟讨论省约 1.5 分钟),代数天花板 −23%,
28
+ > 而"最差的那次等待"(p90)连天花板情形也只降 4.7%——因为它由 provider
29
+ > 失败等待造成,与生成量无关。质量:无判据(两边都未测量)。它是本次涉及的
30
+ > 所有杠杆里最小的一个;第一位是"请求数",而最大的那笔账是 provider 失败
31
+ > 重试(11–19%,当前报告里根本没有字段)。**
32
+
33
+ ---
34
+
35
+ ## 一、前提核对:这次"降级"是真实且孤立的(已确证)
36
+
37
+ | 时间(UTC) | 事件 |
38
+ |---|---|
39
+ | 2026-09-11T07:30:13Z | `thinking_level_change → max` |
40
+ | 2026-09-11T08:17:39Z | `model_change → commandcode-goat/…/deepseek-v4.1-flash` |
41
+ | **2026-09-12T08:35:17Z** | **`thinking_level_change → high`**(`settings.json` mtime 同秒) |
42
+ | 16:36:29(本地) | 本次 spec 生成(`models.md` 写成 `, high`) |
43
+
44
+ - **模型换代与级别变更相隔约 25 小时 → 是两个独立事件**;级别变更走全局
45
+ `defaultThinkingLevel`(**不是**那个永不命中的 per-model 覆盖)。**"降级"真实发生。**
46
+ - **更稳的表述**:结论不依赖"改的是哪个键"——事件线已足够给出"**2026-09-12
47
+ 08:35 起 v4.1-flash 的生效级别为 `high`,且此前不是**"。
48
+ - **推论(重要)**:**e2e14 的 44 分钟基线不能当 `max` 臂**(模型+级别双混杂)
49
+ → 要做 A/B 必须新跑,不能复用旧数据。
50
+ - 过程中修正:曾按会话切片显示误读为"相隔 18 分钟"(性能自纠);由该错误
51
+ 推出的"零成本路径已试尽"(简单)也一并作废。
52
+
53
+ ---
54
+
55
+ ## 二、速度:实测量级 + 硬上界(本节是本题的核心答案)
56
+
57
+ **`f` 的定义与实测**:`f` = high 相对 max 少掉的推理 token 比例。
58
+
59
+ | 档 | n | `reasoning ~ output` 斜率 | R² |
60
+ |---|---|---|---|
61
+ | max(v4.1-flash,约 25 小时开发工作) | 983 | **0.775 ± 0.012**(\|t\|=64.9) | 0.811 |
62
+ | high(本讨论自身) | 127 | **0.675 ± 0.024**(\|t\|=28.1) | 0.863 |
63
+ | 差 | — | **0.100 ± 0.027 → \|t\|=3.71(显著)** | — |
64
+
65
+ → **`f ≈ 0.13`,95% CI [0.07, 0.19]**。**统计显著,但因果不干净**(max 侧是
66
+ 一整天开发工作、high 侧是本讨论 → 任务构成混杂);更早两个估计(0.44、≈0)
67
+ 均由样本/混杂问题作废。
68
+
69
+ **逐响应扣减 → 唤醒求和 → max-of-3(本讨论快照,n=145 响应 / 18 唤醒)**:
70
+
71
+ | 情形 | 每轮 mean | mean 变化 | 每轮 p90 | **p90 变化** |
72
+ |---|---|---|---|---|
73
+ | 现状 | 195.5s | — | 402.6s | — |
74
+ | **f=0.13(实测)** | 188.5s | **−3.6%** | 400.2s | **−0.6%** |
75
+ | f=0.50(假设) | 171.9s | −12.1% | 393.2s | −2.3% |
76
+ | **f=1.00(代数上界)** | 150.3s | **−23.1%** | 383.8s | **−4.7%** |
77
+
78
+ **三条带界的结论**:
79
+
80
+ 1. **实测值:每轮 −3.6%**(44 分钟讨论 → 省约 **1.5 分钟**)。
81
+ 2. **绝对上界(把推理全部消掉,物理上不可能):mean −23.1%** →
82
+ **"降 high 最多能帮多少"有硬天花板:永远不超过约四分之一。**
83
+ 3. **p90 即便 f=1 也只 −4.7%**(实测 f 下 −0.6%)→
84
+ **"修不了最差等待"不是估计,是被上界封死的结论**。
85
+
86
+ **为什么尾部治不了(机制)**:最大的两个唤醒里 **49% 与 64% 是 provider
87
+ 失败等待**;扣掉后一个降到预测的 1.46×(token 模型基本解释得了),另一个仍有
88
+ **4.2× 残余**(该唤醒 error=0 → 是**非失败慢**)。
89
+ → **尾因 = 失败等待(主) + 非失败慢(残余),两者都与生成量无关。**
90
+ (正确表述是"**尾部在样本内、却不由 token 解释**",而不是"回归没看尾部"。)
91
+
92
+ **结论对 `f` 不敏感**:`f ∈ [0.07, 0.19]` → 每轮 −2%~−4.5%;即便真值取 0.3,
93
+ 也只 −6.3% → **排序不变**。所以"精确测 `f`"不改变任何行动 → 微实验**可选**。
94
+
95
+ ---
96
+
97
+ ## 三、质量:**无判据**(并说明为什么不能写成"无提升")
98
+
99
+ - 全仓**没有任何质量落点**(无评分/通过率/评审结果聚合);现有讨论记录只有
100
+ 消息条数与引用关系,无法判定内容好坏。
101
+ - 与质量最相关的一个已知机制是:`thinking=max` 可能**推理挤满输出预算 → 正文
102
+ 零产出**(另一次开发机诊断的先例)。**本例中该机制不成立**:本模型 ×
103
+ 本 maxTokens(393,216)× 本档位下,619 条 `stopReason` 全分布为
104
+ `toolUse 548 | stop 65 | error 30 | None 6` → **零截断信号**;最大 output
105
+ 10,299 vs 上限 393,216(38 倍余量)。**注意作用域**:该结论**不可跨配置外推**
106
+ (换小上限模型/换 provider 无依据)。
107
+ - **因此**:不得写"无提升"(缺席 ≠ 0),也不得写"有提升"。
108
+ `f` 只有 13%(推理 token 差异本来就小)是唯一间接信号。
109
+ - **假收敛风险存在但只能作信号**:更多 freezing/pass → 轮次变少 → 墙钟变小,
110
+ 所以"省下来的时间"里有一部分是**"少检验"买来的**。但 `pass` 比升高也可能是
111
+ 真收敛(**假阳性**),且报告**不得升级为验收 gate** → 报告里只能标为**信号**。
112
+
113
+ ---
114
+
115
+ ## 四、杠杆排序与"真正的账"
116
+
117
+ | 排序 | 杠杆 | 量级 | 现状 |
118
+ |---|---|---|---|
119
+ | **1** | **请求数**(工具往返/轮次) | 每请求非生成成分 **5.26–7.44s**(视总体);本讨论 `mean(Δt)=15.77s` 时占 **47%** | 协议/prompt 层可动 |
120
+ | **2** | **provider 失败重试** | **11%–19%**(e2e13 的 11% + 本场最新 **19.4%** = 725s/3734s,n=7,最长单条 130.6s) | **报告零字段** |
121
+ | **3** | **thinking 级别** | **−3.6%**(上界 −23%;p90 上界 −4.7%) | 已有字段(`--thinking`) |
122
+
123
+ - **可加性已定案**:回归样本里 **error 是唯一被排除的类别**(5/171)→ 截距
124
+ **不含**失败等待 → **"减少请求"与"减少重试"两个杠杆不重叠、可加**(此前
125
+ 担心的重复计算不成立,且有实测支撑)。
126
+ - **成本不是杠杆**:thinking ≈ **$0.02–0.03/讨论**,输入侧 ≈ **$0.9/讨论** →
127
+ 降 thinking **对成本几乎无感(<3%)**。**不要用降本来论证它,也不要用
128
+ "省不了钱"来反对它。**
129
+ - **自指观察**:本文这场讨论自己就有约 **1/5** 的 LLM 时间花在 provider 失败上。
130
+
131
+ ---
132
+
133
+ ## 五、机制事实(已实测 / 已代码确认)
134
+
135
+ 1. **链路**:主 pi 的 `PI_MODEL`/`PI_REASONING_LEVEL` → `--prepare` 时
136
+ `_detect_pi_model_thinking`(`spec_gen.py:80-114`)快照进 `spec/models.md`
137
+ → 每个 agent 的 `pi-agent.json` → **每次唤醒**由 `meeting_loop.py:320` 拼
138
+ `--thinking <level>`。**级别在 `--prepare` 时固化**。
139
+ 2. **粒度**:`models.md` **逐 agent 一行**——所以"按视角分级"是**机制上已支持**
140
+ 的形态(但**策略**无依据,见七.5)。
141
+ 3. **`reasoning ⊂ output`**(thinking 计入 output,**不能相加**):
142
+ `input + cacheRead + output == totalTokens` 成立,加 reasoning 则不成立。
143
+ 占比:主会话全样本 52%、本讨论主导类 **63.7%**、本讨论长消息 **79–83%**。
144
+ 4. **单次时长分解(本讨论 n=166 快照)**:`Δt = 7.44 + 0.00471 × output`
145
+ (2 变量;`mean(Δt)=15.77s`)→ 时长 = **每请求非生成成分** + 生成量/速率。
146
+ 5. **cached context ≈ 免费**(每 100k 的 95% 置信上界 **−0.3s**);
147
+ **未缓存 input 有代价**(0.019 ms/tok → 90k ≈ **+1.6~1.9s**)。
148
+ → "context 不是杠杆"必须限定为"**命中部分不是,未命中部分是**"。
149
+ 6. **会话启动会写 `model_change` / `thinking_level_change` 事件**
150
+ (`session, model_change, thinking_level_change, session_info, …`)→
151
+ 生效级别在 session 侧**有家、可冷路径读**。
152
+ 7. **`stopReason` 分组不得推断内容构成**:写长消息走 `write` 工具,
153
+ **长消息就在 `toolUse` 类里** → "85% toolUse" 只能支撑请求数结论,
154
+ **不能**推出"短工具往返主导"(本轮真实踩过这个错并据此低估过省时)。
155
+ 8. **U1(thinking 是否在后续请求重发)**:在**性能**上 ≈0(若重发则进
156
+ `cacheRead`,实测近乎免费;成本侧每场 ≈$0.005)。它应按**设计一致性**升为议题:
157
+ fork 的 budget 模式明确**丢弃 thinking**(视推理为**可丢的过程产物**),
158
+ 而"重发进入后续上下文"给出相反答案 → **同一系统对"thinking 算不算内容"
159
+ 的两个答案需要对齐**(优先级由设计一致性定,不由性能定)。
160
+
161
+ ---
162
+
163
+ ## 六、口径:本轮付出过代价的地方(结论引用时必须带上)
164
+
165
+ 1. **截距不可跨设定比较**:带截距 OLS 精确通过均值点,**换设定时变化全部进
166
+ 截距**(截距 = 外推到"所有自变量为 0",样本里没有点在那个位置 → 是**外推量**)。
167
+ 故 `5.32s`(2 变量)与 `6.79s`(4 变量)不是漂移、是设定差异;历史三值
168
+ (5.32 / 6.07 / 6.79)**作废**,只引两个总体:
169
+ **fork 源全量 n≈680 → 5.26s**;**本讨论自身 n=166 → 7.44s**。
170
+ 要报"每请求成本"用 **`mean(Δt)`**(与设定无关)。
171
+ 2. **口径标头 `(设定, 总体, 快照, 筛选)` 同时管截距与系数**:同设定下斜率也在
172
+ 漂(`0.00405 → 0.00471 → 0.00488`,**±10%**)→ **"每 output token 单价"同样
173
+ 不可跨快照比较**。
174
+ 3. **回归筛选规则(完整)**:计入条件 `0 < Δt < 600 且 output > 0 且有前驱`;
175
+ 快照 171 条 → 计入 166,**error 是唯一排除项(5 条,usage 全零)**;
176
+ `Δt` 上界**从未触发**(样本内最大 **120.2s**,余量 5 倍);fork 全量 773 条 →
177
+ 733 计入 / 34 error / 6 output=0 / **0 超限**。
178
+ **反证据**:把 error 强行并入 → R² **0.814 → 0.429 腰斩** → 高 R² 本身就是
179
+ "未混入"的证据。(备注:那个 600s 上界是**从未触发的防御性条件**,
180
+ 若将来触发会静默截尾——结论里保留"筛选口径 + 实际排除 0 条 + 样本内最大 Δt"
181
+ 一句即可复现。)
182
+ 4. **`Δt` 拆列**:`Δt_gen`(成功响应,回归/decode 只用它)与 `Δt_fail`
183
+ (`stopReason=error`,usage 全零)**分列,不并入也不丢弃**——否则 provider
184
+ 抖动会被算成"生成变慢"。
185
+ 5. **机制命名不进结论**:截距的 "TTFT/排队/网络" 是**外推命名**(纯推断,无独立
186
+ 证据);且截距与 `cacheRead` 共线 →"固定成本"与"context 成本被截距吸收"
187
+ **本数据分不开**。→ **报告只出现能直接读出的量(回归量名),不出现机制名。**
188
+ 6. **唤醒跨度 ≠ per-response 跨度 ≠ 墙钟**(一个数字一个口径):
189
+ `elapsed_ms` = 进程跨度(无家 → 就地捕获);per-response `Δt` =
190
+ `ts(assistant_i) − ts(紧邻前一事件)`(有家 → 冷路径读);墙钟 = bare commit 差。
191
+ **不得互替。**
192
+ 7. **16.2% / 19.4% 的稳健性**:从**很少的事件**算出(n=4→7),单条最长 130.6s
193
+ 占 34% → 应写成**两场区间 11%–19%** 并附 n 与最长单条,而不是单场点估计。
194
+ 8. **否定性/存在性结论必须附检索口径**(查了什么、判据式、样本 n、筛选规则、
195
+ 快照时点)——否则它与"未测"等价。**本轮同族错误共 5 次**。
196
+
197
+ ---
198
+
199
+ ## 七、铁律视角:职责边界 / 复杂度匹配 / 设计符合度的具体发现
200
+
201
+ 1. **级别决策的 owner 在 pi 侧,本仓不该也不能复刻**:三层解析
202
+ (`modelThinkingLevels[model]` → `defaultThinkingLevel`)属于 pi 的配置。
203
+ 本仓职责只有三件:**①读"生效值" ②显式落 spec ③读不到时可见地失败**。
204
+ 在 `spec_gen` 里复刻一份优先级表 = **两处实现 / 两个事实源 / 必漂移**。
205
+ 2. **同名词反义(比"同概念多名"更严重)**:`start_discussion.py:96-97` 相邻两行里,
206
+ `model` 槽的 `default` = **继承本机**,`variant` 槽的 `default` = **max**
207
+ (硬编码)。读者按上一行直觉读下一行**必错**;且**用户无法表达
208
+ "variant 继承本机"**(写 `default` 得 max、留空也得 max)。
209
+ → 修法 = **删掉这个别名**(**不是**加 `inherit` 档——那才是引入第三种语义)。
210
+ 3. **探测失败会静默落到 `max`**:`_detect_pi_model_thinking` 返回空串 →
211
+ `spec_gen.py:283-291` 只写 model(variant 槽整个省略)→
212
+ `start_discussion.py:96-97` 的 `v = "max"` → `pi-agent.json` `"thinking": "max"`。
213
+ **spec 文件表面完全正常**(甚至更"干净")→ 意图与生效值之间**没有留痕**。
214
+ → **处方:删静默(失败可见),不是把默认翻到便宜侧**——否则同类 bug 换个方向
215
+ 复现。
216
+ 4. **僵尸配置**:`modelThinkingLevels` 共 6 条 per-model 覆盖 = **3 dead**
217
+ (provider 已无该 id)/ **1 dormant**(有 id 未启用,**不删**)/ **2 live**。
218
+ **3 条 dead 说明"换模型后旧键不清理"是系统性行为,不是手误**(dead 可删,
219
+ 但优先级低:本仓无该键、且第 5 条落地后不再有诊断成本)。
220
+ 5. **逐 agent 分级**:机制免费(`models.md` 逐行),但**策略不免费**——
221
+ "谁给高谁给低"是一条**无设计依据的策略**。**放行条件**:出现**角色差异的
222
+ 测量依据**(如 resultWriter 是唯一产出交付物者)时才允许;否则是补丁复杂度。
223
+ (性能侧同结论:归因成本爆炸,收益上限 15% 量级。)
224
+ 6. **"判定不受影响" ≠ "轨迹不受影响"**:`meeting_core` 的判定是纯逻辑、不读
225
+ thinking,**但它的输入就是 LLM 产出的消息类型** → 级别改变的是**走哪条分支**
226
+ (freezing → af 补写;pass → RR;轮次变化 → 墙钟变化)。
227
+ → 正确表述:**安全有界 ≠ 成本有界**(机器不变,轨迹会变)。
228
+ 7. **验收判据必须配对(可复用规则)**:**单向指标(越小越好)在存在"少做事也能
229
+ 变小"的路径时,不可单独作验收判据**——墙钟可以靠"少检验"变小 →
230
+ 验收必须是 **`墙钟 ↓` 且 `收敛前检验量(RR/af 轮次分布)不下降`**。
231
+ ("快"与"浅"在本系统里量级可比:非生成成分占 47%,与上界 23% 同阶。)
232
+ 8. **报告的职责是"人的观测面",不是"留存"**:契约明确 `--report` **不持久化**
233
+ (冷路径 / 不落盘 / fail-open / **不得升级为验收 gate**)。
234
+ → **cleanup 之后连报告也不存在**(除非用户自己复制)→ **加字段 ≠ 留存**。
235
+ 准入闸门最终三条:**① 源被删前是否必须被看见(当场诊断价值)② 口径唯一
236
+ ③ 不参与判定**。
237
+ (此前"必须能说出哪个判定读它"的判据被证伪:它与契约"不得升级为 gate"冲突,
238
+ 连 `effective_level` 自己都过不了。**判据与结论不匹配比结论错更危险。**)
239
+ 9. **删除分支的前提是"失败仍然可见",不是"当前不可达"**:
240
+ `start_discussion.py:391` 的 `else "max"` 静态不可达,但删掉后若跨 20 行的
241
+ 不变式被破坏 → `thinking: ""` → `meeting_loop` 的 `if thinking:` 不成立 →
242
+ **不加 `--thinking` → 静默回落到 pi 的解析值**(正是要消灭的"不可读")。
243
+ 保留 `or DEFAULT`(引用同一常量)成本 1 个表达式,且使该场景**行为无害**。
244
+ **但要明确:兜底 ≠ 可见**(写出的 `max` 是个看起来正常的值)——要可见须
245
+ ①上游报错 ②来源标记 ③报告读 effective level 并对照意图。
246
+ 10. **同层同义重复应合一,且值只留一个声明点**:`start_discussion.py:320` 与
247
+ `:384` 是同一个 `models.get(p, (None, "max"))` 在同一函数出现两次 →
248
+ 合一,**值收成一个常量**,其余只**引用**(否则只是把重复换个排列)。
249
+ 11. **一次分析里"两个面"不可混**(可复用规则):
250
+ **一次性分析 vs 冷路径报告**;**验收判据 vs 常设信号**。同一个数字在不同
251
+ 用途下**强度不同,必须标出"这是判据还是信号"**——否则会出现"信号被当 gate"
252
+ 或"gate 被当信号"(同一错误的两个方向)。
253
+
254
+ ---
255
+
256
+ ## 八、建议清单(三视角合一)
257
+
258
+ **核心结论:先修"自明性与口径",再谈调参。** 调级别的收益上限(−23%,实测
259
+ −3.6%)**小于**把口径理顺能减少的返工——本轮 13 条消息里,被反复裁决的是
260
+ **边界与口径**,几乎没有一条是关于 thinking 本身的技术事实。
261
+
262
+ ### 1. 报告最小集 = **4 个按谓词分组的字段**(+ 一组对照)
263
+
264
+ ```
265
+ usage_totals = {output, reasoning, input, cacheRead} # 同源 usage 对象的合计
266
+ requests_by_stopReason = {toolUse, stop, error, None} # 计数,键 = 原值
267
+ seconds_by_stopReason = {toolUse, stop, error, None} # 时长,键 = 原值
268
+ effective_levels = [high] # 集合,零校验分支
269
+ ```
270
+ 外加输出 **`(declared, effective)` 对照**——`declared` 本来就在 `pi-agent.json`
271
+ 里,只是**从来没人和生效值对照过**(**0 新增字段、0 新增分支**的可见性形态)。
272
+
273
+ **设计要点**:
274
+ - **`error` 键里就是那笔 16–19% 的账**(计数在 `requests_*`,时长在 `seconds_*`,
275
+ **同谓词、两种度量,读法一致**)。
276
+ - **键 = `stopReason` 原值**:名字即事实、不会过期(`requests_tool/final` 这类
277
+ 解释性命名会因"长消息也是 toolUse"立刻过期)。
278
+ - **键集合必须保留 `None`**(否则"计数相同、时长差两个数量级"的情形会静默丢失)。
279
+ - **`seconds_by_stopReason` 是"响应跨度合计",不是墙钟**(口径行必须写明:
280
+ 不含工具执行段 / 进程框架段 / 唤醒间隔)。
281
+ - **字段数不随数据增长**(新增同类数字只多一个键)→ 规则数意义上更简。
282
+
283
+ **实现边界**:**只读"已有家"**(session 的 `usage` / `stopReason` / `timestamp` /
284
+ `thinking_level_change`),**禁止在 loop log 新增记录**——同一事实两处 = 双写,
285
+ 且 loop log 的不变量是**零判定输入**(只有 `rc`/`elapsed_ms` 那类"**无家**"的量
286
+ 才该就地捕获)。**不新增度量、不新增校验器。**
287
+
288
+ **明确排除**:per-response `Δt` **分布**(口径需版本化:4 条边界 + 逐条无常设
289
+ 消费者);摘要(`n/p50/p90`)按弱偏好不加——**能现算的,不进常设面**。
290
+
291
+ ### 2. 让级别"自明"(3 处,都是减法)
292
+ - **spec 永远写显式 variant**(含探测失败的路径);探测失败**可见**(报错或显式
293
+ 回显"使用默认 X"),而不是静默取档。
294
+ - **删 `variant` 槽的 `default` 别名**(消掉同名词反义)。
295
+ - **合一 `320`/`384` + 值收成一个常量**;`391` **保留同常量兜底**。
296
+
297
+ ### 3. 不做的
298
+ - **不跑整场 A/B**(60–90 分钟真实 LLM;`f` 不阻塞任何建议动作,且结论对 `f`
299
+ 不敏感)。
300
+ - **不做自适应/动态调级**、**不做逐 agent 分级策略**(无依据的策略 = 补丁复杂度)。
301
+ - **不为质量新建度量/评分**;不加 provider 档位白名单校验(外部依赖型校验)。
302
+
303
+ ### 4. 可选的
304
+ - **微实验**(同模型、同长提示,`high`/`max` 各 1 条,约 1–2 分钟):**只在
305
+ 带上判据时才做** —— ①输入用**真实 wake prompt**(仓库里已有,避免自造任务
306
+ 构成)②每档若干重复 ③判据写成"**两档差异区间是否排除 `f > 0.3`**"。
307
+ 否则它只是又派生一个**没有判据的数字**。
308
+ - 清理 3 条 **dead** per-model 键(前提已可判定;优先级低)。
309
+
310
+ ---
311
+
312
+ ## 九、未决项 / 需用户拍板
313
+
314
+ 1. **度量是否持久化(契约级,需拍板)**:
315
+ - **A. 保持不持久化(三方倾向)**:跨场比较**已有既有载体**(`docs/reviews/`
316
+ 归档,e2e13 的 11% 就是这样来的)→ **零新增产物、零新生命周期、不动契约
317
+ 不变量**。代价(须承认):**从产物本身无法回答跨场趋势**,要趋势得人工归档
318
+ 一次——**用"零机制"换"人工一次"**。
319
+ - **B. 增加持久化落点**(如 cleanup 前写 `<分析目录>-report.txt`):需**修改
320
+ "不持久化"不变量** + 记录决策 + 新产物的生命周期/残留语义。
321
+ - **A/B 不改变"该加哪些字段",只决定"理由是什么"**(A 下加 error 字段的理由是
322
+ "**当场可见最大的杠杆**")。
323
+ 2. **微实验**(可选,判据已给,见八.4)。
324
+ 3. **U1:thinking 是否在后续请求重发**——作为**设计一致性**议题
325
+ (fork budget 模式丢 thinking ⇄ "重发即进入上下文"),优先级由设计一致性定。
326
+ 4. **僵尸键清理**(dead 3 条可删,优先级低)。
327
+ 5. **本场占比数字的区间化**(16.2%→19.4% 仅 n=7)→ 结论里用 **11%–19%**。
328
+
329
+ ---
330
+
331
+ ## 十、过程记录:本轮的方法教训(值得固化)
332
+
333
+ **13 条消息里的修正分布**(自撤清单):
334
+
335
+ | 视角 | 自撤/自纠 |
336
+ |---|---|
337
+ | 性能 | C2「prefill 与 decode 同阶」、E3「收益随轮次衰减」、「7 倍」分子/分母口径、「18 分钟」切片读错、「报告是 cleanup 后唯一存留」(事实错) |
338
+ | 简单 | 「不对称」论证、「本场未见崩塌」软信号、「零成本路径已试尽」、L12「删 391 不可达分支」、「截距三值主因是 n」 |
339
+ | 铁律 | 「该值没有 owner」表述、「收益集中在尾部」、「真实成本不是 thinking 而是配置」(幅度)、「分析类响应更长 vs toolUse 更短」的分类前提、「三值主因是筛选规则 + 样本」、「回归从未见过尾部」 |
340
+
341
+ **四条可复用的规则**(本轮由错误逼出,建议写入方法论):
342
+
343
+ 1. **否定性/存在性结论必须附检索口径**(查了什么、判据式、样本 n、筛选规则、
344
+ 快照时点)——**"未测"与"试尽"是同一类结论**。本轮同族错误 5 次,共同形态是
345
+ 把"**我没找到**"写成"**不存在**"。
346
+ 2. **一个数字一个口径 + 口径标头 `(设定, 总体, 快照, 筛选)`**,且标头**同时管
347
+ 截距与系数**;**外推量(截距)不可跨设定比较**。
348
+ 3. **验收判据必须配对**:单向指标在存在"少做事也能变小"的路径时不可单独用。
349
+ 4. **判据本身要自洽**:与既定契约冲突的判据(如"每个数字都要有判定读它")会
350
+ **误挡合法数字**——**判据错比结论错更危险**。
351
+
352
+ **成本结构(性能实测)**:本轮约 **1/3** 的取数动作花在"**配置与观测不可读**"上
353
+ (级别要跨 4 处才能确定、生效值不自明、口径未定义),**2/3** 花在真正的测量。
354
+ → 这 1/3 里每一笔都可归因到**同一类缺陷**,所以"**先修自明性**"的性价比结论成立
355
+ ——但应写成"1/3 的返工是自明性买来的",**而不是**"真实成本不是 thinking 而是
356
+ 配置"(后者会把测量本身的价值也抹掉)。
357
+
358
+ ---
359
+
360
+ ## 附:本次分析的最终回答(给提问者)
361
+
362
+ > **你把 thinking 降到 `high`,速度上大约省 3%(44 分钟省 ~1.5 分钟),
363
+ > 最好的情况(把推理全消掉)也不会超过 23%;而"最差的那次等待"基本不动
364
+ > (上界 4.7%),因为它是 provider 失败等待造成的。质量上没有任何判据
365
+ > ——既不能说变好,也不能说变差。**
366
+ >
367
+ > **它是三个杠杆里最小的一个**:第一位是**请求数**(每个请求有 5–7 秒的
368
+ > 非生成成分),第二位是 **provider 失败重试**(本场 11–19% 的 LLM 时间,
369
+ > 而报告里**根本没有这个字段**)。
370
+ >
371
+ > **所以真正值得做的不是继续调这个旋钮,而是:① 让最大的那笔账可见
372
+ > (报告加 4 个分组字段 + `(declared, effective)` 对照)② 让"这一场跑在什么
373
+ > 级别"变成 spec 里自明的值(探测失败要可见)③ 顺手把两个同层的默认值重复
374
+ > 合一(保留兜底常量)。**
@@ -0,0 +1,126 @@
1
+ <!-- 存档:docs/reviews/2026-09-13-e2e19-scoped-config-review.md
2
+ 来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
3
+ 分析场次目录已随 --cleanup 删除;文中的消息编号(如 `效率/0003`)不可再核验,
4
+ 仅作溯源线索(与代码注释引用约定一致:行为以自描述为准)。 -->
5
+
6
+ # 存档说明
7
+
8
+ - **主题**:审阅「agent 进程作用域配置」(关 AFT 语义搜索的实现)
9
+ - **场次**:`mv-mv-main-20260913-122720`(3 视角;真实 pi 讨论)
10
+ - **抓到的问题**:键名方向写反(恒写旧名 → 上游一旦移除即静默回吐 57s);`_strip_jsonc` 会**改写字符串值**(尾逗号正则作用于含字符串的整段文本);AFT 关不掉的条件与用户侧副作用未文档化
11
+ - **落地**:`c2be72d`(键名改现行名 + 两趟法 strip + gate 判文件 + 条件清单 + 瘦身)
12
+
13
+ ---
14
+
15
+ # 多视角分析结果:审阅「agent 进程作用域配置」改动(关闭 AFT 语义搜索的实现)
16
+
17
+ - **分析主题**:审阅 `0ab8aee`(agent 进程作用域配置:`build_agent_config` / `_spawn_env` / `_strip_jsonc` / `start_discussion` 接线)
18
+ - **参与者**:性能、简单、铁律(三视角)
19
+ - **分析类型**:审阅(只提意见,不修改、不运行测试)
20
+ - **发起**:2026-09-13,分析目录 `mv-mv-main-20260913-122720`
21
+ - **结论一句话**:**结构方向正确**(改用户配置 → 改为 agent 进程作用域配置,主 pi 零影响),但要修 **1 处键名方向错误(唯一选点)**、**1 条活着的静默值改写 bug**、**1 条确定性覆盖边界与 1 条用户侧副作用的可见性**;另有多处瘦身。全批约 **净删 27–30 行**,**无一项新增常驻机制**。
22
+
23
+ ---
24
+
25
+ ## 一、事实基础(讨论中实测/源码核实的依据)
26
+
27
+ ### 1.1 动因(性能实测)
28
+
29
+ | 变体(`pi --print "ok"`,cwd = 本仓) | 用时 |
30
+ |---|---|
31
+ | `--no-extensions`(基线) | 2.2s |
32
+ | 仅 magic-context | 2.8s |
33
+ | 仅 mcp-adapter | 2.2s |
34
+ | 仅 AFT(用户配置,语义搜索开) | **61.0 / 60.9 / 61.0s** |
35
+ | 仅 AFT(`experimental_semantic_search: false`) | **3.3 / 3.9 / 4.1 / 4.3 / 4.4s** |
36
+
37
+ - 跨项目复现(本仓 / pi-agents-helper / 空目录)→ 非索引冷热、非并发竞争;耗时落在**收尾段**(pi 退出前,AFT 日志显示其自身 ~2s 已 shutdown)。
38
+ - 关键路径:`meeting_loop._run_wake_proc` 等 pi 进程退出才继续 → **每次唤醒**付这 57s。
39
+ - 量化:33 次唤醒 × 57s ≈ **12–14 分钟 / 55 分钟墙钟(≈22–25%)**;剩余成本(语义关后)≈ **1–2s/唤醒 ≈ 2%**。
40
+
41
+ ### 1.2 AFT 源码级机制事实(本次讨论新核出,均带出处)
42
+
43
+ | # | 事实 | 出处(`@cortexkit/aft-pi/dist/index.js`) |
44
+ |---|---|---|
45
+ | M1 | `configHome()` = `XDG_CONFIG_HOME`(须绝对路径)否则 `~/.config` —— 与 `meeting_fs._source_config_home` **逐条同规则** ✓ | `configHome()` |
46
+ | M2 | 现行键 = `semantic_search`;`experimental_semantic_search` = **旧名**(迁移表 `oldKey → newPath:["semantic_search"]`);读取端 `semantic_search ?? experimental_semantic_search` | `CONFIG_MIGRATIONS` / `migrateRawConfig` / `loadConfigFromPath` |
47
+ | M3 | 旧名与新名并存时:**警告 + 忽略旧名 + 删除**(`Config migration conflict … ignored`) | `migrateRawConfig` |
48
+ | M4 | 项目级 `.cortexkit/aft.jsonc` 对 `semantic_search` / `experimental` 等**安全名单键**在层级合并中**覆盖用户层**(`mergeConfigs(user, project)` → `{...base, ...safeOverride}`);且 AFT 不会为此打警告 | `PROJECT_SAFE_TOP_LEVEL_FIELDS` / `loadAftConfig` / `mergeConfigs` |
49
+ | M5 | AFT 在**每个 pi 进程启动**执行 `migrateAftConfigLocations(process.cwd())`;user-scope 目标 = `configHome()/cortexkit/aft.jsonc`(**被我们的 XDG 注入换成讨论目录里的临时副本**);目标已存在且与 legacy 源语义不同时 → 差异写进我们的临时目录 + **`unlinkSync` 用户侧 legacy 文件** + 写 `.MOVED_READPLEASE`(内含原文) | 入口段 / `migrateAftConfigFile` / `markLegacySourcesMovedAside` |
50
+ | M6 | agent 侧 AFT 的**活源清单**:`~/.pi/agent/aft.json[c]`(HOME 派生,活)、`<proj>/.pi/aft.json[c]`、`<proj>/.opencode/aft/aft.json[c]`、`<proj>/.cortexkit/aft.json[c]`(项目派生,活);用户级 OpenCode `<XDG>/opencode/aft/…` 因 XDG 被换而**非活源**;`OPENCODE_CONFIG_DIR` 若设置则绕过 XDG → 条件性活口 | `resolveLegacyAftConfigSources` / `legacyOpenCodeConfigDir` |
51
+ | M7 | 影响面实测(读取者):pi 自身与 pi-agent-core **不读** XDG;扩展只有 `@cortexkit/aft-pi`、其传递依赖 `@cortexkit/aft-bridge`(同一份 `cortexkit/aft.jsonc`,无第三个文件要拷)与 `pi-magic-context`(已逐字拷贝) | 逐包 grep + 简单复核 |
52
+
53
+ ---
54
+
55
+ ## 二、意见清单(合并去重后的最终态)
56
+
57
+ | # | 严重度 | 位置 | 问题 | 依据 | 建议(最终形态) | 来源 |
58
+ |---|---|---|---|---|---|---|
59
+ | **1** | **中** | `meeting_fs.py:182-186` | **键名方向写反**:恒写旧名 `experimental_semantic_search`,现行名 `semantic_search` 只在用户配置里本来就有时才写;注释又把现行名标成"旧键名" | M2/M3:现行键是 schema 字段;写旧名要靠迁移才生效,上游一旦移除旧名即**静默失效**(每唤醒回吐 57s);旧名残留还会触发 migration-conflict 警告 | **恒写 `semantic_search = False` + `pop("experimental_semantic_search")` + 注释改成与源码一致 + 测试名改正** | 铁律#2 / 简单 S4 / 性能第 2 条(**三方同点,唯一选点**) |
60
+ | **2** | **中** | `meeting_fs.py:159-160`(docstring) | "已知边界"把**确定性覆盖**写成"**可能**覆盖" | M4:项目层对安全名单键直接压过用户层;主场景"在别人的项目里跑分析"必然暴露(本仓恰好无该文件 → 本机免疫) | docstring 改为确定语义;并入 #3 的 setup 检测(命中即打印) | 铁律#1 / 性能 / 简单 |
61
+ | **3** | **低-中** | setup(新增) + docstring | legacy 源与**迁移副作用**均未在文档中列出;且迁移目标被我们重定向(用户级 legacy 文件可能被 `unlink` + `.MOVED_READPLEASE`,配置被"迁"进会删除的临时文件) | M5/M6;概率低(主 pi 通常已消费 legacy)+ 后果中-重(主 pi 之后读不到自己的配置) | **与 #2 合并为一张 `(pattern,label)` 清单 + 循环 + 命中才打印**(setup、冷路径):四条 glob `<proj>/.cortexkit/aft.json*`、`<home>/.pi/agent/aft.json*`、`<proj>/.pi/aft.json*`、`<proj>/.opencode/aft/aft.json*` **+ `OPENCODE_CONFIG_DIR` 条件项**;标注第三方出处(`aft-bridge/dist/paths.js` + `aft-pi`)与**重核触发条件**;警告带**可操作下一步**;**只陈述事实、不进 `--report`、不做判定** | 铁律#1 扩展(新发现)/ 性能 §四 / 简单 §四 |
62
+ | **4** | **中**(活着的 bug) | `meeting_fs.py:77-116` | `_strip_jsonc` 的 40 行状态机**为注释跟踪了字符串状态,最终尾逗号正则却作用于含字符串的整段文本** → 字符串值里的 `", }"` / `", ]"` 被静默改写(改写后仍是合法 JSON,`json.loads` 挡不住),篡改被写入作用域副本 | 性能**执行级矩阵**:现状实现即 ✗(活着的 bug,非新提案引入);`_read_jsonc:122` 已含 `json.loads` | **8 行替换版**:按字符串切分,注释与尾逗号两趟正则**只作用于非字符串段**;`json.loads` 兜底沿用现状(不新增校验)。**新增两条回归用例**:`{"exclude": "a, }"}` 断言**解析后的值**不变;`{"a": [1, 2, ]}` → `[1, 2]`(双不变量,现状会红) | 铁律#3 / 简单 S3 / 性能复现 |
63
+ | **5** | **低** | `meeting_loop.py:349` | `_spawn_env` 的注入门判**目录**而非"我们的配置文件":半成品状态(目录在、文件缺)会注入一个空/半配置的 XDG → AFT/MC **静默**回落默认(= 57s 回吐) | 代码自身;docstring 声明的是"配置就位" | 门改判**恒写的那个文件**(具名推导,见 #6);未命中时 gate 内 `log()` 一行,**内容只写事实**(路径 + 未注入 XDG_CONFIG_HOME),**不设 once-flag**(重复只发生在异常态,落在每 agent 的 loop 日志文件里);**谓词单点**——不得在 loop 入口再判一次 | 铁律#5 / 简单 S6 / 性能 §三 |
64
+ | **6** | **低** | `meeting_fs.py:201` + `start_discussion.py:420` | `build_agent_config` 返回的**目录**在生产端被丢弃(唯一调用是 `_, warnings = …`),只有测试在用,其中一条还是同义反复断言 | 生产签名应等于真实调用形态;"路径只有一个来源" | 返回值收成 `warnings`;写入侧与判定侧**共用具名推导**(`agent_config_dir(base)` + 文件推导),不靠返回值传递、不 `dirname(dirname())` 反推;删同义反复断言 | 简单 S1 + 铁律#5 / 性能 §三(**合并为一件事**) |
65
+ | **7** | **低** | `meeting_fs.py:134` | `source_config_home=None` 参数**只有测试在用**,而配置根已有注入路径(`XDG_CONFIG_HOME`) | `grep` 全量:7 处调用全在 tests | 删参数;测试改 `patch.dict(os.environ, {"XDG_CONFIG_HOME": …})` | 简单 S2 |
66
+ | **8** | **低** | `meeting_loop.py:347-348` | `os.path.dirname(workdir)` 同表达式算两次 | 代码可见 | 一个局部变量 | 简单 S5 |
67
+ | **9** | **低**(风格) | `meeting_fs.py:62-65` vs `:216` | `AGENT_CONFIG_DIR` 常量与内联 `"repo.git"` 两套风格 | 同文件两处 | **内联**(删常量):判据 = **同一事实的名字数**(常量 + 函数 = 2 个名字指向同一字符串),内联后与 `bare_of_base` 同形 | 简单 S7(两轮反覆后定案) |
68
+ | **10** | —(撤回) | — | 原建议"枚举未拷贝的源配置目录并告警" | 谓词(谁读 XDG)**无界**,枚举口径匹配不上;本机实测噪声(`~/.config` 8 项、`cortexkit/` 7 项里 5 个 `.bak`) | **撤回为文档义务**:docstring 影响面段标"**快照 + 重核动作**"(装新扩展时重跑 `grep -rl XDG_CONFIG_HOME <包 dist/>`),**不得写成不变量** | 铁律#4(自撤)/ 简单 §五 |
69
+
70
+ ### 只准改一处 → **选 #1(键名)**
71
+
72
+ 三方同点,理由:它是**唯一"删分支 + 去错误事实 + 挡静默回退"三项同时成立**的改动(现行键是 schema 字段 → 定义上生效;恒写旧名是对弃用名的单点依赖)。#2/#3 **只能让失效可见**,无法消除;#4 是修一条活着的 bug,价值高但属第二批。
73
+
74
+ ### 建议批次与顺序
75
+
76
+ `#1(键名)→ #2+#3(同一张清单)→ #4(S3 八行版 + 两条用例)→ #5(+S6)→ #6/#7/#8/#9`
77
+
78
+ ---
79
+
80
+ ## 三、质量背书与"看了但没问题"(避免为找问题而找问题)
81
+
82
+ **结构上成立、无需改动的点**(三视角均有交叉确认):
83
+
84
+ - `agent_config_dir(base)` 单点路径推导(docstring 明说"两边各拼一次就会漂")——与本仓 `bare_of_base` 的规矩一致 ✓
85
+ - `_source_config_home` 与 AFT `configHome()` 逐条同规则(M1)——"第二事实源"但零依赖,接受 ✓
86
+ - `_spawn_env` 合并 `os.environ`(Popen 的 env 是整体替换,不合并会丢 PATH)✓;且它是**唯一环境构造点**(原 GIT_CEILING 散在 Popen 参数里,收敛后更清晰)
87
+ - fail-open + 可见警告链:`build_agent_config` 返回警告 → setup 打印;`mv_cli._call` 是**直通不捕获** → prompt/CLI 主路径确实可见 ✓
88
+ - `magic-context.jsonc` 用 `shutil.copyfile` 逐字拷贝(1 行、不解析)——与 AFT 的"解析+合并"不对称**由需求差异产生**,不是重复代码 ✓
89
+ - 不改用户配置(测试锁定源文件逐字节不变)、目录随讨论目录删除(零残留)、"目录存在才注入"保护老环境 ✓
90
+ - 读取者范围已复核(M7):**无第三个文件要拷**,"未知读取者"的今日暴露面最小 ✓
91
+ - `build_agent_config` **不拆**:四段不到 60 行且有注释边界 ✓
92
+
93
+ **性能总评**:热路径新增成本 ≈ 0(全部落建环境期或少数 `stat`);收益 22–25%;**不加缓存**(唯一可能的过度设计)。性能**验收**(本场 `--report`):① 单唤醒进程时长无 ~57s 尾峰;② 进程跨度/墙钟(对照 e2e17 = 124min / 55min);③ 档位对照行(声明 max / 生效 max)。
94
+
95
+ ---
96
+
97
+ ## 四、明确"不做"的清单(本批已否掉的形状)
98
+
99
+ | 否掉项 | 理由 |
100
+ |---|---|
101
+ | 时长双峰 / 异常尾判定(进 `--report`) | 观测面契约:`--report` 是观测面**唯一机器出口、不得升级为验收 gate**;且属"用时长方差/时间窗猜测"的判定形状(本仓明令禁止) |
102
+ | 读 AFT 日志文案(`local embedder ready`)检测 | 把正确性绑到**第三方日志文案**("无家"信号)——用一个静默换另一个静默,新增跨部件耦合 |
103
+ | 给 `_spawn_env` 加缓存 | 每唤醒一次百项级 dict 拷贝 + 一次 stat,微秒级;缓存是此处唯一的过度设计,还引入"讨论中途配置变化"的一致性论证 |
104
+ | 扩拷贝面(把其他配置目录也拷给 agent) | 替其他扩展**决定启用与否**(越界),且可能触发未知的昂贵初始化 |
105
+ | ban AFT(`--pure` / `--no-extensions -e <MC>`) | 剩余仅 ~1–2s/唤醒 ≈ 2%,代价是 agents 工具行为漂移(`hoist_builtin_tools` 换掉 read/write/edit/bash);**用户已定"以后单独测",本批不做** |
106
+ | 任何新增常驻机制(阈值/统计/每唤醒重活) | 简单视角验收口径第 2 条;能删的机制不新增 |
107
+
108
+ ---
109
+
110
+ ## 五、验收口径(实施后核对)
111
+
112
+ - **简单性(4 条,可机械判定)**:① **净行数不增加**(本批估算 **−27 ~ −30 行**);② **无新增常驻机制**(无阈值/统计/缓存/per-wake 重活);③ **谓词单点**(`agent-config/cortexkit/aft.jsonc` 在不在,只有 gate 内一处判定);④ **路径单点**(XDG 根与被检查文件各一个具名推导,写入侧与判定侧共用)。
113
+ - **正确性(铁律)**:两条 strip 回归用例(**断言解析后的值**,现状实现会红);docstring 的"已知边界"补全 legacy 源清单 + 迁移目标被重定向(含 `unlinkSync` + `.MOVED_READPLEASE` 与第三方出处);警告行只陈述事实、**不写风险评级或推断**。
114
+ - **性能(3 条)**:见 §三末(本场 `--report` 三处对照)。
115
+
116
+ ---
117
+
118
+ ## 六、讨论过程索引
119
+
120
+ | 参与者 | 主要贡献(消息) |
121
+ |---|---|
122
+ | **性能** | 0001 收益量化(22–25%)与"加检测"初版建议;0003 收下键名纠正、**撤回**两个判定类检测机制;0004 接受"并存"、给出值改写**执行级复现**、指出告警不应每唤醒一行;0005 追加执行矩阵、支持 legacy 固定清单、撤回"告警一次";0008 给出 legacy 名单的**减法**(用户级 OpenCode 非活源)与 `OPENCODE_CONFIG_DIR` 条件项 |
123
+ | **简单** | 0001 七条瘦身(S1–S7);0002 自行纠正 S3 版本、给出 8 行最终版、核实迁移边界;0004 独立核到 `unlinkSync` + `.MOVED_READPLEASE` 实锤;0005 S7 定案(内联);0006 S6 落点(gate 内 log,flag 会引入测试顺序依赖)与两处归并;0007 给出**四条简单性验收口径**;0009 `OPENCODE_CONFIG_DIR` 定案 |
124
+ | **铁律** | 0001 五条意见(含 M4/M5 两处源码级机制事实);0002 纠正键名方向;0003/0005 反对判定类检测、#4 自撤(接受噪声论据);0004 给出"删旧名"的执行细节与 #3 的半行修正;0007/0010 收敛 S6/S7;0008 严重度/契约判断("概率低 + 后果重 + 成本≈0")与清单验收条件;0009 核 性能 的迁移 I/O 疑问(每进程一次、消费后不复现) |
125
+
126
+ **收敛状态**:三视角全部 `pass`(性能/0010、简单/0012、铁律/0013),无保留意见。