pi-multi-viewers 0.10.0 → 0.10.2

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.md CHANGED
@@ -69,25 +69,36 @@ tests/ 测试(unittest discover tests)
69
69
 
70
70
  | 档 | 唤醒命令 | 用途 |
71
71
  |---|---|---|
72
- | **mc-tools**(默认) | 四个 `--no-*` + `-e <MC 的 subagent-entry.js>` + `-e <pi-mcp-adapter 入口>` | 给 agents **按需检索**(`ctx_search` 查项目历史、web_search 等查外部)——协议模板里带一句「需要项目历史时用 ctx_search」的条件指引(仅在工具真到位时出现)。**降级/严格/生效值语义见 docs/design.md 决策 20 的语义清单**(唯一权威段) |
72
+ | **mc-tools**(默认) | 四个 `--no-*` + `-e <MC 的 subagent-entry.js>` + `-e builtin:mcp`(pi **内置** MCP,零第三方依赖) | 给 agents **按需检索**(`ctx_search` 查项目历史、web_search 等查外部)——协议模板里带一句「需要项目历史时用 ctx_search」的条件指引(仅在工具真到位时出现)。**降级/严格/生效值语义见 docs/design.md 决策 20 的语义清单**(唯一权威段) |
73
73
  | **none** | 四个 `--no-*` | 零扩展、**零依赖**(无 MC 的机器/CI 用这档) |
74
74
  | **all** | 不加任何 `--no-*`(pi 默认发现)| A/B 实验与显式 opt-in |
75
75
 
76
+ **前提与盲区**(细节见 docs/design.md 决策 20「平台能力的可见性」):本档依赖 **pi ≥ 0.99**
77
+ (更早版本 = 未知 builtin 名 → 硬失败 rc=1,不是静默降级);**非交互模式下 MCP server 状态
78
+ 不进任何产物**(失败只走 no-op 的 notify)⇒ `rc=0` ≠ 工具可用,报告固定写一行「平台能力:未观测」。
79
+
76
80
  为什么**不能**用插件全档(`all`):两类插件在**我们这种 session 形态**上都是分钟级负担、
77
81
  且都在关键路径上(loop 等进程退出才继续)——
78
- · **AFT**:大 session 上进程退出前多活数分钟(受控对照 445.9s → 0.5s);
82
+ · **AFT**:大 session 上进程退出前多活数分钟(承重 = **生产真场** e2e21 收尾占
83
+ 66–78% vs e2e23 ≈0%);
79
84
  · **MC 全档**:它的 historian 对"带大段未处理历史"的 session **在默认输出上限
80
- (32000)下**每次必失败并立刻重试(受控对照:同输入 **447s → 10.3s,43 倍**;根因 =
81
- 推理流吃光输出上限——该上限**可调**:主 pi 抬到 131072 后首跑即成功、输出 36954 ✓)。
85
+ (32000)下**每次必失败并立刻重试(承重 = e2e21 同场 62 次/60 失败 vs e2e23 0 次 +
86
+ MC 自己的 `context.db` #916;上限**可调**:主 pi 抬到 131072 后首跑即成功、输出 36954 ✓)。
87
+ (旧引的 445.9s / 447s 两笔属 2026-09-13 装置有缺陷的探针,已**退出证据位**——
88
+ 撤回记录见 `docs/design.md` 决策 20 证据段,本文件不复述。)
82
89
  零扩展**真场实测**:每次唤醒约 48–82s、收尾≈0%、historian 0 次(墙钟随唤醒数变动,
83
90
  区间与测点见 docs/design.md §二)。
84
- **mc-tools 档的实测**:entry **只注册工具、不装 hook** → historian 0/6 ✓(生产 0/3 ✓);
91
+ **mc-tools 档的实测**:MC 的 entry = **工具注册 + 两个生命周期钩子**(开/关 DB),
92
+ **无** historian/压缩执行钩子 → historian **0**(真场:e2e24 0/3、e2e25 0);
85
93
  `ctx_search` 实测可用 ✓;成本**未测得显著差异**(受控探针 n 小、组内方差>组间差 ✗;
86
94
  生产基线:本场 strict=1、n=19,唤醒启动段中位 **0.68s**、收尾中位 0.04s ✓)。
87
- **入口解析**:`meeting_fs` 从 pi 的 packages 找包 → 读它自己声明的 `pi.extensions`
88
- (不硬编码布局)→ `resolve_mc_tools_entry`(取同目录 subagent-entry.js)与
89
- `resolve_mcp_adapter_entry`(取声明的入口本身)。**降级/严格模式/生效值语义**
90
- 见 docs/design.md 决策 20 的语义清单——本文件不复述(本周刚付过一次漂移的账)。
95
+ **入口解析**:MC 那份由 `meeting_fs.resolve_mc_tools_entry` 解析(从 pi 的 packages
96
+ 找包 → 读它自己声明的 `pi.extensions`,不硬编码布局 → 取同目录 subagent-entry.js);
97
+ MCP 那份是**常量** `meeting_fs.BUILTIN_MCP_ENTRY = "builtin:mcp"`(pi 0.99+ 自带,
98
+ 无需解析——第三方 `pi-mcp-adapter` 依赖已删,2026-09-30)。为什么必须显式 `-e`:
99
+ `--no-extensions` 关的是"扩展发现**与内置扩展**"(pi --help 原文)。
100
+ **降级/严格模式/生效值语义**见 docs/design.md 决策 20 的语义清单——
101
+ 本文件不复述(本周刚付过一次漂移的账)。
91
102
 
92
103
  **主 pi 完全不受影响**(只改我们 spawn 的 agent 进程命令行;主 pi 的 MC/历史学家照常)。
93
104
 
package/README.md CHANGED
@@ -122,8 +122,8 @@ scripts/mv.sh --start <spec目录> # 启动(自动挂载主 sessi
122
122
  # 可选:--max-meeting 15 --max-rr 7 --stall-timeout 600
123
123
  # (配额:建环境时固化进 protocol.json,之后不可改;meeting 配额是"每 agent")
124
124
  # 可选:--extension-policy mc-tools|none|all(默认 mc-tools = 零扩展 + 两份只读工具入口:
125
- # MC 的 ctx_search 与 MCP adapter 的 web 检索等;none = 零扩展、零依赖;
126
- # all = 走 pi 默认发现。两份入口各自独立,缺谁少谁且可见降级)
125
+ # MC 的 ctx_search + pi 内置 MCP 的 web 检索等;none = 零扩展、零依赖;
126
+ # all = 走 pi 默认发现。MCP 那份是内置、恒在;MC 那份缺了则可见降级)
127
127
  # 高级:--agents "a,b" 起一次性视角(不建 viewers/ 时用;prompt 入口不传它)
128
128
 
129
129
  # 观看:--start 会输出可直接执行的 !! 流式观看命令(复制执行)
package/docs/design.md CHANGED
@@ -120,6 +120,7 @@ compaction 的 `firstKeptEntryId` 起 + 其后的条目"——窗口内含 compa
120
120
  | `扩展策略` 行 | `声明 / 生效 / strict / 降级原因` | 声明 = `protocol.extensionPolicy`;生效 = loop log 的**登记行**(首唤打一次:`扩展策略: 声明=X 生效=Y strict=0\|1[ 降级原因=…]`)→ 报告给"声明 vs 生效 + ⚠ 生效≠声明"(与"档位:声明/生效"同型;e2e24 评审 E2)|
121
121
  | `终止` 行 | 分类 + 原料计数 | `终止:共识(RR 全体 pass)|freezing N / all-freezing N / pass N / stall 接管行 N`;分类判据只有事实(bare 的 type 计数 + loop log 的"超时兜底/声明接管"字样)——**不做评分** |
122
122
  | `唤醒构成` 表 | 每次唤醒一行 + 每 agent 合计 | 四端点(spawn / 首事件 / 末事件 / exit)+ 往返数 + `Δ助手` + `Δ工具` + retry + 本唤醒内的 commit;合计给 `跨度 = 启动前 + 事件内 + 收尾` 与平均/往返/retry(e2e23 分析产出,见下) |
123
+ | `跨度` 段 | 两个直标量各一行(`Σ进程跨度` / `墙钟跨度`)+ 派生量(`并行度`) | 两量**各自命名、不可互替**;**0 是合法值**(判缺失一律 `is None`——与「缺席≠0」是同一纪律的两面);派生量的**算式永远打印**(n/a 时也打)——2026-09-27 复盘审计产出(外部引用曾把两个 span 混算:Σ1538s÷751s=2.05 vs ÷636s=2.42);全部派生自「进程」段的登记字段与 bare 的 commit 时间,不新增度量 |
123
124
 
124
125
  **`唤醒构成` 表的精度契约**(e2e23 多视角分析定稿):**可推导** = 分段 Δ、
125
126
  按 role 拆分、计数、retry、终止原因、区间重叠、离群 top-N;**不承诺** =
@@ -409,8 +410,9 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
409
410
  AFT(语义关)3.3–4.5s → **~1–2s/唤醒 ≈ 2% 墙钟**(33 唤醒 ≈ 1 分钟
410
411
  / 55 分钟)。最初那 57s 已经拿回,**小 session 上几乎无剩余收益**。
411
412
  **限定(2026-09-14 注记)**:"2%" 仅来自**小 session 单点探针**、**不可外推**——
412
- 生产规模下 AFT 的收尾成本可达数分钟(见本节后文 445.9s 对照);"是否屏蔽 AFT"
413
- 以决策 20(默认 mc-tools / none)为准,本句仅存档。
413
+ 生产规模下 AFT 的收尾成本可达数分钟(承重证据 = 决策 20 的 e2e21 收尾占比
414
+ 66–78% vs e2e23 ≈0%;本节旧引的"445.9s 对照"已在 2026-09-27 审计中退出证据位);
415
+ "是否屏蔽 AFT"以决策 20(默认 mc-tools / none)为准,本句仅存档。
414
416
  - 三种屏蔽方式:`--no-extensions` + `-e <MC 扩展入口>`(AFT 不加载、
415
417
  MC 保留;入口可从 `~/.pi/agent/settings.json` 的 `packages` + 包的
416
418
  `pi.extensions` 解析,不硬编码);`--pure`(全关,已实现);现状。
@@ -471,23 +473,36 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
471
473
  20. **agent 进程扩展策略三档(默认 mc-tools)**(2026-09-13 定零扩展、
472
474
  2026-09-14 用户定为默认 mc-tools 并改“允许而非要求”;取代决策 19):
473
475
 
474
- **证据(两类插件都在关键路径上,都是分钟级)**:
475
- - **AFT**:大 session 上进程退出前多活数分钟——同一份 1.9MB session、同一模型
476
- 受控对照:全扩展收尾 **445.9s** vs 只留 MC **0.5s**。
476
+ **证据(两类插件都在关键路径上,都是分钟级;承重证据 = A 类真场 + 上游账本)**:
477
+ - **AFT**:大 session 上进程退出前多活数分钟(结构性原因:`meeting_loop` 等进程
478
+ 退出才继续 ⇒ 收尾段全额进用户等待)。**承重**:e2e21(插件在场)收尾段占进程跨度
479
+ **66–78%**(进程总跨度 3676/3681/3709s,收尾 2769/2881/2437s)vs e2e23(零扩展)
480
+ 收尾 ≈0%。
481
+ ⚠ **一条已撤回的引用**:本节曾引"全扩展收尾 **445.9s** vs 只留 MC **0.5s**"当
482
+ 受控对照——那批属 2026-09-13 装置有缺陷的探针(`docs/test-methodology.md` #26 已记),
483
+ 且 445.9/0.5 只是运行账本两条**总时长**(458s / 7s)的收尾段拆解,原始分段查无、
484
+ n=1、提示词形态未记录 ⇒ **自愿退出证据位**,不作决策依据(2026-09-27 复盘审计)。
477
485
  - **MC**:它的 historian 对"带着大段未处理历史"的 session(agents 都是这种:
478
- fork 自大历史)**每次必失败并立刻重试**——同一份 fork 源、同一极小任务受控
479
- 对照:**给 MC 447.2s(其中 435.5s 是 3 次连续失败的 historian、尾部占 97%)
480
- vs 不给 MC 10.3s(43 倍)**。曾疑为 historian 模型 id 过期(已修,实测**仍然**
481
- 失败)→ **修正(2026-09-14)**:根因至少含**可调默认上限**——MC 源码
486
+ fork 自大历史)**每次必失败并立刻重试**。**承重**:e2e21 同一场 historian
487
+ **62 次 / 60 失败**(每 agent 每 ~2.5 分钟一次、每次 ~150s,失败即重排)vs
488
+ e2e23 historian **0 次**;主 pi 侧的 32000 上限根因有 MC 自己的 `context.db`
489
+ 记录为证(#916:`maxTokens` 抬到 131072 后首跑成功、输出 36954)。
490
+ ⚠ **另一条已撤回的引用**:"给 MC 447.2s vs 不给 MC 10.3s(43 倍)"同属那批
491
+ 装置有缺陷的探针、**运行账本无对应臂**、且与同批账号里"仅 MC 收尾 0.5s"方向相反
492
+ ⇒ 同样**退出证据位**("归因混淆"属假说,不追查:追查成本 > 价值,决策不依赖它)。
493
+ **根因修正(2026-09-14,仍有效)**:不止"模型 id 过期"——MC 源码
482
494
  `maxOutputTokens: historian?.maxTokens ?? 32000`,而 historian 模型是
483
495
  reasoning:true(推理流吃光上限 → "all reasoning, no text")。主 pi 把
484
- `historian.maxTokens` 抬到 131072 后**首跑即成功**(其 context.db #916:
485
- completed、输出 36954 > 旧上限 32000)。故原结论应限定为"**在该默认上限
486
- (32000)下不工作**";agents 会话(fork 大历史)在抬高上限后**未复测**。
487
- - **真场验证(零扩展)**:墙钟 **12m31s** / 32 次唤醒 / **每次唤醒 48.1s** /
488
- **收尾 ≈0%**(对照插件在场时 66–78%)/ **historian 0 次** / 并行度 2.42/3.0 /
489
- 档位对照 ✓。同一机制的对照:e2e19 71.6s·17m19s、e2e20 81.6s·20m34s、
490
- e2e21 **330s·1h13m**(historian 风暴 62 次 60 失败)。
496
+ `historian.maxTokens` 抬到 131072 后首跑即成功。故原结论限定为"**在该默认上限
497
+ (32000)下不工作**";agents 会话(fork 大历史)在抬高上限后**未复测**
498
+ (只对 `all` 档有意义——默认档结构上不跑 historian)。
499
+ - **真场验证(零扩展,e2e23)**:32 次唤醒 / **每次唤醒进程跨度 48.1s** /
500
+ **收尾 ≈0%**(对照插件在场时 66–78%)/ historian **0 次** / 并行度 2.42/3.0 /
501
+ 档位对照 ✓。
502
+ *口径注记*:该场墙钟曾记 **12m31s**,**源不可复核**;按同批可核数字反算
503
+ (Σ进程跨度 1538s ÷ 并行度 2.42)≈ **10m36s**。引用请带这层限定。
504
+ 同机制的其他场次(各自 n=1,**均为"场次墙钟 / 每次唤醒跨度"**):
505
+ e2e19 17m19s·71.6s、e2e20 20m34s·81.6s、e2e21 **1h13m·330s**。
491
506
 
492
507
  **做法(三档;值域/默认值的家 = `meeting_fs.EXTENSION_POLICIES` /
493
508
  `DEFAULT_EXTENSION_POLICY`,协议字段 `extensionPolicy`,CLI
@@ -495,7 +510,7 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
495
510
 
496
511
  | 档 | 唤醒命令 | 语义 |
497
512
  |---|---|---|
498
- | `mc-tools`(默认) | 四个 `--no-*` + `-e <MC subagent-entry.js>` + `-e <pi-mcp-adapter 入口>`(任一份入口缺失即**部分降级**:缺谁少谁、都可见;全缺 = 等价 none)
513
+ | `mc-tools`(默认) | 四个 `--no-*` + `-e <MC subagent-entry.js>` + `-e builtin:mcp`(MC 入口缺 → 可见降级:少它那份 `-e`、生效=none;内置 MCP 恒在)
499
514
  | `none` | 四个 `--no-*` | 零扩展:最快、**零依赖** |
500
515
  | `all` | 不加任何 `--no-*` | pi 默认发现(A/B 与显式 opt-in)|
501
516
 
@@ -503,11 +518,27 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
503
518
  是必要的 background 补充”):**fork 本身是主背景通道**(agent 继承主会话的开发
504
519
  上下文),覆盖面受 ① fork 源内容 ② `budget` 裁剪比例限制;`ctx_search` 是它的
505
520
  **兜底**(按需检索记忆/文档/历史)。MC 的
506
- `subagent-entry.js`(MC **自己**给它的"搜索类子代理"用的入口)**只注册工具、
507
- 不装任何 hook** → 给 agents 按需检索能力(memories / docs / 历史),
508
- **不含** historian/压缩/打标。实测:`ctx_search` 可用 ✓;historian 0/6 ✓;
509
- **成本未测得显著差异**(受控探针 n 小、组内方差 > 组间差 ✗;生产基线:首次真场
510
- strict=1、n=19 → 唤醒启动段中位 **0.68s**、收尾中位 0.04s ✓)。
521
+ **两份入口的结构句与检测覆盖面(2026-09-27 复盘审计订正;2026-09-30 第二份改为内置)**:
522
+ - **MC 的 `subagent-entry.js`**(MC 自己给"搜索类子代理"用的入口)= **工具注册 +
523
+ 两个生命周期钩子**(`session_start` 开 DB / `session_shutdown` 关 DB),**没有**
524
+ historian / 压缩 / 打标类执行钩子 → agents 得到按需检索能力(memories / docs /
525
+ 历史)。*(此前写作"只注册工具、不装任何 hook",与上游 0.43.2 源码不符,已订正。)*
526
+ **检测覆盖面三面**:唤醒命令的**启动段**、报告**收尾列**、MC 自己的 `context.db` 账本。
527
+ - **pi 内置 MCP 扩展**(`builtin:mcp`)= 在 **`session_start`** 建立连接(上游
528
+ `extensions/mcp/index.ts` 的 `pi.on("session_start", …)`),连接后按服务器逐个
529
+ `registerTool` ⇒ 成本落在**启动段**("首个 prompt 最多等 10 秒"是上游文档写明的
530
+ 上界;先前第三方 adapter 的实测边际是 **+0.2s/唤**,内置实现**待测**——见 §假与口径)。
531
+ **兜底 = 换档**(要零扩展就显式 `--extension-policy none`)。
532
+ - **重估触发**:上游 MC / pi **升版**,或报告**启动段/尾列出现分钟级离群** → 重核
533
+ (MC:`session_start`/`session_shutdown` 内是否新增工作;MCP:连接时长与工具注册数)。
534
+ - **探针双向句**:那 2 臂探针(16–23s vs 4s)验的是**能力与卡死/收尾**,
535
+ **不测钩子成本**。
536
+
537
+ **真场计数**:historian **0**(e2e24 0/3、e2e25 0、e2e23 零扩展 0);`ctx_search`
538
+ 可用 ✓;**成本未测得显著差异**(受控探针 n 小、组内方差 > 组间差 ✗;生产基线:
539
+ 首次真场 strict=1、n=19 → 唤醒启动段中位 **0.68s**、收尾中位 0.04s ✓)。
540
+ *口径项*:fork 源随唤醒增长(构建时 606 条 / 1.40MB → 读数时 729–800 条 /
541
+ 1.44–1.56MB)——**是文件在涨,不是时间在涨**,不构成成本项。
511
542
  **入口解析 fail-fast**(`resolve_mc_tools_entry`:pi 的 packages → MC 包 →
512
543
  它声明的扩展入口 → 同目录 `subagent-entry.js`);缺 MC 时**可见降级**(严格模式
513
544
  `MV_MC_TOOLS_STRICT=1` 才报错退出)。
@@ -519,21 +550,84 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
519
550
  (E2;与"档位"同型),否则降级只在 loop log 里、产品面看不见。
520
551
 
521
552
  **使用指引(2026-09-25)**:协议模板里有一节「需要项目历史时」告诉 agents何时用 `ctx_search`(并写明"项目文件优先、记忆可能过期")——**它只在工具真会到位时出现**(策略为 mc-tools 且入口可解析),降级/零扩展时整节消失(不留空指引)。起因:e2e25 自然使用观察里三 agent 自发调用 **0 次**;加装指引后需在下一次"任务书不提工具"的场次里复测计数(utility 无客观判据,只做计数 + 抽看)。
522
- **两份入口(2026-09-25 用户裁决 B)**:`mc-tools` 除 MC 的只读检索工具外,再显式
523
- `-e` 加载 **pi-mcp-adapter**(web_search / web_reader / zread 等 MCP 工具)。
553
+ **数字口径规则(2026-09-27 定,审计产出)**:时间/成本数字必须带 **span · 测点 ·
554
+ 规模 · n**(例:"收尾段,同一份 1.9MB session,n=1");**增长量带时点**("构建时
555
+ 606 条 → 读数时 729–800 条");**无源或不可复核的数字一律标注**("无源"/
556
+ "不可复核"),不得直接引用。
557
+
558
+ **回退判据(单行规则,2026-09-27 定)**:自 **2026-09-25**(装置变更:加协议
559
+ 指引 + 第二份入口)起,连续两场「**需求场景可证在 fork 窗口之外**」且 utility≈0
560
+ → 提议回退 `none`(**需用户拍板**)。两入口**分列计数**(`ctx_search` / MCP 工具);
561
+ 这是**能力取舍判据、不是性能开关**;数据来源 = 各场 `result.md` 的元信息
562
+ (不设计数器/监控)。*前置限定不可省*——否则会把"需求不在窗口内"当成负证据
563
+ (2026-09-27 那场即此情形:主题所需事实全在窗口/仓库内)。
564
+ **两份入口(2026-09-25 用户裁决 B;2026-09-30 第二份改为 pi 内置 MCP)**:
565
+ `mc-tools` 除 MC 的只读检索工具外,再显式 `-e builtin:mcp` 得到 MCP 工具
566
+ (web_search / web_reader / zread 等)。**为什么改成内置**(用户 2026-09-30:
567
+ "pi 原生支持 mcp,不需要另装插件了"):pi 0.99+ 自带 MCP 扩展,配置写在
568
+ `~/.pi/agent/mcp.json`(或项目 `.pi/mcp.json`)、工具名 `mcp__<server>__<tool>`;
569
+ 换成它 = **去掉一个第三方依赖**(`pi-mcp-adapter` 的入口解析/版本漂移全没了,
570
+ `resolve_mcp_adapter_entry` 已删)。
524
571
  **语义清单(唯一权威段,别处引用不复述)**:
525
- ① 两份入口**各自独立降级**(缺谁少谁)、**都可见**、**允许而非要求**——缺入口
526
- 不阻断分析(机器上没装其中之一照样能跑);
527
- ② `MV_MC_TOOLS_STRICT=1`(测试/探针保真)→ **任一**入口缺失即报错退出
528
- (否则测试可能在"没装某入口"的环境里通过,而该工具从未生效);
529
- ③ 生效值语义:**部分降级仍 `生效=mc-tools`**(只是少了那份 `-e`),
530
- 两份全失才 `生效=none`;
531
- ④ 为什么必须显式 `-e`:`--no-extensions` 关的是**扩展发现**,显式路径照常生效
532
- (pi `--help` 原文)——不加载就等于 agents 完全失去该能力(MCP 那侧 = 失去
533
- 联网检索)。
534
- 不新增档位(保持简单)。`none` 零依赖(无 MC/adapter 的机器/CI 显式选它)。
535
-
536
- **依赖边界**:见上方清单 ①(允许而非要求、缺谁少谁、都可见)——本段不再复述。
572
+ ① MC 入口**允许而非要求**:解析失败不阻断分析(缺 MC 的机器照样跑)——它现在是
573
+ **唯一"可失败"的入口**;内置 MCP 是常量入口、恒在(无第三方、无解析)。
574
+ ② `MV_MC_TOOLS_STRICT=1`(测试/探针保真)→ MC 入口缺失即报错退出
575
+ (否则测试可能在"没装 MC"的环境里通过,而 `ctx_search` 从未生效);
576
+ 内置 MCP 不参与该判定(名字由 pi 注册、无需解析)。
577
+ ③ 生效值语义:**以本档核心能力为准** —— MC 入口解析成功 → `生效=mc-tools`;失败 →
578
+ `生效=none`(本档核心 `ctx_search` 缺失);原因字段 = **入口层失败原因**(不写
579
+ 工具可用性——那件事本档观测不到,见下方「平台能力的可见性」)。
580
+ *(旧措辞「部分降级仍生效=mc-tools」出自「两份入口都会被解析」的时代,已随第二份
581
+ 改为常量入口而失效。)*
582
+ ④ 为什么必须显式 `-e`:`--no-extensions` 关的是"扩展发现**与内置扩展**"
583
+ (pi `--help` 原文)——内置 MCP 也在关停范围,不显式加载 = agents 完全没有
584
+ MCP 工具;`-e <path>` 接受 `builtin:<name>`(同一份 help)。上游证据:
585
+ `core/extensions/index.ts` 里 `{ name: "mcp", builtin: true }`,
586
+ `core/resource-loader.ts` 在 `noExtensions` 时只保留 CLI 显式 `-e` 的扩展。
587
+ ⑤ **exposure 是配置层的事**(不属于本档):`mcp.json` 里每个 server 的
588
+ `exposure` 默认 `codemode`(工具**不声明给模型**,只能从 codemode 脚本调用);
589
+ 要让 agents 直接调用(= 先前 adapter 的体验)需在 `mcp.json` 写
590
+ `"exposure": "direct"`——**该文件是用户级配置、也影响主 pi**(2026-09-30 用户裁
591
+ 决选 direct)。
592
+ 不新增档位(保持简单)。`none` 零依赖(无 MC 的机器/CI 显式选它)。
593
+ **实测(2026-09-30 真场,38 次唤醒)**:内置 MCP 的**每唤醒成本 ≈ 0**——逐唤醒「启
594
+ 动前」中位 **−1.3s**(测量偏置)、三 agent 合计 −2s / −5s / +19s、收尾中位 1.1s;
595
+ 对照 AFT 的 445.9s 与 MC historian 的 447s,差三个数量级。**唯一离群**:一次 19s 启
596
+ 动(未归因,不排除 provider 抖动)。
597
+ **平台能力的可见性(2026-09-30 自审产出;含 4 条不变式)**:
598
+ - **前提**:本档依赖 **pi ≥ 0.99**(`builtin:mcp` 与 `pi mcp` 子命令自该版本)。更早的 pi
599
+ **不是静默降级而是硬失败**:未知 builtin 名 → `Unknown built-in extension`
600
+ (`core/resource-loader.ts`)→ 诊断 → `process.exit(1)`(`main.ts`)⇒ 每次唤醒 rc=1。
601
+ - **不变式 ①**:`meeting_loop` 里**两处守卫拦不同失效模式**,不可互相替代 —— 值域检查拦
602
+ **元组之外**的值;`else: raise` 拦**元组之内、但无分支**的值。别再当死代码删(删掉后新
603
+ 策略值会静默落进 `all` 档 = 分钟×N 级且无信号)。
604
+ - **不变式 ②**:分析目录里的**运行快照只含 4 个模块**(meeting_loop / fs / core / engine),
605
+ **`observability` 不在快照里** ⇒ `--report` 永远用**主仓今天**的代码读**当时** writer 写的
606
+ loop 日志(reader/writer 跨版本解耦)。判定条件必须容得下"旧 writer 产出 d==e 且
607
+ reason 非空"(`or reason` 的存在理由)。
608
+ - **不变式 ③**:**非交互模式下 MCP server 状态不进任何产物** —— 单 server 失败被
609
+ `Promise.allSettled` 吞掉,只走 `ctx.ui.notify`,而 `--mode json --print` 用的是
610
+ `noOpUIContext`(`core/extensions/runner.ts` 的 `notify: () => {}`);session 也不落工具集。
611
+ ⇒ **`rc=0` ≠ 工具可用**;报告写「**平台能力:未观测**」(缺席≠0 的同一纪律)。
612
+ - **登记(既定路径,非行动项)**:真需要机器判据时用 **`pi mcp list --json`**(上游**文档化
613
+ 结构接口**;字段 `state`/`tools`/`error`,任一 enabled server 非 `connected` ⇒ 非零退出)。
614
+ 触发条件 = 用户报"agents 没搜到" 或 上游给出其它可读信号。做法:`--start` 时采一次 → 落盘
615
+ 分析目录 → `--report` 读文件(**探针在组合层/start 层,不进 observability**——报告必须
616
+ 保持"零插桩事后推导")。代价:常态 **+2.7–2.8s**(n=3;复测 4.6s),最坏 +T(我们侧
617
+ 超时,T ≤ 10s)。取值三态 **ok / 异常(点名 server)/ 未验证**,**禁写"不可用"**(我们只知
618
+ "此刻不可达")。护栏:超时 + fail-open、可注入(默认单测不得真连远端)、采样须以
619
+ **cwd = fork_cwd** 启动(项目级 `.pi/mcp.json` + trust 会改变 agents 所见,别把近似当事实)、
620
+ 文档写明只覆盖**持续性**失效(不覆盖"启动正常、中途断")。**两条已证伪的错路**:成功路径
621
+ stderr(notify 是 no-op)与 session 文件(工具集不上盘)——别再捡。
622
+ - **exposure 的成本与口径**:`direct` 的代价实测 = **+1,025 tok/请求**(差值口径;n=2,
623
+ 配置 = 3 server / 5 工具全 direct;人口 = agent 侧探针会话;**机制外推**:主 pi 每请求同量级
624
+ ≈ +1k,**未测**)。数字随 server 数 / exposure 变化而作废;写**差值**不写绝对值。
625
+ - **三档边界**(可复用的判据):① 上游**文档化结构接口**(`pi mcp list --json` 字段、
626
+ session 条目)→ **可作机器判据**,字段缺失/形状变 ⇒ 记"未知";② 上游**人类 prose**
627
+ (stderr / notify 文案)→ **只落盘留痕、不做分支**;③ 上游**内部布局**(第三方包
628
+ `dist/*.js`)→ **不碰**(本次已删净)。
629
+ **依赖边界**:见上方清单 ①(MC 允许而非要求;内置 MCP 是 pi 自带)——本段不再复述。
630
+ 第三方依赖为零:本档只用 pi 内置扩展 + 用户自己选的 MC 包。
537
631
  **死代码纪律**:`--extensions` 别名与 `extensions: true` 历史字段(只存在约 1 天)
538
632
  **已删净**(无移除条件的兼容层不留);扩展策略只有一个入口:`--extension-policy`
539
633
  + 协议字段 `extensionPolicy`。
@@ -557,6 +651,10 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
557
651
  (① 职责归属怎么变 ② 净收益数据来源 ③ 新增失败面与检测延迟),不得直接实施。
558
652
  将来若要为 agents 加回任何扩展,须**显式 opt-in + 净收益账**(本次实证:
559
653
  AFT/MC 两次都是"加了才知道贵")。
654
+ **加回 AFT 的定价程序(2026-09-27 定,一句话,不另开节)**:任何加回 AFT 的
655
+ 提案须在**当前条件**下做 **1 次受控唤醒**(硬超时、用户当次同意)+ **收尾段
656
+ 实测** + 净收益账,**不得引用 2026-09-13 那批单点数字**(已退出证据位,见决策
657
+ 20 证据段的撤回记录);反证到手前按预期量级 **≈ 1.4–2.1h/场加在关键链** 予以否决。
560
658
 
561
659
  21. **git 守卫范围 = 从讨论 workdir 发起的操作**(`GIT_CEILING_DIRECTORIES`
562
660
  注入于 spawn);主项目仓库不在守卫范围(agent 的 cwd 就是主项目,其
@@ -0,0 +1,158 @@
1
+ <!-- 存档:docs/reviews/2026-09-27-aft-mc-evidence-audit.md
2
+ 来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
3
+ 分析场次目录已随 cleanup 删除;文中消息编号不可再核验,仅作溯源线索
4
+ (与代码注释引用约定一致:行为以自描述为准)。 -->
5
+
6
+ # 存档说明
7
+
8
+ - **主题**:复盘 2026-09-13 前后「屏蔽 AFT」的决策——当时的对照数据、结论链,
9
+ 以及现在的默认扩展策略(`mc-tools`)是否仍成立
10
+ - **场次**:`mv-mv-main-20260927-134822`(3 视角:效率 / 简单 / 铁律;真实 pi 讨论;
11
+ `forkMode=budget`、`extensionPolicy=mc-tools`(声明=生效)、配额 **meeting=20**(走
12
+ `/multi-viewers-config` 设的默认值)、`maxRR=7`;墙钟 22m34s、39 条消息、
13
+ 35 次唤醒(9/17/9)、**rc≠0 0 次 / retry 0 次 / 收尾 ≈0**;共识收敛)
14
+ - **判定**:**默认扩展策略成立** ✓——承重证据已换成 A 类真场(AFT:e2e21 收尾占
15
+ 66–78% vs e2e23 ≈0%;MC:e2e21 同场 historian 62 次/60 失败 vs e2e23 0 次 +
16
+ 上游 `context.db` #916 的 32000 上限根因)
17
+ - **本场最重的一条(审计自身证据链)**:`docs/test-methodology.md` #26 在 09-14
18
+ 已记「那批数据……后来发现装置有缺陷」,而 `docs/design.md` 决策 20 **仍把
19
+ 445.9s vs 0.5s / 447.2s vs 10.3s 当“受控对照”引用**(引用方与纪律方自相矛盾)
20
+ → 两笔**退出证据位**(原始分段查无、n=1、账本无对应臂、与同批“仅 MC 收尾 0.5s”
21
+ 方向相反),承重证据改指真场;决策本身不受影响
22
+ - **其余订正**:MC 的 `subagent-entry` 是“工具注册 + 2 个生命周期钩子”(非“不装
23
+ 任何 hook”);adapter 有 7 个钩子、**每轮钩子成本 = 已知盲区**(兜底 = 入口可独立
24
+ 移除);e2e23 的“12m31s”标为**源不可复核**并按可核数字反算 ≈10m36s;新增
25
+ **数字口径规则**(span·测点·规模·n;增长量带时点;无源数字必须标注)
26
+ - **新规则**:**回退判据**(自 2026-09-25 起连续两场“需求场景可证在 fork 窗口外”且
27
+ utility≈0 → 提议回退 `none`;两入口分列计数;**能力取舍判据、非性能开关**)+
28
+ **加回 AFT 的定价程序**(须在当前条件下受控实测,不得引用 09-13 单点)
29
+ - **本场的 `ctx_search` 观察**(第一次非诱导调用):效率 3 / 简单 0 / 铁律 3,全部
30
+ 用于核查探针数字的原始记录、**均未命中** ⇒ **本场不构成有效效用样本**(所需事实
31
+ 在 fork 窗口/仓库内)——这正是“回退判据必须带前置限定”的由来
32
+ - **落地**:`ffe28aa`(批 A 文档 + 批 B 注释;491 python + 54 harness 全绿)。
33
+ §四 的代码待办(档位分派穷尽化 + `all` 档形状测试、报告两 span 各打一行)未做,
34
+ 留作独立排期
35
+
36
+ # 多视角分析结果:复盘 2026-09-13 前后「屏蔽 AFT」的决策——对照数据、结论链、默认扩展策略是否仍成立
37
+
38
+ - **参与者**:效率、简单、铁律(3 视角;resultWriter = 铁律)
39
+ - **分析类型**:复盘审计(只读:未改文件、未跑 LLM;主仓 `git status` 干净)
40
+ - **场次**:`mv-mv-main-20260927-134822`(2026-09-27;3 视角真实讨论)
41
+ - **终止**:共识(RR 全员 pass)
42
+ - **约束**:本轮**只提意见、不改代码**——§四全部动作为**建议(未落地)**。
43
+
44
+ **三问直答**:
45
+ 1. **当时的对照数据**:见 §一(一张表;其中两笔探针对照退出证据位,标"待考");
46
+ 2. **结论链**:见 §二(六步,每步一个承重事实);
47
+ 3. **默认扩展策略是否仍成立**:**成立**——承重事实 = **构造 + A 类真场**(§三);另有两条未了账与一个已知盲区(§三.2/3.3)。
48
+
49
+ ---
50
+
51
+ ## 一、对照数据(一张表;"证据位"一列,承重位 = A 类行)
52
+
53
+ | # | 对照 | 数字 | 口径 / 来源(span · 规模 · n · 时点) | 证据位 |
54
+ |---|---|---|---|---|
55
+ | ① | AFT 语义搜索隔离(09-12,决策 19) | 开 **61.0/60.9/61.0s**(n=3)vs 关 **3.3–4.4s**(n=5);零扩展基线 2.2s;e2e17 33 唤 ≈ **22–25%** 墙钟 | 逐扩展隔离 + 跨项目复现;生产 e2e17 | 历史(该机制已退役) |
56
+ | ② | AFT 收尾(09-13 探针) | **445.9s vs 0.5s** | **收尾段**;同一份 1.9MB 真 session、同模型、极小任务;**n=1**(账本 A 458s / B 7s 总时长可对);提示词形态未记录 | **待考**(原始记录缺失) |
57
+ | ③ | MC historian(09-13 探针) | **447.2s**(435.5s = 3 次连续失败、尾部 97%)**vs 10.3s(43×)** | 同 fork 源、极小任务;**账本无对应臂**;与 B 臂(仅 MC 收尾 0.5s)方向相反 | **待考**(同上) |
58
+ | ④ | 场次对照(生产,各 n=1) | e2e19 **17m19s**·71.6s/唤;e2e20 **20m34s**·81.6s/唤;e2e21 **1h13m·330s/唤**(historian **62 次/60 失败**;收尾 66–78%);e2e23 **636s≈10.6m·48.1s/唤·收尾 0%** | 生产;e2e23 墙钟 = 首末 commit 636s;并行度 2.42 = 1538/636 | **A 类(承重)** |
59
+ | ⑤ | 零扩展真场(e2e23) | 32 唤 · 48.1s/唤 · 收尾 0% · historian 0 · 并行度 2.42 | 同 ④。**注**:`design.md:487` 的 **12m31s 为无源口径**(且与 2.42 混用两个 span:1538/751=2.05 ✗)→ 改可复核的 636s/10.6m | **A 类(承重)** |
60
+ | ⑥ | mc-tools 生产基线(现默认档) | e2e24(strict=1,**n=19**):启动中位 **0.68s**(max 0.75)/ 收尾中位 **0.04s**(max 0.10)/ historian **0/3**;e2e25:historian **0**、ctx_search **自然 0 次**;09-25 复核:单入口 46 唤 vs 双入口 24 唤 → 启动中位 **0.60 → ≈0.80s**、尾 ≈0 ⇒ 第二入口**边际 ≈ +0.2s/唤** | 生产单场 + 09-25 跨场单对照;探针 `historian 0/6`、`中位 7.8/9.2s` 属待考(与方法论 #26 的装置缺陷记录同批) | **A 类(承重)**;探针数字降**待考** |
61
+ | ⑦ | 构造事实(静态核对) | `--no-extensions` **只关发现**、显式 `-e` 照常生效(pi --help);MC 0.43.2 `subagent-entry` = **工具注册 + `session_start`/`session_shutdown`(开/关 DB)**、**无 historian/压缩执行钩子**;MCP adapter 2.38.0 = **7 个钩子**(含 `input`/`tool_result`/`before_agent_start` 等**每轮执行**钩子);本场登记行 `声明=mc-tools 生效=mc-tools strict=0` | 上游源码(本机版本)+ pi --help + 本场 loop 日志 | **A 类(承重;上游升版需重核)** |
62
+
63
+ **成本口径三层(引用时不得省略)**:**段**(启动/收尾,不是整场)· **规模**(n、场次)· **关键链**(agent 并行 ⇒ 墙钟 ≈ 最慢链;当前 ≈ **7–15s/场 ≈ 0.5–2%**;不要把 0.7s × 总唤醒数 × 3 agent,会虚高约 3 倍)。另有规则:随时间增长的量(fork 源、AFT 存储、`context.db`、重试计数)必须带**时点**;无源数字标"无源/不可复核"。
64
+
65
+ **本场现场事实(口径项,非成本项)**:fork 源 = 各 agent 的活动 session 文件,随唤醒增长——构建时 **606 条 / 1.40MB**(est≈80k)→ 读数时(06:04Z)729–800 条 / 1.44–1.56MB;该增幅不改任何环节量级。
66
+
67
+ ---
68
+
69
+ ## 二、结论链(六步,每步一个承重事实)
70
+
71
+ 1. **前提(设计事实)**:`meeting_loop` **等 pi 进程退出才继续** ⇒ 收尾段全额进用户等待;"进程退出前多活多久"直接等于墙钟。
72
+ 2. **09-12 决策 19**:AFT 语义搜索让每进程多活 **57s**(①)→ e2e17 33 唤 ≈ **22–25%** 墙钟 → 用 XDG 作用域配置关语义搜索(不动用户配置、主 pi 不受影响)。
73
+ 3. **09-13 决策 20 第一步(屏蔽 AFT、暂留 MC)**:插件在场的大 session 出现**分钟级尾巴**——生产收尾 **66–78%**(e2e21:11–12 唤进程总跨度 3676/3681/3709s,收尾 2769/2881/2437s)+ 探针 445.9s vs 0.5s(②)→ 屏蔽 AFT。
74
+ 4. **09-13 同日再改零扩展**:MC 的 historian 也在关键路径上(③ 的探针 + ④ 的 e2e21 风暴)→ `--no-extensions`(决策 20 最终形态);真场验证 636s/10.6m · 48.1s/唤 · 收尾 0%(⑤)。
75
+ 5. **09-14 根因修正 + 定默认 `mc-tools`**:historian 失败根因**至少含默认上限 32000**(reasoning 流吃光上限;主 pi 抬到 131072 后首跑成功——MC `context.db #916`:completed、输出 36954)⇒ 原结论限定为"**在该默认上限下不工作**";agents 侧未复测。同日用户裁定默认 `mc-tools`(理由是**能力**:`ctx_search` 补 fork 窗口覆盖;"允许而非要求"、缺谁少谁**可见降级**)→ 探针 + 生产(⑥)证明**成本有界**。
76
+ 6. **09-25 补第二入口 + 协议指引**:MCP adapter 并入默认档(恢复文件/git 之外的联网检索;边际 +0.2s/唤)+ 协议加「需要项目历史时」一节(**仅在工具有效时出现**);自然使用复测 0 次——但需求场景未出现(见 §三.2)。
77
+
78
+ **现状综合**:默认档固定成本 ≈ **1–2% 墙钟**(关键链口径),被移除的三笔(57s/唤、445.9s/次、435s/3 次)**高 2 个数量级** ⇒ 决策实质**不是"省 1%",而是把无上界成本换成有上界成本**(AFT 尾巴随自身存储增长;historian 随未处理历史量放大)。
79
+
80
+ ---
81
+
82
+ ## 三、「现在的默认扩展策略是否仍成立」判定
83
+
84
+ ### 3.1 成立(承重事实 = 构造 + A 类真场)
85
+
86
+ - **(A) AFT 被结构性排除**:唤醒命令 = 四个 `--no-*` + **仅两份 `-e` 白名单**;`--no-extensions` 只关发现、显式 `-e` 照常生效(pi --help)⇒ 连加载都不加载(与"配置写没写对"无关,但与**我们的命令组装正确**相关——none/mc-tools 两档已有形状测试,all 档形状测试见 §四③ 待办)。
87
+ - **(B) MC 侧不带 historian/压缩执行钩子**:现行 entry 只有工具注册 + 两个生命周期钩子(开/关 DB)。**注意措辞精确**:不是"不装任何 hook"(该句与现行 0.43.2 不符,属文档漂移);adapter 侧有 7 个钩子(含每轮执行),但实测边际 +0.2s/唤、尾 ≈0。
88
+ - **(C) 成本有界**:启动 0.6–0.9s/唤、收尾 ≈0;关键链 ≈7–15s/场 ≈0.5–2% 墙钟。
89
+ - **(D) 可观测**:登记行"声明 vs 生效"(含降级原因)+ 报告收尾列 + MC 自己 DB 账本 ⇒ 入口缺失/降级不静默。
90
+
91
+ ### 3.2 两条未了账(不阻塞默认档,但需入档)
92
+
93
+ 1. **agents 侧 historian@131072 未复测**——只对 `all` 档有意义(默认档结构上不跑 historian);**默认档不必等这个悬案**。
94
+ 2. **`ctx_search` utility 无有效样本**:e2e24 的 9 次全部由问卷诱导(0 次决定性帮助、1 例过期记忆);e2e25 自然 0 次;**本场也不构成有效样本**(主题所需事实全在 fork 窗口/仓库内——本场只有 6 次调用,全部用于查证探针数字的原始记录,均未命中,见 §六)。
95
+ ⇒ 回退判据须加**前置限定**:"需求场景可证在 fork 窗口之外"的场次才入账;否则会拿"需求不在窗口内"当负证据。
96
+
97
+ ### 3.3 已知盲区(写清比假装有探测器好)
98
+
99
+ **adapter 每轮钩子成本不可分**:其 5 个按事件触发的钩子(`input`/`tool_result`/`before_agent_start`/`session_tree`/`resources_discover`)成本落在 **LLM 主导的事件内段**(中位 50–55s/唤),现有列(启动段/尾列)分辨不出其变化。**兜底 = 两份 `-e` 独立降级/可独立移除**(怀疑 adapter 即去掉第二条)。2 臂探针(16–23s vs 4s)**验的是能力与卡死/收尾,不隔离钩子成本**。
100
+
101
+ ---
102
+
103
+ ## 四、建议动作清单(**建议/未落地**;本轮未改动任何文件)
104
+
105
+ ### ① 文档(权威 = `docs/design.md` 决策 20 + `AGENTS.md`)
106
+
107
+ 1. **MC 证据位与复述清理**:MC 结论改挂 A 类真场(e2e21 vs e2e23 + MC DB 账本 + 09-14 根因修正段);探针数字(447.2/10.3、445.9/0.5)在 decision 20 只留**一句"不作为证据"**(四要素:原始记录查无〔2026-09-27 审计:仓库/运行账本/记忆三处〕;账本无对应臂;与 B 臂〔仅 MC 收尾 0.5s〕方向相反;**可能归因混淆 = 假说**);**三处复述删**(`design.md:479-480`、`AGENTS.md:80`、`tests/test_meeting_loop.py:737-742` 三条 bullet → 结构句 + 指针);`design.md:508` 的 `historian 0/6` 换"结构句 + 真场计数(e2e24 0/3、e2e25 0)"。
108
+ 2. **`design.md:487` 的 12m31s** → 改可复核口径 **636s/10.6m**(或标 span 并注明"源不可复核");并落一行规则:**时间/成本数字带 span·测点·规模·n;增长量带时点;无源数字标"无源/不可复核"**。
109
+ 3. **结构句与重估触发按入口分写**:
110
+ - MC:工具 + 2 个生命周期钩子(开/关 DB);**检测覆盖面三面**(启动段/尾列/账本);
111
+ - adapter:7 个钩子(含每轮);**仅 init 被启动段覆盖、每轮钩子 = 已知盲区**;兜底 = 独立移除;
112
+ - 触发:**上游 MC/adapter 升版,或报告尾列出现分钟级离群 → 重核**(MC:`session_start`/`session_shutdown` 内是否新增工作;adapter:每轮钩子是否变重);
113
+ - 探针双向句:**2 臂探针 = 能力与卡死/收尾检查,不测钩子成本**。
114
+ 4. **语义清单 ③ 补语**:`生效 = 入口解析成功;命令形状由测试锁(none/mc-tools/all 各一)`(不新增编号项、不改字段名)。
115
+ 5. **回退判据(单行规则)**:自 **2026-09-25**(装置变更:加指引 + 第二份入口)起,连续两场"**需求场景可证在 fork 窗口外**"且 **utility≈0** → 提议回退 `none`(**需用户拍板**);**两入口分列计数**(`ctx_search` / MCP);写明"**能力取舍判据、不是性能开关**";数据来源 = 各场 result 元信息(不设计数器/监控)。
116
+ 6. **门槛条款补"加回 AFT 的定价程序"**(一句话,不另开节):任何加回 AFT 的提案须在**当前条件**下做 **1 次受控唤醒**(硬超时、用户当次同意)+ 收尾段实测 + 净收益账,**不得引用 09-13 单点**;兜底句:预期量级 ≈ **1.4–2.1h/场**加在关键链(拿到反证前按此否决)。
117
+ 7. **fork 源增幅 = 口径项、不是成本项**(一行,防把"文件在涨"读成"时间在涨")。
118
+
119
+ ### ② 注释(改的是代码文件,**不动行为**)
120
+
121
+ 8. `tests/test_meeting_loop.py:737-742` docstring 三条 bullet → **一行测试契约 + 指向 decision 20 的指针**(docstring 只写"本测试锁什么",不存放历史证据/设计理由)。
122
+ 9. `tests/test_main_paths.py:217` 死注释("`extensions: true` 走兼容路径"——该兼容路径已删,全仓仅此一句)**删**。
123
+
124
+ ### ③ 代码/测试待办(**不属本轮**,只记待办)
125
+
126
+ 10. **档位分派穷尽化**:`elif extension_policy == "all": pass` + `else: raise`(注释"值域增长时 fail-closed;当前守卫下不可达")+ **all 档形状测试**一行(命令既无 `--no-*` 也无 `-e`)。**不为不可达分支加测试**。
127
+ 11. (未来)报告按两个直标口径各打一行——**两个 span 必须各自命名**;属观测面契约变更,不在本轮。
128
+
129
+ ---
130
+
131
+ ## 五、争议数字与审计记录
132
+
133
+ - **447.2s vs 10.3s(43×)**:`design.md` 标"受控对照",与 `docs/test-methodology.md` #26(09-14)"那批数据……后来发现装置有缺陷;真正干净的证据只有生产真场与 MC 自己的 DB 账本"**互相矛盾**;三处查无原始记录;**两条算术旁证**(445.9/10.3=43.29、447.2/10.3=43.42;`435.5` + `≈10.3` ≈ `445.9`、两处"尾部 97%"同值)与**一条反证**(账本 B 臂"仅 MC"收尾 0.5s)⇒ "同一批记录被两种叙事分别引用、归因混淆"是**假说、不可裁决**。**处置:退出证据位、不追查**(追查成本 > 价值,结论不依赖它)。
134
+ - **445.9s vs 0.5s**:账本 A 458/B 7 总时长可对(收尾段口径),但 n=1 且提示词形态未记录 ⇒ **退出承重位(待考)**。
135
+ - **12m31s**:无源且与并行度 2.42 混用两个 span(1538/636=2.42 ✓ / 1538/751=2.05 ✗)⇒ 改 **636s/10.6m**(本报告 §一⑤ 已按此写)。
136
+ - **撤回/更正记录**(只改论据,不改结论):简单 4 处("三份值域判定=S1 同型"说 / "每唤醒 ×3 agent"成本账 / adapter"统一判据"措辞 / "收尾时"错误标签);效率 1 处("主会话 32.8MB vs 1.9MB ≈17×"的规模比较——agents 打开的是 fork 源,同量级);另有一处**措辞漂移**统一修正("entry 不装任何 hook"→ 有 `session_start`/`session_shutdown` 生命周期)。
137
+ - **出席**:效率 7 条分析消息 + freezing + pass;简单 16 条 + freezing + pass;铁律 6 条 + freezing + pass。**无缺席、无未表态者**;本报告不代表任何一方未表达过的立场。
138
+
139
+ ---
140
+
141
+ ## 六、本场运行元信息
142
+
143
+ | 项 | 值 | 来源 |
144
+ |---|---|---|
145
+ | 协议 | `mode=meeting`;`forkMode=budget`;`extensionPolicy=mc-tools`;配额 meeting=20 / rr=7 | `protocol.json` |
146
+ | 扩展策略登记行 | `声明=mc-tools 生效=mc-tools strict=0`(三 agent 一致) | loop 日志首唤 |
147
+ | fork 源(构建时) | 606 条 / 丢弃 53 / est≈80k(字符/3)/ 构建 521ms / 92MB 峰值(本视角 agent 的日志行;各 agent 构建耗时略有差异:317–525ms) | loop 日志 |
148
+ | 唤醒与跨度 | 效率 9 唤/1061s;简单 17 唤/972s;铁律 8 唤/621s(result 写作唤醒另计) | loop 日志 `elapsed_ms` |
149
+ | 墙钟(消息 commit 首末) | 13:48:22 → 14:08:27 ≈ **20m05s**(不含 result 写作) | 讨论仓 git log |
150
+ | 终止 | **consensus**(RR 全员 pass) | loop 日志 |
151
+ | 消息文件数 | 效率 10 / 简单 19 / 铁律 9(含 freezing、pass 与 loop 的 all-freezing 信号)⇒ 参与性表态:效率 9(7 消息+freezing+pass)/ 简单 18(16 消息+freezing+pass)/ 铁律 8(6 消息+freezing+pass) | 讨论仓 |
152
+ | **工具计数(边界之后,两入口分列)** | **效率:ctx_search 3 / MCP 0;简单:0 / 0;铁律:3 / 0** | 各 agent session 的 toolResult 记录(`meeting_fs.iter_after_boundary`) |
153
+ | **utility 抽看(一句)** | 6 次 ctx_search 全部服务于同一核查(探针数字的原始记录):**均未命中原始记录**(命中 historian 根因/阈值/方法论等记忆),无决定性帮助、未见陈旧知识注入 ⇒ **本场不构成有效效用样本**(所需事实在 fork 窗口/仓库内) | 结果摘要抽看 |
154
+ | 主仓改动 | **无**(`git status` 干净;本轮只读审计) | `git status` |
155
+
156
+ ---
157
+
158
+ *本报告为"意见轮"产物:§四动作均为**建议(未落地)**;争议数字的处置只涉及**文档表述**,默认扩展策略本身无需改动。*
@@ -0,0 +1,284 @@
1
+ <!-- 存档:docs/reviews/2026-09-30-builtin-mcp-audit.md
2
+ 来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
3
+ 分析场次目录已随 cleanup 删除;文中消息编号不可再核验,仅作溯源线索
4
+ (与代码注释引用约定一致:行为以自描述为准)。 -->
5
+
6
+ # 存档说明
7
+
8
+ - **主题**:自审 —— agents 的联网检索能力从第三方 `pi-mcp-adapter` 换成 pi 内置 MCP
9
+ (`-e builtin:mcp`)+ `exposure: direct`:这个改动站不站得住?有没有没想到的副作用?
10
+ - **场次**:`mv-mv-main-20260930-135705`(3 视角:效率 / 简单 / 铁律;真实 pi 讨论;
11
+ `forkMode=budget`、`extensionPolicy=mc-tools`(声明=生效,无降级)、配额 **meeting=20**
12
+ (用户的默认值配置)、`maxRR=7`;墙钟 **32m43s**、38 条消息、35 次唤醒(15/10/10)、
13
+ rc≠0 0 次 / retry 0 次、收尾中位 1.1s、存续 **共识**(RR 全体 pass)、并行度 2.34;
14
+ 对象 = 本仓 HEAD `af9ee53`)
15
+ - **判定**:**替换成立**(净简化 + 职责归还平台 + 第三方依赖清零);同批落地 9 项
16
+ 零运行时成本修正;**配置层不动**(三方撤回各自的配置建议——主 pi 实测在
17
+ 用全部三个 server:12 个 session / 13,144 次 toolCall 里 51 次 mcp 调用)
18
+ - **本场最重的发现(新问题)**:**per-server 静默失效** —— 某个 MCP server 连不上 /
19
+ 凭据过期时,agents 当次静默失去那件工具,分析照常跑完且产物零痕迹。链条(我逐处
20
+ 复核过上游源码):`Promise.allSettled` 吞掉失败(`extensions/mcp/index.ts`)→
21
+ `reportProblems` 只 `ctx.ui.notify`(不 exit)→ 非交互模式用 `noOpUIContext`
22
+ (`core/extensions/runner.ts` 的 `notify: () => {}`)→ 我方只在 rc≠0 时读 stderr
23
+ ⇒ **`rc=0` ≠ 工具可用**(我此前探针报告里的那条推论说满了,撤回)
24
+ - **本场同时验证**:`-e builtin:mcp` **真生效**(另一场功能臂真搜到网页标题);每唤醒
25
+ 成本 **≈ 0**(38 次唤醒「启动前」中位 −1.3s、三 agent 合计 −2s/−5s/+19s,对照 AFT
26
+ 445.9s 与 MC historian 447s 差三个数量级);agents **自然**用 MCP = **0 次**
27
+ (边界后逐条统计工具调用:bash 58/65/56、read 7/0/19、write 10/10/15、edit 4/1/0,
28
+ 无一条 `mcp__*`)
29
+ - **过程诚实记录**:三视角共撤回/更正 13 项(含两处**曾被判死代码的分支改判为留**、
30
+ 一处"唤醒成功 ⇒ MCP 已加载"的推论撤回、若干自己的配置建议撤回)
31
+ - **落地**:`839ef4f`(九项:删无据断言 / rc≠0 落 stderr / 报告「平台能力:未观测」/
32
+ 两条守卫注释改准 / `or reason` 真实理由 + 移除条件 / 三条不变式 / 前提 pi≥0.99 /
33
+ exposure 成本口径 / 值域三档边界;495 python + 54 harness 全绿);方法论 #27
34
+ (检测器不得继承被检测对象的失效面 · 多余的文字一旦断言未知即错误)
35
+
36
+ # 多视角分析结果:内置 MCP 替换(`-e builtin:mcp`)与 `exposure: direct` 自审
37
+
38
+ - **场次**:`mv-mv-main-20260930-135705`(效率 / 简单 / 铁律 三视角,模型 `…deepseek-v4.1-flash`, xhigh)
39
+ - **对象**:本仓 HEAD `af9ee53`(`feat(ext-policy)!: mc-tools 第二份入口改用 pi 内置 MCP(删第三方 adapter 依赖)`)
40
+ - **判定**:**替换成立,可以发布**;但必须同批完成 9 项零运行时成本的修正/落字(§6),
41
+ 且有一项新发现(per-server 静默失效,§3)需要在文档中如实声明。
42
+
43
+ ---
44
+
45
+ ## 1. 结论摘要
46
+
47
+ | # | 结论 | 状态 |
48
+ |---|---|---|
49
+ | 1 | 第二份入口从第三方 `pi-mcp-adapter` 换成 pi 内置 `-e builtin:mcp` = **净简化 + 职责归还平台** | 三视角一致支持 |
50
+ | 2 | `--no-extensions` 关的是"扩展发现**与内置扩展**" ⇒ **必须**显式 `-e builtin:mcp`(实现前提,已落字) | 已满足 |
51
+ | 3 | **新发现:per-server(连接级)失效在本形态下完全静默**——`rc=0 ≠ 工具可用` | 三视角一致(互补证据) |
52
+ | 4 | 观测能力位(MCP 探针):**本轮不做**,登记为"条件触发的既定路径" + 报告写"能力未观测" | 三方一致(B+登记) |
53
+ | 5 | 两处曾被判"死代码"的分支改判为 **留**(`else: raise` / `or reason`) | 三方一致 |
54
+ | 6 | 一处**无据断言**必须删(`meeting_loop.py:356` 半句,5 个副本) | 三方一致 |
55
+ | 7 | A 层三项(有墙钟成本)**无干净注入点 ⇒ 只文档化**;配置层改动 = **用户拍板项** | 三方一致 |
56
+ | 8 | 本场推荐改动集**不需要任何真实 LLM 运行**验证(单测 + harness,分钟级、0 LLM) | 三方一致 |
57
+
58
+ ---
59
+
60
+ ## 2. 替换成立的理由(三视角证据)
61
+
62
+ - **铁律(职责边界/复杂度)**:删掉 `resolve_mcp_adapter_entry`(19 行)+ `MCP_ADAPTER_PACKAGE` + 测试侧 6 处 mock,
63
+ 用一个**常量** `meeting_fs.BUILTIN_MCP_ENTRY = "builtin:mcp"`(`meeting_fs.py:903`)替代——
64
+ 从"解析第三方包内部布局"降为"引用平台标识",是**职责归还平台**;第三方依赖在本档清零
65
+ (本档 = pi 内置扩展 + 用户自选的 MC 包)。
66
+ - **简单(净简化)**:降级状态空间从"2 入口 × 各 on/off + 部分/完全标签"收敛为"1 个可失败入口";
67
+ 测试 mock 点 16 → 9;遗留符号全仓活代码 0 命中。
68
+ - **效率(成本)**:固定成本打平/略优——
69
+ - **token 差值 +1,025/请求**(探针 n=2,两臂各两次独立运行同值:含 = 105,334 / 不含 = 104,309;
70
+ 配置 = 3 server / 5 工具全 `direct`);
71
+ - **延迟边际 +0.3–0.8s/唤**(被组内方差淹没,与旧 adapter 的 +0.2s/唤同量级);
72
+ - **3 个 server 并行连接**(上游 `extensions/mcp/index.ts:816` `Promise.all`)⇒ 成本 = max(单 server),非 Σ。
73
+ - **功能验证(真跑一次,含内置 MCP 的臂)**:要求"用 MCP 搜一次"的臂真实取回网页标题
74
+ (`The Agent Harness Where the Coding Agent Extends Itself`,pyshine.com)⇒ `builtin:mcp` **确实生效**。
75
+ - **操作前提(已核 pi --help:43 原文 + 上游源码)**:`--no-extensions` 亦关内置扩展
76
+ (`core/extensions/index.ts` 的 `{ name: "mcp", builtin: true }`;`core/resource-loader.ts` 在
77
+ `noExtensions` 时只保留 CLI 显式 `-e`),且 `-e <path>` 接受 `builtin:<name>`。
78
+ - **未知 builtin 名 = loud + fatal**(核实三处源码):`core/resource-loader.ts:715-719`
79
+ (`Unknown built-in extension`)→ `main.ts:793-797` 诊断 → `main.ts:906-916` `process.exit(1)`。
80
+ ⇒ 因此 **pi < 0.99 不是静默降级,而是每次唤醒 rc=1 硬失败**(可见)。
81
+
82
+ ---
83
+
84
+ ## 3. 本场最重要的发现:**per-server 静默失效**(完整链条)
85
+
86
+ **结论:某个 MCP server 连不上 / OAuth 过期时,agents 当次唤醒静默失去联网工具,而分析照常跑完,
87
+ 报告里 rc≠0=0、收尾≈0、"生效=mc-tools" 一切干净。**
88
+
89
+ 链条(逐处上游源码 + 我方代码):
90
+
91
+ | 环节 | 位置 | 行为 |
92
+ |---|---|---|
93
+ | 单 server 连接结果 | `extensions/mcp/index.ts:816` `Promise.all` + **`:818` `Promise.allSettled(getClient)`** | 失败被 **吞掉**,不抛 |
94
+ | 连接失败/需认证 | `:408-419` `reportProblems` → `ctx.ui.notify(…, "warning")` | **不写 `runtime.diagnostics`** ⇒ 不 exit(对照 `main.ts:906-916`) |
95
+ | 10s 帽触发 | `:833-847`(`startupWaitMs` 默认 `:79`=10000) | 只 `ui.notify(..., "info")` |
96
+ | 整体加载失败 | `:820-826` / `:884` | 也只 `ui.notify(..., "error")` |
97
+ | **通知通道本身** | `core/extensions/runner.ts:323-327` `noOpUIContext.notify = () => {}`;`:565` 未传 ui 即用 noOp | **我们的模式(`--mode json` + `--print`)既非 rpc(`rpc-mode.ts:320`)也非 interactive(`interactive-mode.ts:2532`)⇒ notify 是 no-op** |
98
+ | 我方消费 | `meeting_loop.py:515-521` | stderr **只在 `rc≠0` 时**被读;`rc=0` 时丢弃(`_log_wake_done` 只记 session/elapsed/rc,`:527-537`) |
99
+ | 我方产物 | `wake-logs/*.txt`(`meeting_loop.py:491-498`) | 只有命令行全文 = 证"**请求**了 `-e builtin:mcp`",不证"加载成功" |
100
+ | 登记行 | `meeting_loop.py:367-376` | 由策略代码产出 = 证"**策略跑过**",不证任何 server 连接 |
101
+ | session 文件 | 本场三个 agent 会话逐条枚举 | 条目类型无工具集条目;`session` 头只有 fork 元数据;`toolNames`/`availableTools` 递归 **0 命中** ⇒ **生效工具集不上盘** |
102
+
103
+ **⇒ 合起来的完整链条:失败 → 不 exit → 只 notify → notify 是 no-op → 产物里没有任何痕迹。**
104
+ (三视角独立得到同一结论:铁律/0003 §3、简单/0005 §1、效率/0006 §1,证据互补。)
105
+
106
+ **附带修正**:`rc=0 ⇒ MCP 已加载` 的推论**不成立**——它只证明扩展加载器接受了
107
+ `builtin:mcp` 这个名字,**不证明任何 server 连上、任何工具可用**(简单/0005 §1 自撤回该推论)。
108
+
109
+ ---
110
+
111
+ ## 4. 观测裁决:本轮**不做**探针,改为"登记 + 声明"
112
+
113
+ **报告侧(零成本)**:显式写 **"平台能力:未观测(本档不采)"**,把不可见说出来。
114
+
115
+ **登记(非行动项,写入 `docs/design.md` 决策 20 的"盲区/既定路径"段)**——出现真正消费者时按此实现:
116
+
117
+ ```
118
+ 触发条件:用户报"agents 没搜到" / 上游给出工具注册的可读信号(例:pi mcp list 之外的可读出口)
119
+ 做法:pi mcp list --json 一次/场:--start 采集 → 落盘分析目录 → --report 读文件
120
+ (探针在组合层/start 层,不进 observability——保持"零插桩事后推导")
121
+ 代价:常态 +2.8s(实测 2.72–2.80s,n=3);最坏 +T(我们侧超时,T ≤ 10s)→ 记"未验证"
122
+ 取值三态:ok / 异常(state≠connected,点名 server)/ 未验证 —— 禁写"不可用"
123
+ 护栏:
124
+ 1 不进 observability(报告必须是纯事后推导,同目录两次报告一致)
125
+ 2 超时 + fail-open(超时/命令缺失 → "未验证",绝不阻塞或失败 run)
126
+ 3 三态写死,禁写"不可用"(我们只知道"此刻不可达",不知道"当时不可用")
127
+ 4 文档写明只覆盖持续性失效(凭据过期/端点下线),不覆盖"启动正常、中途断"的瞬断
128
+ 5 可注入/测试可短路(否则默认单测会真的连三个远端 server:见 §6 计数)
129
+ 6 采样必须以 cwd = fork_cwd(agents 的 spawn cwd)启动、继承 PI_CODING_AGENT_DIR
130
+ 边界依据:main.ts:585 process.cwd() → :610 runMcpCommand({cwd}) → cli.ts:190/196-197 读
131
+ <cwd>/.pi/mcp.json + trust;而 meeting_loop.py:393 spawn_cwd = fork_cwd or workdir
132
+ ⇒ 在仓库根采样,若被分析项目有 .pi/mcp.json,采样集 ≠ agents 所见(把近似当事实)
133
+ 两条已被证伪的错路(别再捡):
134
+ ① 成功路径 stderr —— notify 是 no-op,什么都没写
135
+ ② session 文件 —— 工具集不上盘
136
+ ```
137
+
138
+ **不做探针的理由(三方一致)**:无现实消费者(不会据此重试/中止/换档)+ 收益频率无数据
139
+ (`mcp.log` 不存在,无失败率口径)+ 会把整批从"**0 次 LLM 验证**"变成"需真跑 e2e 验证(20–40 min)"。
140
+
141
+ **平台状态查询能力(已核实,供将来实现)**:`pi mcp list [--json]` 是文档化子命令
142
+ (`extensions/mcp/cli.ts:37/70`),JSON 形如 `{servers:[…], errors:[…]}`(`:463-470`),
143
+ 每项带 `state` / `tools` / `error`;退出码语义 `:461`(有 config error 或任一 enabled server
144
+ `state !== "connected"` ⇒ 非零)。**属"文档化结构化接口",可作机器判据**(见 §9 三档边界)。
145
+
146
+ ---
147
+
148
+ ## 5. 两处"死代码"改判为**留**(本场主要的自我纠错成果)
149
+
150
+ ### 5.1 `else: raise`(`meeting_loop.py:361-366`)——留代码,只改注释
151
+
152
+ - **原判**(简单/0001 §2):与 `:326` 的守卫重复 ⇒ 不可达 ⇒ 删。
153
+ - **改判**:两处拦的是**两种不同失效模式**——`:326` 拦**元组之外**的值(用户传错),
154
+ `:361` 拦**元组之内、但无分支**的值(开发者加值忘加分支;此时 `:326` **不响**)。
155
+ - **删掉的代价**:新策略值会静默落进 `all` 档 = pi 默认发现 = 载入 AFT/MC 全档
156
+ ⇒ **分钟×N 级**、且无信号(成本不对称:保留 0 成本 vs 删除后最坏分钟×N)。
157
+ - **要改的**:`meeting_loop.py:362-363` 现注释"当前守卫下不可达"与同句"值域增长时这里响"
158
+ 自相矛盾——改为:
159
+ > 当 `EXTENSION_POLICIES` 增长而分派未同步加分支时在此响(元组**内**无分支 ≠ 元组**外**的值:
160
+ > 后者由 `:326` 的守卫拦);当前元组下不可达,**不是死代码**。
161
+
162
+ ### 5.2 `or reason`(`observability._report_extension_line`)——留分支,改测试论证 + 写移除条件
163
+
164
+ - **原判**(简单/0001 §3):现行 writer 下 `reason ⇒ d≠e`,故 `or reason` 永不改变结果 ⇒ 死判定;单测还"冻住"了它。
165
+ - **改判**:**reader/writer 版本结构性解耦**——
166
+ - 快照**只含 4 个模块**(`start_discussion.py:429-430`:meeting_loop/fs/core/engine),
167
+ **不含 observability**;而 `--report` 走**主仓**代码(`mv_cli.py:36,287-289` → `start_discussion.py:582`);
168
+ - ⇒ loop 日志由"分析启动那一刻的快照"写,报告由"今天的代码"读;
169
+ - `af9ee53^:361-362` 真的产出过 `声明=mc-tools 生效=mc-tools 降级原因=部分:…`(**d==e 且 reason 非空**)。
170
+ - **要求**:① 测试 docstring 改**真实理由**(跨版本回落,源行 `git show af9ee53^:meeting_loop.py:361-362`);
171
+ ② 补**移除条件**:"当不再存在 af9ee53 之前启动、且仍需 `--report` 的分析目录时可删"。
172
+
173
+ ---
174
+
175
+ ## 6. 必须修:一处**无据断言**(5 个副本同批改)
176
+
177
+ `meeting_loop.py:356` 的 `downgrade_reason = f"{err};内置 MCP 工具不受影响"` —— 在 §3 已证
178
+ "工具是否可用不可观测"的前提下,这是**断言了未知**("多余的文字一旦断言了未知,就从啰嗦升级为错误")。
179
+
180
+ **落点取"删"而非"改"**(理由:改写会在 5 处**再复述一遍**"入口"这个已在
181
+ `meeting_loop.py:343`、`meeting_fs.py:870/878` 单点声明过的事实;删则净减文字且假保证从仓库消失):
182
+
183
+ | # | 位置 | 动作 |
184
+ |---|---|---|
185
+ | 1 | `meeting_loop.py:356` | `downgrade_reason = err`(删半句) |
186
+ | 2 | `tests/test_main_paths.py:686` | fixture 文本同步删 |
187
+ | 3 | `tests/test_meeting_loop.py:862` | 断言同步删 |
188
+ | 4 | `tests/test_meeting_loop.py:798-804`(docstring) | "降级不牵连内置 MCP"改述为入口层或删 |
189
+ | 5 | `docs/design.md:578-579`(半句) | 删,保留"原因字段=入口层失败原因" |
190
+
191
+ ---
192
+
193
+ ## 7. A 层:有墙钟成本的三(+1)项 —— **只文档化**,无干净注入点
194
+
195
+ | 项 | 数字/依据 | 口径 |
196
+ |---|---|---|
197
+ | ① 连接帽 `startupWaitMs` 10s × N | `index.ts:79`(默认 10000)+ `:833-847`(`before_agent_start` 等一次)+ `:177/796/835`(`waitedForStartup` 是 **per-session**,而我们**一唤一进程**)⇒ 每唤都等一次;某 server 慢/挂 → 最坏 **+350s/场(22min 场的 +27%)** | 上界、非均值 |
198
+ | ② `exposure=direct` 的 token | **差值 +1,025 tok/请求**(agent 侧实测,n=2,5 工具全 direct) | 配置一变(server 数或 exposure)**作废** |
199
+ | ③ 全局 `mcp.json` 共享税 | 主 pi 每次请求同量级 **+~1k(机制外推、未测)** | **外推,不是测量** |
200
+ | ④ `--approve` ⇒ 被分析项目的 `.pi/mcp.json` 进 agents 工具面 | ⇒ A 层成本**随被分析项目变化、无上界**;唯一杠杆 `--no-approve` 不可行(会丢项目 AGENTS.md) | 记为"无上界" |
201
+
202
+ **为什么只能文档化**:项目级 `mcp.json` 只在 `<cwd>/.pi/mcp.json` 且项目被 trust 时读
203
+ (`extensions/mcp/config.ts:100-101`),而 agents 的 cwd = **被分析项目**(写进去=污染用户仓库);
204
+ 无 env/CLI 覆盖;`startupWaitMs` 是**扩展注册选项**(`index.ts:76`),`config.ts` 无字段,无注入点。
205
+
206
+ ---
207
+
208
+ ## 8. 明确不做 + 一个用户拍板项
209
+
210
+ **不做**:报告新增 MCP 位/字段;启动期校验;import 期断言(要么**替换** `else`、要么不做,
211
+ 手写"已处理集合"= 第二份会漂的抄本);任何"每唤执行"的检查;`pi mcp list` 探针(**本轮**,见 §4 登记)。
212
+
213
+ **用户拍板项(不由分析代拍)**:是否做配置层改动(`enabled:false` 子集化 / per-server `exposure`)。
214
+ - 收益 ≈ **$0.003/场**(≈28.7k tok);
215
+ - 代价落在**主 pi**:`mcp.json` 全局共享,`exposure` 不 gate 连接(`index.ts:805` 是 `servers.filter(isEnabled)`)
216
+ ⇒ 混合 exposure 只省 token,`enabled:false` 才同时降 token 与失效面;
217
+ - **实测**(效率,方向性):最近 12 个主 pi session / 13,144 次 toolCall,其中 `mcp` 网关 **51 次**,
218
+ 参数里 web-reader **12** / web-search-prime **9** / zread **5** ⇒ **三个 server 主 pi 都在用**
219
+ (方法限制:子串匹配、含 describe 类调用,只作方向性证据)。
220
+ - ⇒ 三方均**撤回**原建议(效率撤回混合 exposure,简单撤回 `enabled:false`),默认**不动配置**;
221
+ 若用户明确"这两个我在主 pi 不用",则按简单版 `enabled:false` 做(少一种机制)。
222
+
223
+ ---
224
+
225
+ ## 9. 纪律产出(可复用,建议进相应文档)
226
+
227
+ 1. **"落盘可以,解析不行"——细化为三档**:
228
+ | 档 | 对象 | 规则 |
229
+ |---|---|---|
230
+ | ① | 上游**文档化结构化接口**(`pi mcp list --json` 字段、session 条目) | **可作机器判据**;字段缺失/形状变 → **"未知"**,不写"不可用" |
231
+ | ② | 上游**人类 prose**(stderr、notify 文案) | **只落盘/留痕**,不做分支 |
232
+ | ③ | 上游**内部布局**(第三方包 `dist/*.js`) | **不碰**(本次已删) |
233
+ (本仓已有合规先例:`observability.py:405-438` 解析 session 的 `thinking_level_change` 得"生效档位"。)
234
+ 2. **检测器不得继承被检测对象的失效面**(效率/0010 §2;继承不可避免 ⇒ 关键是**上界 + fail-open**,
235
+ 不是"消除继承"):`pi mcp list` 要连那三台 server,若某台挂住,探针自己也会挂住 ⇒ 必须我们侧套 `T ≤ 10s`。
236
+ 3. **多余的文字一旦断言未知,就从"啰嗦"升级为"错误"**(简单/0005 §3,直接产出 §6 的修复)。
237
+ 4. **无证据的改动建议,不比无证据的断言干净**(简单/0006 §1 自陈)。
238
+ 5. **数字必须带口径**:本场统一为——写**差值**(+1,025)不写绝对值(105k 随 fork 源大小漂);
239
+ 标明 n、配置版本、人口(哪些 session)、以及**机制外推 vs 实测**。
240
+ 6. **三条不变式落字**(否则会被下一个人当死代码删掉):① 两处守卫各拦一种失效模式;
241
+ ② observability 不在快照、`--report` 永远用主仓版本;③ **非交互模式下 MCP server 状态不被报告 ⇒ rc=0 ≠ 工具可用**。
242
+
243
+ ---
244
+
245
+ ## 10. 落地方式与验证(**0 次真实 LLM 运行**)
246
+
247
+ - **合并成一次改动 + 一次全量验证**(不要分成多个周期):
248
+ ① `:362-363` 注释;② `or reason` 测试 docstring + 移除条件;③ 三条不变式落字;
249
+ ④ 前提声明"pi ≥ 0.99";⑤ §6 的 5 个副本删半句;⑥ token 数的口径(差值/人口/机制);
250
+ ⑦ `rc≠0` 时截断落 `stderr`(**只落不解析**、**归"通用诊断"**、**带截断口径**,
251
+ 先例 `meeting_fs.py:356` 的 `[:200]`);⑧ `tests/test_meeting_loop.py:737` 的 `0–2` → `1–2`(stale);
252
+ ⑨ 报告写"平台能力:未观测"。
253
+ - **验证手段 = 现有测试套件**(492 py + 54 harness,分钟级、0 LLM)。
254
+ 唯一有行为的是第 ⑦ 条——用**单元测试造 rc≠0 的假进程输出**验证,**不需要 e2e**
255
+ (本场无任何时序敏感的行为改动;按旧习惯"改完跑一场"是 20–40 分钟换不到本场所需证据)。
256
+ - **`rc≠0` 落 stderr 的归类要求**:必须在文档里归到"通用诊断",**不得**列在"MCP 可见性"名下——
257
+ §3 已证它 0 覆盖 per-server 静默;归类错了等于用一个 0 覆盖的机制去结一笔未结的账。
258
+
259
+ ---
260
+
261
+ ## 11. 过程诚实记录(撤回与更正)
262
+
263
+ | 谁 | 撤回/更正 | 原因 |
264
+ |---|---|---|
265
+ | 铁律 | 撤回"给 `builtin:mcp` 常量加能力位校验"(F2(a));撤回 F1 的"版本静默失效"分支;撤回自己提议的 LLM 探针 | 所有权分层(入口路径归我们→解析;名字归 pi→引用不校验;server 状态归 pi 运行时→查询);源码链已证 loud+fatal;源码读比探针便宜且确定 |
266
+ | 简单 | 撤回 `0001` §2(`else` 不可达)、§3(`or reason` 死判定)、`0004` §5/`0005` §5(`enabled:false`)、`0004` §4("唤醒成功 ⇒ MCP 已加载")、`0004` §1(import 期断言) | 前提错误 + reader/writer 解耦 + 主 pi 实测 + no-op notify |
267
+ | 效率 | 撤回 `0003` §1 的覆盖面(`rc≠0` 落 stderr 抓不到 per-server)、撤回 `0001` §4 子集化建议、让出 (A) 探针 | 覆盖面经复核不成立;主 pi 在用三 server;无消费者 + 会把验证成本从分钟级推到 20–40 分钟 |
268
+ | 计数更正 | `setup_environment` 的直接执行点 = **7 处**(`tests/test_spec.py:297/566/589/599/635`、`tests/test_flow_composition.py:98`、`tests/test_startup_defaults.py:52`),另 `tests/test_main_paths.py:873` 一处是 **mock** ⇒ 记 **7+1** | 效率/0013 更正,铁律在轮转中在案确认 |
269
+
270
+ ---
271
+
272
+ ## 12. 三方立场清单(谁在什么视角上贡献了什么)
273
+
274
+ - **效率**:A 层定价(10s×N ≈ +350s/场最坏、+1,025 tok/差值、主 pi 共享税)、
275
+ `pi mcp list --json` 实测价签(2.72–2.80s,n=3)、"每场一次 vs 每唤 = 35 倍差"、
276
+ "检测器继承失效面"、`--approve ⇒ 无上界`、"B 层可 0 LLM 验证 ⇒ 不要 e2e"。
277
+ - **简单**:净简化计量(mock 16→9、状态空间收敛)、三处自我撤回与一次接受驳回、
278
+ "三条不变式落字"(含本场核心产出:可读性缺失会被误读成复杂度过剩,进而诱导删除正确代码)、
279
+ "`:356` 半句取删不取改"、护栏 5"可注入是前提"、探针归类 A 层(不入 B 层零成本批)。
280
+ - **铁律**:职责分层(入口路径/平台标识/运行时状态三分)、`else` 与 `or reason` 的实物反驳、
281
+ per-server 静默链条的完整取证(`runner.ts:327` no-op)、`pi mcp list` 的 **cwd=fork_cwd 前提**、
282
+ 三档边界细化、`:356` 的编辑边界与 5 副本清单、登记文本的护栏合并。
283
+
284
+ **无未决分歧**。唯一留在用户手里的是 §8 的配置层改动(`enabled:false` / exposure 子集化)。
@@ -39,6 +39,8 @@
39
39
  | `2026-09-25-multi-viewers-postfix-review.md` | 复验 0.8.0 的 extension 合并与 P1–P6(含 harness 覆盖审查) | **P1–P6 逐条到位、合并净简化**;新抓 **漏 A:`run_tests.sh --reuse` 的错误成功信号**(harness 失败仍算绿 → 命中旧绿 + exit 0,修法 ②′ 清指纹 + rc==0 才写回);D2 通知里的不实断言(pi-web 忽略 `setEditorText`);B1/B2 契约前缀与不可执行出路;7 类现存分支零覆盖 + sid 注入与 percent-encoding 两装置缺口;D1/D3 文档漂移;S1–S3 简化 | `69a415a`(+ `e92eacc` 第三交付出口) |
40
40
  | `2026-09-25-extension-mechanisms-review.md` | 复验 0.8.2 三处机制(报告落盘 / `--set-viewer` / 观看命令通道)+ 测试覆盖与断言强度 | **① 显示层失败会跳过清理**(BrokenPipe 逃逸 → rmtree 被跳过、目录残留;修法 `_print_best_effort` + rmtree 进 finally ⇒ 清理必达);**② `--set-viewer` 半成功**(校验在写之后 → rc≠0 但文件已写入;改 B′ 校验前移);③ 通道模型由「三出口」收敛为四通道角色表,并证实 custom_message 随 fork 进每场上下文;文档三处「不持久化」复述、断言偏弱、prompt 复述、fail-open 宽窄不对称 | `51a4535` |
41
41
  | `2026-09-25-patch-audit-review.md` | 审阅 0.8.0 → 0.9.0 一周改动是否有补丁堆叠 / 复杂度失配 / 职责边界问题 | **判定:没有补丁堆叠**;真问题是**文档漂移 F1**(两份入口降级语义改了、6 处复述没跟)、**名实不符 F2/S3**(`_viewer_set_errors` 自称唯一组合点+数量≥2,皆不成立)、**契约只有注释 F4**(cleanup 裸 print 禁令 → 本仓首条 AST 结构断言);顺带:自然使用复测 `ctx_search` 0 次(不可证伪那句指引)、MCP adapter 无收尾尾巴 | `6c024be` |
42
+ | `2026-09-27-aft-mc-evidence-audit.md` | 复盘「屏蔽 AFT」决策(对照数据 / 结论链 / 现默认扩展策略是否成立) | **判定:成立**(承重换成真场:AFT 收尾 66–78% vs ≈0%;MC historian e2e21 62 次/60 失败 vs e2e23 0 次 + 上游 `#916`)。**最重一条 = 审计自身证据链**:决策 20 把 09-13 那批**装置有缺陷**的探针数字当「受控对照」(#26 早已记录)→ 两笔退出证据位;另订正 entry 结构句(2 个生命周期钩子,非「无 hook」)、adapter 每轮钩子 = 已知盲区、e2e23 的 12m31s 标为源不可复核;新增**数字口径规则**与**回退判据**(带「需求场景在窗口外」前置限定)+ 加回 AFT 的定价程序 | `ffe28aa` |
43
+ | `2026-09-30-builtin-mcp-audit.md` | 内置 MCP 替换(`-e builtin:mcp`)与 `exposure: direct` 自审 | **per-server 静默失效**(`rc=0` ≠ 工具可用:失败被 allSettled 吞掉 → 只走非交互模式下为 no-op 的 notify → 产物零痕迹);每唤醒成本 ≈ 0(38 唤);agents 自然用 MCP = 0 次;三视角撤回/更正 13 项 | `839ef4f` |
42
44
 
43
45
  ## 环境口径(读报告时的背景)
44
46
 
@@ -450,3 +450,70 @@ historian vs e2e23 48.1s/唤醒 + historian 0)与 **MC 自己的 DB 账本**
450
450
  验证 + fork 源(带交接叙事)做规模/成本验证;每臂硬超时;统计只算**边界之后**的
451
451
  条目(继承历史的 toolCall 不算——第一版踩过);同条件重复 ≥3 次压 provider 方差
452
452
  (单样本差异被噪音淹没的实例:同条件 3.9s vs 44s,11 倍)。
453
+
454
+ ### 27. 证据链的两条纪律:检测器不得继承失效面 · 多余的文字一旦断言未知即错误(2026-09-30 内置 MCP 自审)
455
+
456
+ **来源**:把 agents 的联网检索从第三方 `pi-mcp-adapter` 换成 pi 内置 MCP(`-e builtin:mcp`)后的
457
+ 一次多视角自审(`docs/reviews/2026-09-30-*-audit.md` 同批)。它挖出一条我们**从未意识到**的
458
+ 失效路径,并顺带产出两条通用纪律。
459
+
460
+ **发现(结论,可复用)**:某个 MCP server 连不上 / 凭据过期时,**agents 当次静默失去那件工具**,
461
+ 分析照常跑完且产物里**一点痕迹都没有**——`rc=0` ≠ 工具可用。
462
+
463
+ 链条(逐处上游源码,2026-09-30 核):
464
+
465
+ ```
466
+ 单 server 失败 → Promise.allSettled 吞掉(extensions/mcp/index.ts)
467
+ → reportProblems → ctx.ui.notify(..., "warning")(不 exit)
468
+ → 非交互模式用的是 noOpUIContext(core/extensions/runner.ts: notify 是空函数)
469
+ → 我们只在 rc≠0 时读 stderr ⇒ 什么都没留下
470
+ ```
471
+
472
+ **纪律一:检测器不得继承被检测对象的失效面。**
473
+ 想拿"服务器状态"当判据,最容易想到的就是去问那台服务器(例:`pi mcp list`)——但那样一来,
474
+ **它挂住时检测器自己也挂住**。继承往往不可避免(要判就必须连),所以重点不是"消除继承",而是
475
+ 给检测器**加我们侧的上界 + fail-open**(超时即记"未验证",绝不阻塞生产路径),并**明文写出它覆盖
476
+ 什么、不覆盖什么**(只覆盖持续性失效,不覆盖"启动正常、中途断"的瞬断)。
477
+
478
+ **纪律二:多余的文字一旦断言未知,就从"啰嗦"升级为"错误"。**
479
+ 本次的实物:降级时我写了一行 `downgrade_reason = f"{err};内置 MCP 工具不受影响"`——在"工具是否
480
+ 可用不可观测"已经成立的前提下,后半句就是**断言了未知**。处理取**删**不取改:改写会在多处
481
+ **再复述**一遍入口事实(那句话在代码里已单点声明),删则净减文字、假保证从仓库消失。
482
+
483
+ **推论(值域边界,落地成三档)**:
484
+ | 档 | 对象 | 规则 |
485
+ |---|---|---|
486
+ | ① | 上游**文档化结构接口**(`pi mcp list --json` 的字段、session 条目) | **可作机器判据**;字段缺失/形状变 ⇒ 记"未知",改写"不可用" |
487
+ | ② | 上游**人类 prose**(stderr、notify 文案) | **只落盘留痕**,不做分支 |
488
+ | ③ | 上游**内部布局**(第三方包 `dist/*.js`) | **不碰**(本次正是删掉了这类耦合) |
489
+
490
+ **附带收获**:① 分析目录的运行快照只含 4 个模块,`observability` 不在其中 ⇒ `--report` 永远用
491
+ **主仓今天**的代码读**当时** writer 写的日志——判定条件必须容得下旧 writer 的形态(这是"报告里
492
+ 一个看似多余的 `or reason`"的真实理由);② 报告新增一行「平台能力:未观测」:**把不可见说出来**,
493
+ 与"缺席≠0"是同一条纪律的两面。
494
+
495
+ ### 28. 补丁脚本纪律:文本先落文件 · 断言后一次写(2026-09-30 一天内三次同坑)
496
+
497
+ **现象**:一天里三次用 shell heredoc 写"含中文引号的 Python 补丁",三次都在
498
+ `SyntaxError: invalid syntax. Perhaps you forgot a comma?` 上炸——原因是把 `"…"`(中文引号)或
499
+ `"`(英文双引号)嵌进了双引号 Python 字符串里。报错信息("忘了逗号")还**指向错误方向**,
500
+ 每次都要额外一轮排查。**三次都没有写坏文件**——靠的是下面第②条。
501
+
502
+ **三条纪律**:
503
+
504
+ 1. **文本先落文件,拼接用纯 ASCII 短脚本。** 含引号 / 多行 / 中文的补丁正文,一律先用
505
+ `write` 工具写成独立文件,再用一个**只含 ASCII** 的小脚本把它拼进目标(读文件 → 定位 →
506
+ 插入 → 写回)。不要在命令行里"现场拼字符串"——shell + Python + 中文引号三层转义叠加 =
507
+ 必然踩坑。
508
+ 2. **先定位再断言,全部通过后才 write 一次。** 补丁脚本的结构固定为:
509
+ `读全文 → 对每个改动 assert 唯一命中 → 改内存 → 最后 write`。失败时**全或无**:
510
+ assert 失败 ⇒ 一个字节都不写。今天两次 `AssertionError` 都是"未写"状态,靠的就是这个顺序;
511
+ 反面写法(边改边写)在 assert 失败时会留下**半改**的文件。
512
+ 3. **行号定位必须配关键字断言。** 用行号改(比大段文本匹配更稳)时,每一行都要
513
+ `assert "关键字" in lines[i]`——行号漂移时立刻响,而不是默默改错行。今天的实例:
514
+ `assert "形状" in lines[363]` 失败 ⇒ 说明我对注释占几行的假设错了 ⇒ 先打印真实行再改。
515
+ 纯行号(无断言)的补丁 = 定时炸弹。
516
+
517
+ **为什么值得单列**:这三条管的不是"代码对不对",而是"**改代码的手段**本身会不会引入故障"。
518
+ 工具的选择要按内容特征走(有引号/多行/中文 ⇒ 块文件;纯 ASCII 单行 ⇒ 命令行即可),
519
+ 这不是口味问题——一天三次的重复率说明它是**结构性**的,不是手滑。
package/meeting_fs.py CHANGED
@@ -294,9 +294,9 @@ def resolve_mc_tools_entry(agent_dir=None):
294
294
  `resolveSiblingEntryPath("subagent-entry.js")`)定位它;我们等价地读它声明的
295
295
  扩展入口(`pi.extensions[0]`),再取同目录下的 subagent-entry.js。
296
296
 
297
- 失败语义(与 resolve_mcp_adapter_entry 同):任一步缺失返回 `(None, 原因)`
298
- ——**由调用方按策略决定**:loop 生产态做**可见降级**,严格态
299
- (`MV_MC_TOOLS_STRICT=1`)直接报错。
297
+ 失败语义:任一步缺失返回 `(None, 原因)` ——**由调用方按策略决定**:loop
298
+ 生产态做**可见降级**(本档唯一"可失败"的入口就是它;内置 MCP 入口是常量),
299
+ 严格态(`MV_MC_TOOLS_STRICT=1`)直接报错。
300
300
  """
301
301
  pkg_dir, exts, err = _declared_extensions(MC_PACKAGE, agent_dir)
302
302
  if err:
@@ -309,25 +309,6 @@ def resolve_mc_tools_entry(agent_dir=None):
309
309
  f"({os.path.relpath(cand, pkg_dir)})——上游版本可能改了布局")
310
310
 
311
311
 
312
- def resolve_mcp_adapter_entry(agent_dir=None):
313
- """解析 MCP adapter 的扩展入口(它声明的 `pi.extensions[0]`)——mc-tools 第二份。
314
-
315
- 为什么需要:MCP 工具(web_search / web_reader / zread…)由 pi-mcp-adapter
316
- 提供,而 `--no-extensions` 关掉的是**扩展发现**——显式 `-e` 路径照常生效
317
- (pi --help 原文)。不显式加载 = agents 完全没有联网检索能力。
318
-
319
- 与 MC 的差别:这里要的**就是主入口本身**(它注册 MCP 工具),不取兄弟文件。
320
- """
321
- pkg_dir, exts, err = _declared_extensions(MCP_ADAPTER_PACKAGE, agent_dir)
322
- if err:
323
- return None, err
324
- cand = os.path.normpath(os.path.join(pkg_dir, exts[0]))
325
- if os.path.isfile(cand):
326
- return cand, ""
327
- return None, (f"{MCP_ADAPTER_PACKAGE} 声明的入口不存在"
328
- f"({os.path.relpath(cand, pkg_dir)})")
329
-
330
-
331
312
  def pi_agent_dir():
332
313
  """pi 的 agent 目录(`$PI_CODING_AGENT_DIR` 或 `~/.pi/agent`)——**单一实现**。
333
314
 
@@ -883,19 +864,19 @@ def parse_log_nameonly(output):
883
864
  # DEFAULT_EXTENSION_POLICY 是**缺省填谁**(全仓引此常量)。
884
865
  # mc-tools : **默认**——只要 MC 的**只读检索工具**(ctx_search)。为什么默认它:
885
866
  # agents 需要主项目背景(背景蒸馏机制已移除),这是它的补充通道;
886
- # 该入口**只注册工具、不装 hook** → historian/压缩不在其中
887
- # (受控实测 historian 0/6、ctx_search 可用;成本未测得显著差异)。
888
- # 代价:本档两份入口**允许而非要求**(缺谁少谁、都可见降级;严格模式见
889
- # MC_TOOLS_STRICT_ENV)
867
+ # 该入口 = **工具注册 + 两个生命周期钩子**(开/关 DB),**无** historian/压缩执行钩子
868
+ # (真场计数 historian 0:e2e24 0/3、e2e25 0;ctx_search 可用;成本未测得显著差异)。
869
+ # 代价:MC 那份入口**允许而非要求**(缺它 = 可见降级;严格模式见
870
+ # MC_TOOLS_STRICT_ENV);内置 MCP 是常量入口、恒在
890
871
  # none : 零扩展——最快、**零依赖**(不依赖任何扩展;无 MC 的机器/CI 用这档)
891
872
  # all : 走 pi 默认扩展发现(A/B 实验与显式 opt-in 用)
892
873
  # (顺序只影响 CLI 帮助的罗列——**不承载语义**,勿按下标取值:
893
874
  # 默认档看 DEFAULT_EXTENSION_POLICY)
894
875
  EXTENSION_POLICIES = ("mc-tools", "none", "all")
895
876
  DEFAULT_EXTENSION_POLICY = "mc-tools"
896
- # mc-tools 档**允许而非要求**两份入口(MC 的 ctx_search、MCP adapter 的 web 工具):
897
- # 缺谁少谁、都必须**可见**;语义清单见 docs/design.md 决策 20
898
- # (打印一行说明 `ctx_search` 本次不可用)——无静默铁律。
877
+ # mc-tools 档**允许而非要求** MC 那份入口(ctx_search)——它现在是唯一"可失败"的
878
+ # 入口(内置 MCP 是常量、恒在),缺失时**可见**(登记行点名原因);
879
+ # 语义清单见 docs/design.md 决策 20——无静默铁律。
899
880
  # 测试/探针要保真(确认"本场确实带着 ctx_search 在跑")时,用环境变量把它变严格:
900
881
  # MV_MC_TOOLS_STRICT=1 → 解析失败即报错退出(测试环境准确性优先,用户 2026-09-14 定)
901
882
  MC_TOOLS_STRICT_ENV = "MV_MC_TOOLS_STRICT"
@@ -912,9 +893,14 @@ def mc_tools_strict():
912
893
  # 入口是它的内部文件,路径解析见 resolve_mc_tools_entry 的 docstring)
913
894
  MC_PACKAGE = "@cortexkit/pi-magic-context"
914
895
 
915
- # MCP 工具(web_search / web_reader / zread…)的提供者——mc-tools 档的第二份入口。
916
- # 名字**由 pi 的注册表给**(settings.json.packages),这里只做精确匹配用。
917
- MCP_ADAPTER_PACKAGE = "pi-mcp-adapter"
896
+ # mc-tools 档的第二份入口 = pi 的**内置** MCP 扩展(pi 0.99+ 自带,无第三方包)。
897
+ # 为什么写成常量:它不需要"解析"——名字由 pi 自己注册(`builtin:` 前缀 + 名字);
898
+ # 这是有意去掉一个外部依赖(`pi-mcp-adapter` 的入口解析/版本漂移全没了)。
899
+ # 为什么必须显式 `-e`:`--no-extensions` 关的是"扩展发现**与内置扩展**"(pi --help
900
+ # 原文)——内置 MCP 也在关停范围;证据:上游 `core/extensions/index.ts` 里
901
+ # `{ name: "mcp", builtin: true }`,`core/resource-loader.ts` 在 noExtensions 时
902
+ # 只保留 CLI 显式 `-e` 的扩展(2026-09-30 读源码核实)。
903
+ BUILTIN_MCP_ENTRY = "builtin:mcp"
918
904
 
919
905
  FORK_MODES = ("budget", "compaction", "full")
920
906
  DEFAULT_FORK_MODE = "budget"
package/meeting_loop.py CHANGED
@@ -317,7 +317,7 @@ def _build_wake_cmd(workdir, agent, sid, cfg, fork_source, fork_cwd,
317
317
  # 现在**只有一个出口**(本函数末尾),降级只是"少追加一个 -e"。
318
318
  #
319
319
  # 三档语义:mc-tools(默认)给 agents `ctx_search`(MC 的只读检索工具;
320
- # entry 只注册工具、不装 hook → 不带 historian/压缩);none 零扩展(零依赖);
320
+ # entry = 工具注册 + 两个生命周期钩子 → 不带 historian/压缩);none 零扩展(零依赖);
321
321
  # all 走 pi 默认发现(A/B 与显式 opt-in,须有净收益账)。
322
322
  no_ext = ["--no-extensions", "--no-skills", "--no-prompt-templates",
323
323
  "--no-themes"]
@@ -330,45 +330,55 @@ def _build_wake_cmd(workdir, agent, sid, cfg, fork_source, fork_cwd,
330
330
  if extension_policy == "none":
331
331
  cmd += no_ext
332
332
  elif extension_policy == "mc-tools":
333
- # mc-tools = 零扩展 + 显式加载**两份只读工具入口**(2026-09-25 用户裁定 B:
334
- # 并入默认档,不再新增档位——保持简单):
335
- # ① MC 的 subagent-entry(只注册工具、不装 hook)→ ctx_search
336
- # ② MCP adapter(web_search / web_reader / zread 等 MCP 工具)
337
- # 为什么必须显式 -e:`--no-extensions` 关的是**发现**,显式路径照常生效
338
- # (pi --help 原文);MCP 工具此前因发现被关而对 agents 完全不可用。
339
- # 两份入口**各自独立**降级(允许而非要求)——缺哪个就少哪个,都**可见**。
340
- resolved = [] # [(label, entry)]
341
- missing = [] # [(label, err)]
342
- for label, resolver in (
343
- ("ctx_search(MC 只读检索)", meeting_fs.resolve_mc_tools_entry),
344
- ("MCP 工具(web_search 等)", meeting_fs.resolve_mcp_adapter_entry)):
345
- entry, err = resolver()
346
- if entry:
347
- resolved.append((label, entry))
348
- else:
349
- missing.append((label, err))
333
+ # mc-tools = 零扩展 + 显式加载**两份能力**(零第三方依赖):
334
+ # ① MC 的 subagent-entry(工具注册 + 生命周期钩子,无 historian)→ ctx_search
335
+ # ② pi **内置** MCP 扩展 → web_search / web_reader / zread 等
336
+ # 为什么必须显式 -e:`--no-extensions` 关的是"扩展发现**与内置扩展**"
337
+ # (pi --help 原文)——内置 MCP 也在关停范围,不显式加载 agents 就没有
338
+ # MCP 工具;`-e <path>` 同时接受 `builtin:<name>`(pi --help 原文)。
339
+ # 为什么用内置而不再用第三方 adapter:pi 0.99+ 自带,少一个外部依赖
340
+ # (用户 2026-09-30 定);此前的 adapter 入口解析机制随之删除。
341
+ # 降级语义:唯一"可失败"的入口是 MC(解析第三方包的内部文件)——
342
+ # 失败则**本档核心能力(ctx_search)不到位** → 生效=none,原因里点名;
343
+ # 内置 MCP 是常量入口、恒在,故降级不影响它(原因字段会说明这一点)。
350
344
  cmd += no_ext
351
- for _label, entry in resolved:
345
+ cmd += ["-e", meeting_fs.BUILTIN_MCP_ENTRY]
346
+ entry, err = meeting_fs.resolve_mc_tools_entry()
347
+ if entry:
352
348
  cmd += ["-e", entry]
353
- if missing:
354
- reason = ";".join(f"{label} 不可用({err})" for label, err in missing)
349
+ else:
355
350
  if meeting_fs.mc_tools_strict():
356
351
  # 严格模式(测试/探针保真):缺入口即响,不降级——否则测试可能在
357
- # "没装某入口"的环境里通过,而该工具从未生效
358
- log(agent, f"[fatal] mc-tools 档入口解析失败(严格模式):{reason}")
359
- raise RuntimeError(f"mc-tools 档不可用: {reason}")
360
- # 允许而非要求:缺入口 → 少一份 -e,但**可见**
361
- downgrade_reason = reason
362
- effective_policy = ("mc-tools" if resolved else "none")
363
- # "all":不加任何 --no-*(走 pi 默认发现)
352
+ # "没装 MC"的环境里通过,而 ctx_search 从未生效
353
+ log(agent, f"[fatal] mc-tools 档入口解析失败(严格模式):{err}")
354
+ raise RuntimeError(f"mc-tools 档不可用: {err}")
355
+ # 允许而非要求:缺 MC → 少一份 -e,但**可见**
356
+ # 原因只写**入口层**事实(err 来自解析)。不写“MCP 工具不受影响”:
357
+ # 工具是否可用我们观测不到(非交互模式下 server 状态不进任何产物,
358
+ # 见 docs/design.md 决策 20「平台能力的可见性」)。
359
+ downgrade_reason = err
360
+ effective_policy = "none"
361
+ elif extension_policy == "all":
362
+ # pi 默认发现:**不加**任何 --no-*、也不加 -e(A/B 与显式 opt-in)
363
+ pass
364
+ else:
365
+ # 失败模式不同,与上面 :326 的守卫不可互相替代:
366
+ # · :326 拦**元组之外**的值(用户传错)
367
+ # · 这里拦**元组之内、但无分支**的值(加值忘加分支;此时 :326 不响)
368
+ # 删掉的代价不对称:新策略值会静默落进 `all` 档(= pi 默认发现 =
369
+ # 载入 AFT/MC 全档,分钟×N 级且无信号);保留 0 成本。当前三档都有
370
+ # 分支 ⇒ 本分支不可达,但**不是死代码**。
371
+ raise RuntimeError(
372
+ f"扩展策略分派未穷尽: {extension_policy!r}"
373
+ f"(合法值: {'/'.join(meeting_fs.EXTENSION_POLICIES)})")
364
374
  if first_wake:
365
375
  # 登记行(观测面的稳定字段;报告据此给"声明 vs 生效")。只在首唤打:
366
376
  # 策略在一次运行内不变,变了也是配置错误(重跑即可)。
367
- # 降级时把"部分/完全"标进原因字段(S2:第二行是复述,已删——
368
- # 报告只解析本行,`observability._report_extension_line` 的 regex 匹配行尾)。
369
- reason = ""
370
- if downgrade_reason:
371
- reason = f" 降级原因={'部分' if resolved else '完全'}:{downgrade_reason}"
377
+ # 降级时把原因写进**登记行的同一行**(S2:第二行是复述,已删——报告只解析
378
+ # 本行,`observability._report_extension_line` 的 regex 匹配到行尾)。
379
+ # 不再有"部分/完全"标签:本档只有 MC 一个入口需要解析(内置 MCP 是常量),
380
+ # 失败即"核心能力缺失",原因本身会点名(2026-09-30)。
381
+ reason = f" 降级原因={downgrade_reason}" if downgrade_reason else ""
372
382
  log(agent, f"扩展策略: 声明={extension_policy} 生效={effective_policy}"
373
383
  f" strict={int(meeting_fs.mc_tools_strict())}{reason}")
374
384
  model = cfg.get("model") or ""
@@ -510,6 +520,14 @@ def wake_llm(workdir, agent, prompt,
510
520
  if new_sid:
511
521
  save_session_id(workdir, agent, new_sid)
512
522
  if r.returncode != 0:
523
+ # 通用诊断(**只落盘、不解析、不作分支**):rc≠0 时 stderr 此前只被读来
524
+ # 判断“是不是 session 失效”,其余丢弃——诊断信息本可留在 loop 日志。
525
+ # 截断 200 字符(先例 meeting_fs 的 [:200])并**显式标注截断口径**。
526
+ # 注意:这**不**解决“MCP server 静默失效”——那种情形 rc=0、stderr 为空
527
+ # (上游 notify 在非交互模式是 no-op),见 design.md 决策 20。
528
+ err_txt = (r.stderr or "").strip().replace("\n", " ⏎ ")
529
+ if err_txt:
530
+ log(agent, f"pi stderr(截断 200 字符): {err_txt[:200]}")
513
531
  # 常见可重试失败:session 文件损坏/不存在。pi 对 --session-id
514
532
  # 通常自动创建;保留 stderr 日志便于诊断。明确 "No session
515
533
  # found" 则清空 status 后下轮新建。
package/observability.py CHANGED
@@ -326,6 +326,31 @@ def build_report(base):
326
326
  f"{_dur(d['total_ms'] // 1000)} / 最大 "
327
327
  f"{_dur(d['max_ms'] // 1000)} | rc≠0 {d['fails']} 次")
328
328
 
329
+ # ---- 跨度口径(两个直标量各自命名 + 一个派生量)----
330
+ # 起因(2026-09-27 复盘审计):外部引用曾把两个 span 混算(Σ进程跨度 1538s ÷
331
+ # 墙钟 751s = 2.05,而按可核的 636s 得 2.42)——两个量都能"直标",但报告没给
332
+ # 它们各自的名字与定义。这里各占一行、名字即口径;派生量显式写出算式,
333
+ # 免得读者自行相除去猜。**缺席一律 n/a,不写 0**。
334
+ # 判"缺席"一律用 `is None`——**0 是合法值**(同一秒提交、极短唤醒),
335
+ # 用真值判断会把它当缺失(0 ≠ 缺席,与"缺席≠0"同一条纪律的两面)。
336
+ total_proc_ms = sum(d["total_ms"] for d in proc.values()) if proc else None
337
+ wall_s = (rows[-1][0] - rows[0][0]) if rows else None
338
+ out.append("跨度(两个直标量 + 一个派生量;各自命名、不可互替):")
339
+ out.append(" Σ进程跨度 "
340
+ + (f"{_dur(total_proc_ms // 1000)}" if total_proc_ms is not None else "n/a")
341
+ + "(各 agent 唤醒跨度相加;唤醒可并行 ⇒ 可能大于墙钟)")
342
+ out.append(" 墙钟跨度 "
343
+ + (f"{_dur(wall_s)}" if wall_s is not None else "n/a")
344
+ + "(首末 commit 差 = 用户等待)")
345
+ # 派生量的定义**永远打出来**(n/a 时也打)——名字即口径,读者不必猜算式
346
+ if wall_s is None or total_proc_ms is None:
347
+ out.append(" 并行度 n/a(= Σ进程跨度 ÷ 墙钟跨度;缺任一被除数)")
348
+ elif wall_s == 0:
349
+ out.append(" 并行度 n/a(= Σ进程跨度 ÷ 墙钟跨度;墙钟跨度为 0s,无法相除)")
350
+ else:
351
+ out.append(f" 并行度 {total_proc_ms / 1000 / wall_s:.2f}"
352
+ f"(= Σ进程跨度 ÷ 墙钟跨度;>1 = 唤醒有重叠)")
353
+
329
354
  # ---- LLM 运行事实(session 文档化字段;流式预过滤,不整文件解析) ----
330
355
  # ---- 扩展策略(声明 vs 生效)----
331
356
  _report_extension_line(base, out)
@@ -654,9 +679,21 @@ def _report_extension_line(base, out):
654
679
  line = f"扩展策略:声明 {d} | 生效 {e}(strict={strict}"
655
680
  line += f",降级:{reason.strip()}" if reason else ""
656
681
  line += ")"
657
- if d != e:
658
- line += " ⚠ 生效≠声明(降级:部分工具不可用——见登记行原因)"
682
+ # 触发条件:声明≠生效 **或** 有降级原因——2026-09-30 起「生效」以本档
683
+ # 核心能力(MC 的 ctx_search)为准,而内置 MCP 入口恒在:可能出现
684
+ # 「声明=生效但仍降级」的组合,只看 d != e 会把降级漏掉(观测面纪律:
685
+ # 降级必须可见)。
686
+ if d != e or reason:
687
+ line += " ⚠ 降级(工具能力不全——见本行「降级」字段)"
659
688
  out.append(line)
689
+ # 平台能力位(2026-09-30 自审结论):**本档不采集** MCP server 的连接状态——
690
+ # 非交互模式下失败只走 `ctx.ui.notify`,而它是 no-op;session 也不落工具集。
691
+ # 所以 rc=0 **不等于**工具可用;报告必须把「未观测」说出来(缺席≠0 的同一
692
+ # 纪律)。**判定用声明或生效任一命中**:内置 MCP 与 MC 入门是两份入口,
693
+ # MC 降级时内置那份仍在(生效=none ≠ 没有 MCP 工具)。none 档不写此行。
694
+ if d in ("mc-tools", "all") or e in ("mc-tools", "all"):
695
+ out.append("平台能力:未观测(MCP server 连接状态不采集——rc=0 ≠ 工具"
696
+ "可用;见 docs/design.md 决策 20)")
660
697
  else:
661
698
  out.append(f"扩展策略:声明 {declared} | 生效 n/a(日志中无登记行)")
662
699
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-multi-viewers",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Multi-perspective analysis for Pi: fork the main session into N perspective agents over the meeting protocol.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -626,8 +626,8 @@ def main():
626
626
  choices=list(meeting_fs.EXTENSION_POLICIES),
627
627
  default=meeting_fs.DEFAULT_EXTENSION_POLICY,
628
628
  help="agents 的扩展策略:mc-tools=默认,只要 MC 的只读检索工具 "
629
- "ctx_search + MCP 工具(缺谁少谁、可见降级);none=零扩展(零依赖);"
630
- "all=走 pi 默认发现")
629
+ "ctx_search + pi 内置 MCP 工具(MC 那份缺了可见降级);"
630
+ "none=零扩展(零依赖);all=走 pi 默认发现")
631
631
  parser.add_argument("--start", action="store_true", help="创建后启动讨论")
632
632
  parser.add_argument("--skip-setup", action="store_true",
633
633
  help="跳过环境生成,只启动已有环境(需 --dir)")