pi-multi-viewers 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +48 -14
- package/README.md +10 -6
- package/docs/design.md +75 -13
- package/docs/reviews/2026-09-14-e2e24-extension-policy-review.md +146 -0
- package/docs/reviews/2026-09-14-e2e25-doc-drift-review.md +188 -0
- package/docs/reviews/README.md +2 -0
- package/docs/test-methodology.md +33 -0
- package/meeting_fs.py +155 -2
- package/meeting_loop.py +69 -27
- package/mv_cli.py +1 -0
- package/observability.py +43 -2
- package/package.json +1 -1
- package/prompts/multi-viewers.md +1 -1
- package/spec_gen.py +11 -7
- package/start_discussion.py +16 -10
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<!-- 存档:docs/reviews/2026-09-14-e2e25-doc-drift-review.md
|
|
2
|
+
来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
|
|
3
|
+
分析场次目录已随 --cleanup 删除;文中消息编号(如 `效率/0003`)不可再核验,
|
|
4
|
+
仅作溯源线索(与代码注释引用约定一致:行为以自描述为准)。 -->
|
|
5
|
+
|
|
6
|
+
# 存档说明
|
|
7
|
+
|
|
8
|
+
- **主题**:审阅本项目「文档 vs 代码」的一致性(文档漂移)——找出确认为错的表述、
|
|
9
|
+
缺失项、文档间矛盾
|
|
10
|
+
- **场次**:`mv-mv-main-20260914-133016`(3 视角:效率 / 简单 / 铁律;真实 pi 讨论,
|
|
11
|
+
以 `MV_MC_TOOLS_STRICT=1` + 默认档 `mc-tools` 启动)
|
|
12
|
+
- **本场同时是"自然使用"观察**(任务书**完全没提工具**):
|
|
13
|
+
- 工具调用合计:`bash 114 / read 46 / write 34`,**`ctx_search` 0 次**
|
|
14
|
+
(对照 e2e24:9 次——但那 9 次全由我写在任务书里的提问诱导,不构成需求证据)
|
|
15
|
+
- **historian 子进程 0 次**(mc-tools 档不装 hook,符合设计)
|
|
16
|
+
- 报告新段首吃真实数据:`扩展策略:声明 mc-tools | 生效 mc-tools(strict=1)`、
|
|
17
|
+
`终止:共识(RR 全体 pass)`、唤醒构成表(34 唤 / rc≠0 0 次)
|
|
18
|
+
- **核心结论(本场最有价值的一条)**:文档漂移的**根源不是文档不够多,而是"对实现的
|
|
19
|
+
复述"**——同一事实被多处抄写,抄本必随实现演进漂移。治本方向 = 现状描述**引事实源,
|
|
20
|
+
不复制**。
|
|
21
|
+
- **抓到的最重一条(行为语义与实现相反,已复核)**:`README.md` / `AGENTS.md` /
|
|
22
|
+
`docs/design.md` **三处**仍写"目录发现找不到时兜底 `mv-*` 并警告",而代码
|
|
23
|
+
(`observability.find_current_dir`,e2e16 评审 2:0:1 裁定删除兜底)是**无兜底、
|
|
24
|
+
未匹配即报错 rc 1**——读者会等一个永不到来的警告。
|
|
25
|
+
- **其余**:`README.md` 缺 `--extension-policy` 说明;报告字段列表停留在旧版;
|
|
26
|
+
4 条缺失项(分析目录代码副本快照语义 / 无产出重试 / stall 600s 兜底 /
|
|
27
|
+
`check-residue.sh` 清单漏项);3 条治理规则;5 条"明确不做";2 条"待核"。
|
|
28
|
+
- **落地**:`ba204ef`(#1 校准批 + #2 结论限定 + #3 治理与小修;含新增
|
|
29
|
+
AGENTS.md「文档维护纪律」节)· 446 测试全绿。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# 文档一致性审阅 · 三方共识结果
|
|
34
|
+
|
|
35
|
+
**主题**:审阅 `pi-multi-viewers` 的「文档 vs 代码」一致性(文档漂移):列出确认为错的
|
|
36
|
+
表述与缺失项。
|
|
37
|
+
**参与者**:效率 / 简单 / 铁律(3 视角;全流程 meeting 讨论 + round-robin 全员 pass)。
|
|
38
|
+
**方法**:只读核对(每项带 `文件:行`,锚点经两方以上逐行复核);审阅对象 = 主仓库
|
|
39
|
+
工作区现场原文。**本场未修改任何文件**。
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 0. 结论摘要
|
|
44
|
+
|
|
45
|
+
1. **文档漂移的根源不是"文档不够多",而是"对实现的复述"**——同一事实被多处抄写,
|
|
46
|
+
抄本必随实现演进漂移(本场找到:行为描述 3 处副本、状态列举 3 版不一、版本状态 2 处、
|
|
47
|
+
数字/容量多处)。治本方向 = **现状描述引事实源,不复制**。
|
|
48
|
+
2. **最重的一条是唯一一类"行为语义与实现相反"**:README / AGENTS / design.md 三处都
|
|
49
|
+
仍写着"目录发现找不到时**兜底 `mv-*` 并警告**",而代码是**无兜底、未匹配即报错**
|
|
50
|
+
(读者会等一个永不到来的警告/误以为写入已降级)。
|
|
51
|
+
3. **三条治理规则**(本场共识,见 §二)落地后,同类漂移结构性减少。
|
|
52
|
+
4. 全部问题分三档:**#1 现状描述校准批** → **#2 E1(错误结论限定)** →
|
|
53
|
+
**#3 治理与小修**(见 §一)。三项"不做"与两项"待核"见 §五/§六。
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 一、确认问题清单(按优先级)
|
|
58
|
+
|
|
59
|
+
### #1 现状描述校准批(引事实源 / 改准确 / 删复述)
|
|
60
|
+
|
|
61
|
+
**① 行为语义反 · 三处副本(T1/T2)**
|
|
62
|
+
|
|
63
|
+
| 位置 | 现象 | 代码依据 |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `README.md:77-79` | "不带目录时自动定位…(`mv-<sessionId>-*` 最新;**找不到则取最新 `mv-*` 并警告**…)" | `observability.py:68-80`(docstring:"**为什么没有**'取项目下最新 `mv-*`'的兜底(e2e16 评审 2:0:1 裁定删除)")· `:90-91`(无 sid/无候选 → `None`,无兜底路径)· `:831-833`(失败文案"请显式传目录参数"+ rc≠0) |
|
|
66
|
+
| `AGENTS.md:213-215` | "目录发现 = `<cwd>/mv-<sessionId>-*` 最新,**兜底 `mv-*`(排除 `mv-spec-*`)并警告**" | 同上 |
|
|
67
|
+
| `docs/design.md:273-274`(决策 9 段) | "无 sid 目录时兜底项目下最新 `mv-*`…并**警告降级**" | 同上;且与**同文档决策 15**(`:315` 起,"**无降级兜底**"在 `:319-321`)直接矛盾 |
|
|
68
|
+
|
|
69
|
+
**改法(两类,不可互换)**:
|
|
70
|
+
- `README.md` / `AGENTS.md`(行为描述,`--help` 无此入口)→ **最短准确陈述**:
|
|
71
|
+
"只匹配本 session;未匹配则报错(rc 1),需显式传目录"。
|
|
72
|
+
- `docs/design.md:273-274` → **删复述 + 引权威**:保留"目录发现零状态文件:
|
|
73
|
+
`ctx.cwd` + `sessionManager.getSessionId()` → `mv-<sid>-* 最新`(session 隔离)"
|
|
74
|
+
半句与"不静默"意图,删"兜底 `mv-*` 并警告降级"行为细节,行为引决策 15(`:315`)。
|
|
75
|
+
|
|
76
|
+
**② 枚举缺失(E6/S1、S2)**
|
|
77
|
+
|
|
78
|
+
| 位置 | 现象 | 改法 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `README.md` 命令区(约 `:91-107`) | 复述了 `--fork-mode` 等,却**缺 `--extension-policy none\|mc-tools\|all`**(`mv_cli.py` USAGE 实有;默认档 mc-tools)——该开关即"48.1s/唤醒 ↔ ~330s/唤醒"的档位 | 删枚举 → 引 `mv.sh --help` |
|
|
81
|
+
| `README.md:100` + `prompts/multi-viewers.md`(第 4 步"结束回合") | 报告字段列举停留在旧版("消息/墙钟/配额/进程跨度/LLM 用量")——新增的**唤醒构成 / 终止原因 / 扩展策略(声明 vs 生效)**零提及 | 删枚举 → 引 `docs/design.md §观测面契约`(`:79` 起;字段表 `:105`) |
|
|
82
|
+
|
|
83
|
+
**③ 状态/计数复述(T5 / T3 / T6)**
|
|
84
|
+
|
|
85
|
+
| 位置 | 现象 | 依据 / 改法 |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `README.md:104` + `prompts/multi-viewers.md:89`(T5) | 状态列举三版不一:代码 **5 态**(running/done/stalled/stopped/not-exists)、README **4 态**(缺 not-exists)、prompt **3 态**(缺 stalled、not-exists) | 代码:`observability.py:103-106/151/171`(各有对外文案;`stalled` 是 `--wait` 退出后给动作的终态)。改法:**不枚举** → 引 `--status` 输出 / `check_status` 定义处 |
|
|
88
|
+
| `AGENTS.md:32` + `:216`(T3) | 同段内自相矛盾("发版**待办**" vs "已发布 **0.3.0**"),实际 `package.json:3` = **0.5.0**(tag v0.4.0/v0.5.0 在) | 改法:引 `package.json` / registry,不抄版本号 |
|
|
89
|
+
| `docs/design.md:105`(T6) | 标题"报告的字段集…**四组谓词分组 + 一组对照**" vs 表格实际 **8 行**(后三行 扩展策略/终止/唤醒构成为新增) | 改法:标题**去计数**("谓词分组 + 对照 + 事实行") |
|
|
90
|
+
|
|
91
|
+
### #2 E1(错误结论 · 证据范围 < 主张范围)
|
|
92
|
+
|
|
93
|
+
- **位置**:`AGENTS.md:69-70`(活文档句)与 `docs/design.md` 决策 20(结论句
|
|
94
|
+
`:464`:"对本项目的 session 形态,MC 的压缩机制**结构性不工作**")。
|
|
95
|
+
- **新证据(2026-09-14)**:该失败类根因至少含**可调默认上限**——MC 源码
|
|
96
|
+
`maxOutputTokens: historian?.maxTokens ?? 32000`;主 pi 把 `historian.maxTokens`
|
|
97
|
+
抬到 131072 后,historian 首跑即成功(MC 自己的 `context.db` #916:completed、
|
|
98
|
+
输出 **36,954** > 旧上限 32,000)。
|
|
99
|
+
- **限度**:agents 会话(fork 大历史)在抬高上限后**未复测** → **"结构性"依据不足**。
|
|
100
|
+
- **改法**:活文档直接改错句(限定为"**在该默认上限(32000)下不工作**");
|
|
101
|
+
决策段加**一行**作废/限定注记(日期 + 证据指针),不叠注记层。
|
|
102
|
+
|
|
103
|
+
### #3 治理与小修
|
|
104
|
+
|
|
105
|
+
| 项 | 位置 | 现象 / 改法 |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| **E2 + E7** | `AGENTS.md:68-74` ↔ `design.md` 决策 20;`README.md:36` / `design.md:51-52` | 数字/容量多处抄本(装置注记、容量"15–19 分钟"已被 e2e19–24 超出:12m31s / 17m19s / 20m34s / 14.2 分钟(853s) / 离群 1h13m)。改法:**单一源在 design.md**,AGENTS/README 只留"区间 + 决定因素(扩展策略/唤醒数)+ 指针";数字带口径(首末 commit vs 全程、样本数、`--force`) |
|
|
108
|
+
| **E3** | `docs/design.md:395-396`(AFT"~2% 墙钟""几乎无剩余收益")↔ 同文档 `:459`(同插件大 session 收尾 **445.9s**,对照 0.5s) | 同一文档两处 ~100× 相反结论并存。改法:`:395-396` 加**一行限定注记**——"2% 仅小 session 单点探针、不可外推;重估'屏蔽 AFT'以决策 20 为准"(带日期 + 证据指针) |
|
|
109
|
+
| **E4** | `meeting_engine.py:51` `MAX_RETRY = 3`(`respond_with_fallback`,`:313-345`) | 机制未文档化。改法:design.md 协议机制节一行式——"无产出重试上限 3 → 每次重试 ≈ 一次完整唤醒(真场 48–87s/唤)→ 单轮最坏 +2.5–4.5 分钟;仍无产出 loop 代写、不耗配额" |
|
|
110
|
+
| **E5** | `meeting_fs.py:52` `DEFAULT_STALL_TIMEOUT = 600` | 未文档化。改法:一行式——"无进展兜底 600s → 死锁墙钟下限 10 分钟/次;任一 agent 可接管(心跳式软仲裁,非互斥)" |
|
|
111
|
+
| **M1** | `start_discussion.py:416-419`(拷贝 4 模块)· `:710`(从副本运行) | **分析目录代码副本机制**零文档:它是运行快照(**非事实源**)、在跑期间可与主仓同名文件 `diff` 溯源、cleanup 后不可追。改法:AGENTS 架构节 + design.md 各一句 |
|
|
112
|
+
| **S3 ≡ M3** | `AGENTS.md:26-27` | scripts 清单漏 `scripts/check-residue.sh`。改法:补一行 + "增删同步本节"(低 churn 事实,不结构改造) |
|
|
113
|
+
| **T4** | `docs/design.md:530`(决策 21 标题**重复两遍**)· `observability.py:121-124`("读路径统一走 fs.run_git…"注释**连续重复两行**) | 复制/合并事故;全文扫描(相邻重复行 + 行内重复)确认**仅此两处**(`meeting_loop.py:79-80` 的 `return 99999` 为 except 分支 + 兜底,非事故) |
|
|
114
|
+
| **README:162** | `README.md:162` "全量(~280s)" | 零成本取证(主会话时间戳,14 样本 282–331s、中位 ≈305s;缓存命中 0.0–0.8s)→ 改"**全量(`--force`,本机 ~300s;缓存命中毫秒级)**";单次 2494s 异常样本列为待核 |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 二、治理规则(本场共识,供后续文档维护)
|
|
119
|
+
|
|
120
|
+
1. **单一事实源**:文档不做第二事实源。**有唯一事实源 → 一律引用**(版本号 →
|
|
121
|
+
`package.json`;选项 → `--help`;报告字段 → `design.md §观测面契约`;状态 →
|
|
122
|
+
`check_status` 定义处;容量 → `design.md §二`)——**与 churn 无关**;
|
|
123
|
+
**无事实源 → 补一行 + 增删同步**(成本按变更频率分级,如 `scripts/` 清单)。
|
|
124
|
+
2. **三类改法优先级**(复述类问题):**删复述引权威 > 最短准确陈述**(无权威入口的
|
|
125
|
+
行为描述)**> 就地改准确**(必须保留语境时)。
|
|
126
|
+
3. **两条判据**:① **重复"计算"在冷路径可接受;重复"事实"在文档不可接受**
|
|
127
|
+
(报告两遍遍历不做合并;文档复述必去)。② **数字必须带口径**(测点/样本/是否
|
|
128
|
+
`--force`;"单点数字不宜当承诺")。
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 三、矛盾清单(哪个对—依据)
|
|
133
|
+
|
|
134
|
+
| A 处 | B 处 | 哪个对 |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `README.md:79` + `AGENTS.md:214-215` + `design.md:273-274`:"兜底 `mv-*` 并警告" | `design.md:315-321`(决策 15)+ `observability.py:68-80/90-91/831-833` + `extensions/multi-viewers-say/index.ts:13`:"无降级兜底、未匹配报错" | **B**(代码双重注释 + 决策 15 三重互证;A 为 e2e16 裁定删除前的旧文案残留) |
|
|
137
|
+
| `AGENTS.md:216`(0.3.0;同段 `:32` 又说"发版待办") | `package.json:3` = 0.5.0 + tag v0.4.0/v0.5.0 | **B**(版本号唯一事实源) |
|
|
138
|
+
| `design.md:395-396`(AFT ≈2%、几乎无剩余收益) | `design.md:459`(同插件 445.9s) | **B**(按生产规模实测;A 为小 session 外推) |
|
|
139
|
+
| (同一文档内)`design.md:530` 标题重复两遍 | — | 排版事故 |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 四、缺失项清单(代码有、文档零字)
|
|
144
|
+
|
|
145
|
+
| 机制 | 代码位置 | 为什么该写 | 建议写在哪 |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| 分析目录代码副本(快照语义) | `start_discussion.py:416-419` · `:710` | 调试"改了主仓为什么不生效"的必经问题;**副本非事实源**边界必须写清 | `AGENTS.md` 架构节 + `design.md` |
|
|
148
|
+
| 无产出重试(E4) | `meeting_engine.py:51` | 固定成本乘数项(最坏 +2.5–4.5 分钟/轮) | `design.md` 协议机制节 |
|
|
149
|
+
| stall 兜底 600s(E5) | `meeting_fs.py:52` | 死锁墙钟下限;"接管"语义未文档化 | 同上 |
|
|
150
|
+
| `scripts/check-residue.sh`(S3) | `scripts/check-residue.sh`(多处被方法论引用) | 清单声称列 `scripts/` 却漏项 | `AGENTS.md:26-27` 一行 |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 五、明确不做(防止把设计当缺陷)
|
|
155
|
+
|
|
156
|
+
1. **不合并四层文档**(README 用户 / AGENTS 开发 / design 决策 / 方法论测试)——
|
|
157
|
+
职责边界不同;问题不是"文档多",是"同一事实被复述"。
|
|
158
|
+
2. **不删决策记录**(含被否决方案与已失效决策 19 的标记)——记录"当时为什么"是其职责。
|
|
159
|
+
3. **不合并 `observability` 两个报告遍历函数**(`_report_session_metrics` /
|
|
160
|
+
`_report_session_levels`)——职责分割 > 遍历次数;报告是冷路径。
|
|
161
|
+
4. **不枚举 prompt 主流程命令**——低 churn + 执行自足优先(prompt 的"状态列举"例外:
|
|
162
|
+
已漂移,按 T5 处理)。
|
|
163
|
+
5. **不把历史决策段/追溯编号("审核#N"等)当缺陷**——AGENTS.md 注释引用约定已声明
|
|
164
|
+
不可核验、行为以自描述为准。
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 六、待核(不猜)
|
|
169
|
+
|
|
170
|
+
- `README.md:162` 计时样本中**单次 2494s**(≈8× 中位,2026-09-11)的成因(疑似机器
|
|
171
|
+
负载/挂起,未查)。
|
|
172
|
+
- `not-exists` 是否应进用户文档的**常规列举**——按 #1③ 改法(不枚举)后该问题自然消解;
|
|
173
|
+
若将来选择保留枚举,需产品口径决定。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 七、收敛记录
|
|
178
|
+
|
|
179
|
+
- 三方在自由讨论后进入 round-robin:**效率 / 简单 / 铁律 均 `pass`**(无异议)。
|
|
180
|
+
- 通过条件的核验点:`#1 ①②③`、`#2 E1`、`#3` 含 **E3** 与 **T4 两处**(及 M1、S3≡M3、
|
|
181
|
+
E2+E7、E4/E5、README:162 口径)——均收录于本清单。
|
|
182
|
+
- 引线以精确版为准:决策 15 = `design.md:315` 起(`520-529` 为决策 20 退役段,作废);
|
|
183
|
+
E3 B 处 = `design.md:459`;第三抄本 = `design.md:273-274`;T6 = `design.md:105`。
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
*本结果为三方共识,非单方观点;各视角原始分析与交锋记录见本讨论目录各 `work-*/` 消息
|
|
188
|
+
(目录将在 cleanup 时删除)。*
|
package/docs/reviews/README.md
CHANGED
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
| `2026-09-13-e2e20-pure-run-review.md` | 审阅 `c2be72d` 的 9 条修复(**该场 agents 零扩展**) | 边界**子串**误判把 fork 历史算进本轮(报告虚高 4–8 倍,也是"Δ 合计 > 墙钟"之谜根因);单趟 strip 让注释含引号的输入解析失败(功能回归);Δ 口径 B 归因被证伪 | `5e02a67` |
|
|
31
31
|
| `2026-09-13-e2e21-postfix-review.md` | 审阅 `5e02a67`(并验证报告口径修复) | AFT legacy 检测 4 组路径**多一层 `aft/`**(死检查 + 静默假阴性);子串预筛是对设计的错误陈述;`n`/`sec` 分母不一致导致均值低估 | `5cc0b5e` 之后的批 |
|
|
32
32
|
| `2026-09-13-e2e23-time-breakdown-analysis.md` | 一次分析的**时间构成**(哪些必要、哪些可省) | AFT / MC historian 两笔分钟级开销都不在报告里;反对"不等退出就推进"与"新增常驻机制" | `98786d6` + `c46d996` / `9deefac` |
|
|
33
|
+
| `2026-09-14-e2e24-extension-policy-review.md` | 扩展策略三档(默认 mc-tools)的实现与证据链 + `ctx_search` 价值评估 | **S1:缺 MC 的降级路径提前 return → 命令被截断(缺 model/print/注入、cwd 错)**;S1a 测试判别力不足;E2 报告缺"声明 vs 生效";E1 成本口径超出精度;T1–T3 文本矛盾;F5/F6/F7/S2/F9 解析链缺陷;**ctx_search 9 次调用全为问卷诱导、0 次决定性帮助、命中 1 条过期记忆** | `91a1171`(Batch 1+3)、`b9329fb`(Batch 2 删死代码)|
|
|
34
|
+
| `2026-09-14-e2e25-doc-drift-review.md` | 文档 vs 代码一致性(文档漂移)+ ctx_search 的**自然使用**观察 | **三处文档仍写"兜底 mv-* 并警告"而代码是无兜底、未匹配报错**(行为语义相反);README 缺 `--extension-policy`;报告字段列表过期;4 条缺失项;**根因 = 对实现的复述**(治本:引事实源不复制);自然使用观察:`ctx_search` **0 次**、historian 0 次 | `ba204ef` |
|
|
33
35
|
|
|
34
36
|
## 环境口径(读报告时的背景)
|
|
35
37
|
|
package/docs/test-methodology.md
CHANGED
|
@@ -417,3 +417,36 @@ session。用户先发现的("我看到不少 pi 的 session 的残留")。
|
|
|
417
417
|
|
|
418
418
|
**边界(诚实)**:闸门只挡 `pi-probe.sh` 这条路径——直接调 `pi` 仍可绕过。
|
|
419
419
|
它的价值是让"绕过"成为**显式动作**(要故意跳过提示),而不是顺手。
|
|
420
|
+
|
|
421
|
+
### 26. 探针三纪律:源带交接叙事 · 提示词用生产形态 · 数据不干净就说"未测"(2026-09-14 探针跑飞)
|
|
422
|
+
|
|
423
|
+
**触发事件(两次,同一根因家族)**:为验证"MC 的 `subagent-entry` 能否只给
|
|
424
|
+
`ctx_search` 而不带 historian",我写了以 **fork 源**(主会话裁剪)为输入、提示词
|
|
425
|
+
只有一句"只回复:ok"的探针。结果:
|
|
426
|
+
|
|
427
|
+
| 次 | 现象 | 根因 |
|
|
428
|
+
|---|---|---|
|
|
429
|
+
| 第一次 | 探针进程**扮演我继续做调查**(跑 bash 查进程/session、写 FINDINGS.md、杀掉我的 run.py)→ 跑满数分钟、阻塞我的会话、三臂只跑了半臂 | fork 源**没有注入生产的"切换叙事"**(`append_handoff_turns` 的 2 对对话)→ 历史惯性把模型拉回"上一件任务" |
|
|
430
|
+
| 第二次(修正装置后) | 4 次"fork + entry"里仍有 1 次跑飞(原文:它开始 `echo "=== ① 探针进程是否还在 ==="`、查 fork 源 session 头……);"fork + 零扩展"4 次全干净 | **提示词不是生产形态**:生产 wake prompt 有"身份锚定 + 单一任务 + 禁止监控",我的探针只有一句干话 |
|
|
431
|
+
|
|
432
|
+
**另外的代价**:那批数据我拿去解释机制("MC 43 倍 / 447s vs 10.3s"),后来发现
|
|
433
|
+
装置有缺陷 → **按用户指正:"你不能没有准确进行测试,就完全靠猜测,那就没有测试
|
|
434
|
+
的必要了"**。真正干净的证据只有**生产路径的真场**(e2e21 330s/唤醒 + 62 次
|
|
435
|
+
historian vs e2e23 48.1s/唤醒 + historian 0)与 **MC 自己的 DB 账本**。
|
|
436
|
+
|
|
437
|
+
**三纪律(写成条目,不只"下次注意")**:
|
|
438
|
+
|
|
439
|
+
1. **以 fork 源为基础的探针必须注入交接叙事**(`meeting_fs.append_handoff_turns`
|
|
440
|
+
或直接用生产 `wake_llm` 路径)。否则探针在结构上"就是我"——它会继续我的任务,
|
|
441
|
+
而不是回答探针问题。
|
|
442
|
+
2. **探针的提示词必须用生产形态**(身份锚定 + 单一任务 + 禁止动作),否则"跑飞"
|
|
443
|
+
这个观察**不可归因**:分不清是被测对象的行为、还是提示词缺了生产防线。
|
|
444
|
+
3. **探针必须自证范围**:跑完检查"边界之后的新增条目数 ≤ N"、"只出现被允许的
|
|
445
|
+
工具调用"、"回复就是被要求的那一句"。**做不到这三项 → 该次数据作废**,不许
|
|
446
|
+
写进结论(宁可写"未测",也不许把装置有缺陷的数字当机制解释)。推论:**引用
|
|
447
|
+
数字时必须能说清装置**(哪种源、什么提示词、几次重复、硬超时多少)。
|
|
448
|
+
|
|
449
|
+
**落地形态(本次已用)**:干净 `build_bootstrap` 源(结构上不可能跑飞)做功能
|
|
450
|
+
验证 + fork 源(带交接叙事)做规模/成本验证;每臂硬超时;统计只算**边界之后**的
|
|
451
|
+
条目(继承历史的 toolCall 不算——第一版踩过);同条件重复 ≥3 次压 provider 方差
|
|
452
|
+
(单样本差异被噪音淹没的实例:同条件 3.9s vs 44s,11 倍)。
|
package/meeting_fs.py
CHANGED
|
@@ -60,11 +60,131 @@ DEFAULT_STALL_TIMEOUT = 600
|
|
|
60
60
|
DEFAULT_THINKING = "max"
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def _entry_source(entry):
|
|
64
|
+
"""pi 的 packages 条目 → 源字符串(两种形态共用;非字符串形态 → "")。
|
|
65
|
+
|
|
66
|
+
形态(实测):裸字符串 `"npm:@scope/name"` / 相对路径 `"../../repo"`;
|
|
67
|
+
对象形态 `{"source": "..."}`。**单一实现**(此前包目录解析与 MC 匹配各写
|
|
68
|
+
一遍、宽严不一)。
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(entry, str):
|
|
71
|
+
return entry.strip()
|
|
72
|
+
if isinstance(entry, dict):
|
|
73
|
+
src = entry.get("source")
|
|
74
|
+
return src.strip() if isinstance(src, str) else ""
|
|
75
|
+
return ""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _npm_package_name(source):
|
|
79
|
+
"""`npm:` 源 → 裸包名(去版本后缀);非 npm 源 → ""。
|
|
80
|
+
|
|
81
|
+
`npm:@scope/name@1.2.3` → `@scope/name`;`npm:name` → `name`。
|
|
82
|
+
(F5:包匹配必须**按裸包名精确相等**,不能用子串——`in` 会把
|
|
83
|
+
`@cortexkit/pi-magic-context-legacy` 也命中。)
|
|
84
|
+
"""
|
|
85
|
+
if not source.startswith("npm:"):
|
|
86
|
+
return ""
|
|
87
|
+
body = source[len("npm:"):]
|
|
88
|
+
if body.startswith("@"): # scoped:@scope/name[@ver]
|
|
89
|
+
at = body.rfind("@")
|
|
90
|
+
return body[:at] if at > 0 else body
|
|
91
|
+
at = body.find("@") # 非 scoped:name[@ver]
|
|
92
|
+
return body[:at] if at > 0 else body
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _package_dir(source, agent_dir):
|
|
96
|
+
"""源字符串 → 包目录(不存在 → None)。
|
|
97
|
+
|
|
98
|
+
"npm:@scope/name" → <agent_dir>/npm/node_modules/@scope/name
|
|
99
|
+
"../../repo-name" → 相对 <agent_dir> 解析(绝对路径原样)
|
|
100
|
+
"""
|
|
101
|
+
if not source:
|
|
102
|
+
return None
|
|
103
|
+
if source.startswith("npm:"):
|
|
104
|
+
name = _npm_package_name(source)
|
|
105
|
+
if not name:
|
|
106
|
+
return None
|
|
107
|
+
cand = os.path.join(agent_dir, "npm", "node_modules", *name.split("/"))
|
|
108
|
+
else:
|
|
109
|
+
cand = source if os.path.isabs(source) else os.path.join(agent_dir, source)
|
|
110
|
+
cand = os.path.normpath(cand)
|
|
111
|
+
return cand if os.path.isdir(cand) else None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def resolve_mc_tools_entry(agent_dir=None):
|
|
115
|
+
"""解析 MC 的**只读工具入口**(`dist/subagent-entry.js`)——"mc-tools" 档用。
|
|
116
|
+
|
|
117
|
+
为什么这样解析而不硬编码路径:MC 自己就是用"主入口的**兄弟文件**"
|
|
118
|
+
(其源码 `resolveSiblingEntryPath("subagent-entry.js")`)定位它。我们的
|
|
119
|
+
等价做法 = 从 pi 的注册表(settings.json.packages)找到 MC 包目录 → 读它
|
|
120
|
+
package.json 声明的扩展入口(`pi.extensions[0]`)→ 取同目录下的
|
|
121
|
+
subagent-entry.js。
|
|
122
|
+
|
|
123
|
+
**失败语义**:任一步缺失返回 `(None, 原因)`——**由调用方按策略决定**:
|
|
124
|
+
loop 在生产态做**可见降级**(打印一行说明后按零扩展运行 ✓ 无静默),
|
|
125
|
+
在严格态(`MV_MC_TOOLS_STRICT=1`,测试/探针保真)直接报错。
|
|
126
|
+
|
|
127
|
+
依赖边界(决策 20):mc-tools **允许而非要求** MC——缺 MC 即降级为零扩展;
|
|
128
|
+
`none` 档零依赖(无 MC 的机器/CI 显式选它)。
|
|
129
|
+
"""
|
|
130
|
+
agent_dir = agent_dir or pi_agent_dir()
|
|
131
|
+
try:
|
|
132
|
+
with open(os.path.join(agent_dir, "settings.json"), encoding="utf-8") as f:
|
|
133
|
+
pkgs = json.load(f).get("packages") or []
|
|
134
|
+
except (OSError, ValueError) as e:
|
|
135
|
+
return None, f"读不到 pi 的 packages({agent_dir}/settings.json): {e}"
|
|
136
|
+
# F5:按**裸包名精确相等**识别(npm 源直接比;路径源读其 package.json.name);
|
|
137
|
+
# F6:遍历**全部**候选、取首个可解析(此前首个匹配失败即停,装着 MC 也会
|
|
138
|
+
# 报"没装",文案误导)
|
|
139
|
+
seen = [] # 候选(source 字符串)——用于错误文案,说明"试过哪些"
|
|
140
|
+
for entry in pkgs:
|
|
141
|
+
source = _entry_source(entry)
|
|
142
|
+
if not source:
|
|
143
|
+
continue
|
|
144
|
+
pkg_dir = _package_dir(source, agent_dir)
|
|
145
|
+
if not pkg_dir:
|
|
146
|
+
continue
|
|
147
|
+
name = _npm_package_name(source)
|
|
148
|
+
if not name: # 路径源:读它的包名(读不到就跳过)
|
|
149
|
+
try:
|
|
150
|
+
with open(os.path.join(pkg_dir, "package.json"),
|
|
151
|
+
encoding="utf-8") as f:
|
|
152
|
+
name = json.load(f).get("name") or ""
|
|
153
|
+
except (OSError, ValueError):
|
|
154
|
+
name = ""
|
|
155
|
+
if name != MC_PACKAGE:
|
|
156
|
+
continue
|
|
157
|
+
seen.append(source)
|
|
158
|
+
try:
|
|
159
|
+
with open(os.path.join(pkg_dir, "package.json"), encoding="utf-8") as f:
|
|
160
|
+
man = json.load(f)
|
|
161
|
+
except (OSError, ValueError) as e:
|
|
162
|
+
return None, f"读不到 {MC_PACKAGE} 的 package.json: {e}"
|
|
163
|
+
exts = (man.get("pi") or {}).get("extensions")
|
|
164
|
+
if isinstance(exts, str): # F7:字符串形态(取首字符会解析错路径)
|
|
165
|
+
exts = [exts]
|
|
166
|
+
if not isinstance(exts, list) or not exts or not all(
|
|
167
|
+
isinstance(x, str) and x for x in exts):
|
|
168
|
+
return None, f"{MC_PACKAGE} 的 pi.extensions 形态不可用(版本不兼容?)"
|
|
169
|
+
cand = os.path.normpath(
|
|
170
|
+
os.path.join(pkg_dir, os.path.dirname(exts[0]), "subagent-entry.js"))
|
|
171
|
+
if os.path.isfile(cand):
|
|
172
|
+
return cand, ""
|
|
173
|
+
# 该候选不可解析 → 继续看后面的候选(F6)
|
|
174
|
+
last_missing = os.path.relpath(cand, pkg_dir)
|
|
175
|
+
if seen:
|
|
176
|
+
return None, (f"{MC_PACKAGE} 的只读工具入口不存在({last_missing})"
|
|
177
|
+
f"——上游版本可能改了布局")
|
|
178
|
+
return None, (f"packages 里没有可解析的 {MC_PACKAGE}——装它"
|
|
179
|
+
f"(pi install npm:{MC_PACKAGE}),或改用零扩展档:"
|
|
180
|
+
f"--extension-policy none")
|
|
181
|
+
|
|
182
|
+
|
|
63
183
|
def pi_agent_dir():
|
|
64
184
|
"""pi 的 agent 目录(`$PI_CODING_AGENT_DIR` 或 `~/.pi/agent`)——**单一实现**。
|
|
65
185
|
|
|
66
|
-
|
|
67
|
-
|
|
186
|
+
**单一实现**:spec_gen / 本模块(会话登记日志、扩展入口解析)都走本函数
|
|
187
|
+
(F9:此前 spec_gen 自持常量、两处各拼一次;测试改为 patch 环境变量)。
|
|
68
188
|
"""
|
|
69
189
|
return os.environ.get("PI_CODING_AGENT_DIR") or os.path.expanduser("~/.pi/agent")
|
|
70
190
|
|
|
@@ -610,6 +730,39 @@ def parse_log_nameonly(output):
|
|
|
610
730
|
# 两种语义角色(勿混):FORK_MODES 是**处理哪个模式**(分派仍用字面量);
|
|
611
731
|
# DEFAULT_FORK_MODE 是**缺省填谁**(仅默认值位置,全仓引此常量)。
|
|
612
732
|
# 历史值 active/curated(rename 前)按非法值处理(解析入口即报错)。
|
|
733
|
+
# ---- agent 进程的扩展策略(design.md 决策 20)----
|
|
734
|
+
# 三种语义(勿混):EXTENSION_POLICIES 是**合法值域**(分派与值域守卫引它);
|
|
735
|
+
# DEFAULT_EXTENSION_POLICY 是**缺省填谁**(全仓引此常量)。
|
|
736
|
+
# mc-tools : **默认**——只要 MC 的**只读检索工具**(ctx_search)。为什么默认它:
|
|
737
|
+
# agents 需要主项目背景(背景蒸馏机制已移除),这是它的补充通道;
|
|
738
|
+
# 该入口**只注册工具、不装 hook** → historian/压缩不在其中
|
|
739
|
+
# (受控实测 historian 0/6、ctx_search 可用;成本未测得显著差异)。
|
|
740
|
+
# 代价:本档**允许而非要求** MC(缺则可见降级为零扩展;严格模式见
|
|
741
|
+
# MC_TOOLS_STRICT_ENV)
|
|
742
|
+
# none : 零扩展——最快、**零依赖**(不依赖任何扩展;无 MC 的机器/CI 用这档)
|
|
743
|
+
# all : 走 pi 默认扩展发现(A/B 实验与显式 opt-in 用)
|
|
744
|
+
# (顺序只影响 CLI 帮助的罗列——**不承载语义**,勿按下标取值:
|
|
745
|
+
# 默认档看 DEFAULT_EXTENSION_POLICY)
|
|
746
|
+
EXTENSION_POLICIES = ("mc-tools", "none", "all")
|
|
747
|
+
DEFAULT_EXTENSION_POLICY = "mc-tools"
|
|
748
|
+
# mc-tools 档**允许**(而非要求)MC:找不到 MC 时降级为零扩展,但必须**可见**
|
|
749
|
+
# (打印一行说明 `ctx_search` 本次不可用)——无静默铁律。
|
|
750
|
+
# 测试/探针要保真(确认"本场确实带着 ctx_search 在跑")时,用环境变量把它变严格:
|
|
751
|
+
# MV_MC_TOOLS_STRICT=1 → 解析失败即报错退出(测试环境准确性优先,用户 2026-09-14 定)
|
|
752
|
+
MC_TOOLS_STRICT_ENV = "MV_MC_TOOLS_STRICT"
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def mc_tools_strict():
|
|
756
|
+
"""严格模式?(仅测试/探针用;生产默认 False = 允许降级)
|
|
757
|
+
|
|
758
|
+
为什么需要它:降级是**可见的**,但"没降级"这件事在测试里必须能被断言——
|
|
759
|
+
否则一个测试可能"通过"而实际上 ctx_search 从未安装(测试环境准确性)。
|
|
760
|
+
"""
|
|
761
|
+
return os.environ.get(MC_TOOLS_STRICT_ENV) == "1"
|
|
762
|
+
# "mc-tools" 档引用的包(该档 = 那个包的能力,故具名引用而非通用机制:
|
|
763
|
+
# 入口是它的内部文件,路径解析见 resolve_mc_tools_entry 的 docstring)
|
|
764
|
+
MC_PACKAGE = "@cortexkit/pi-magic-context"
|
|
765
|
+
|
|
613
766
|
FORK_MODES = ("budget", "compaction", "full")
|
|
614
767
|
DEFAULT_FORK_MODE = "budget"
|
|
615
768
|
#
|
package/meeting_loop.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
复用 meeting_engine 的唯一状态机,只注入"唤醒 pi"的 responder。
|
|
5
5
|
协议逻辑(锁/配额/级联/信号)全在引擎,此处只做 LLM 交互。
|
|
6
6
|
|
|
7
|
-
用法:python3 meeting_loop.py <workdir> <agent> [--
|
|
7
|
+
用法:python3 meeting_loop.py <workdir> <agent> [--extension-policy V]
|
|
8
8
|
(配额/超时从 protocol.json 读——单一事实源;无 CLI 覆盖)
|
|
9
9
|
"""
|
|
10
10
|
|
|
@@ -289,7 +289,7 @@ def _prepare_fork_session(workdir, agent, sid, fork_source, fork_cwd,
|
|
|
289
289
|
|
|
290
290
|
|
|
291
291
|
def _build_wake_cmd(workdir, agent, sid, cfg, fork_source, fork_cwd,
|
|
292
|
-
session_dir, first_wake,
|
|
292
|
+
session_dir, first_wake, extension_policy, prompt,
|
|
293
293
|
fork_mode=meeting_fs.DEFAULT_FORK_MODE, topic=""):
|
|
294
294
|
"""组装唤醒命令(#3 拆分,e2e7 评审):返回 (cmd, spawn_cwd)。
|
|
295
295
|
|
|
@@ -309,19 +309,50 @@ def _build_wake_cmd(workdir, agent, sid, cfg, fork_source, fork_cwd,
|
|
|
309
309
|
else:
|
|
310
310
|
cmd = ["pi", "--mode", "json", "--session-id", sid,
|
|
311
311
|
"--session-dir", session_dir]
|
|
312
|
-
# ---- agent 进程的扩展策略(design.md 决策 20)----
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
312
|
+
# ---- agent 进程的扩展策略(design.md 决策 20;三档见 meeting_fs)----
|
|
313
|
+
# 三档共用**同一段零扩展前缀**;mc-tools 只决定"能否再追加 -e <MC 入口>"。
|
|
314
|
+
# 为什么这样写:此前 mc-tools 的降级分支自己拼前缀后**提前 return**,把后面的
|
|
315
|
+
# model/thinking/system-prompt/--print 与 spawn cwd 全截断——那条"降级"路径在
|
|
316
|
+
# 无 MC 的机器上产出残缺命令(e2e24 评审 S1;根因 = 前缀抄三遍 + 短路返回)。
|
|
317
|
+
# 现在**只有一个出口**(本函数末尾),降级只是"少追加一个 -e"。
|
|
318
|
+
#
|
|
319
|
+
# 三档语义:mc-tools(默认)给 agents `ctx_search`(MC 的只读检索工具;
|
|
320
|
+
# entry 只注册工具、不装 hook → 不带 historian/压缩);none 零扩展(零依赖);
|
|
321
|
+
# all 走 pi 默认发现(A/B 与显式 opt-in,须有净收益账)。
|
|
322
|
+
no_ext = ["--no-extensions", "--no-skills", "--no-prompt-templates",
|
|
323
|
+
"--no-themes"]
|
|
324
|
+
effective_policy = extension_policy
|
|
325
|
+
downgrade_reason = ""
|
|
326
|
+
if extension_policy not in meeting_fs.EXTENSION_POLICIES:
|
|
327
|
+
# 值域守卫在 __main__ 已拦(配置错误不进 engine);直调也要响
|
|
328
|
+
raise RuntimeError(f"未知 extensionPolicy: {extension_policy!r}"
|
|
329
|
+
f"(合法值: {'/'.join(meeting_fs.EXTENSION_POLICIES)})")
|
|
330
|
+
if extension_policy == "none":
|
|
331
|
+
cmd += no_ext
|
|
332
|
+
elif extension_policy == "mc-tools":
|
|
333
|
+
entry, err = meeting_fs.resolve_mc_tools_entry()
|
|
334
|
+
if entry:
|
|
335
|
+
cmd += no_ext + ["-e", entry]
|
|
336
|
+
elif meeting_fs.mc_tools_strict():
|
|
337
|
+
# 严格模式(测试/探针保真):缺 MC 即响,不降级——否则测试可能在
|
|
338
|
+
# "没装 MC"的环境里通过,而 ctx_search 从未生效
|
|
339
|
+
log(agent, f"[fatal] mc-tools 档入口解析失败(严格模式):{err}")
|
|
340
|
+
raise RuntimeError(f"mc-tools 档不可用: {err}")
|
|
341
|
+
else:
|
|
342
|
+
# mc-tools **允许**而非要求 MC:缺 MC → 降级零扩展,但**可见**
|
|
343
|
+
effective_policy = "none"
|
|
344
|
+
downgrade_reason = err
|
|
345
|
+
cmd += no_ext
|
|
346
|
+
# "all":不加任何 --no-*(走 pi 默认发现)
|
|
347
|
+
if first_wake:
|
|
348
|
+
# 登记行(观测面的稳定字段;报告据此给"声明 vs 生效")。只在首唤打:
|
|
349
|
+
# 策略在一次运行内不变,变了也是配置错误(重跑即可)。
|
|
350
|
+
log(agent, f"扩展策略: 声明={extension_policy} 生效={effective_policy}"
|
|
351
|
+
f" strict={int(meeting_fs.mc_tools_strict())}"
|
|
352
|
+
+ (f" 降级原因={downgrade_reason}" if downgrade_reason else ""))
|
|
353
|
+
if downgrade_reason:
|
|
354
|
+
log(agent, f"mc-tools 档未生效({downgrade_reason})——本次按零扩展"
|
|
355
|
+
f"运行:ctx_search 不可用")
|
|
325
356
|
model = cfg.get("model") or ""
|
|
326
357
|
if model:
|
|
327
358
|
cmd += ["--model", model]
|
|
@@ -406,8 +437,9 @@ def _run_wake_proc(cmd, spawn_cwd, workdir, agent):
|
|
|
406
437
|
err or "")
|
|
407
438
|
|
|
408
439
|
|
|
409
|
-
def wake_llm(workdir, agent, prompt,
|
|
410
|
-
|
|
440
|
+
def wake_llm(workdir, agent, prompt,
|
|
441
|
+
extension_policy=meeting_fs.DEFAULT_EXTENSION_POLICY,
|
|
442
|
+
fork_source=None, fork_cwd=None,
|
|
411
443
|
fork_mode=meeting_fs.DEFAULT_FORK_MODE, topic=""):
|
|
412
444
|
"""唤醒 pi(fork-only:首唤由本地生成 fork 源 + `--session` 打开,
|
|
413
445
|
后续 `--session-id` 续接)。返回 (sessionID, returncode)。
|
|
@@ -432,7 +464,7 @@ def wake_llm(workdir, agent, prompt, with_extensions=False, fork_source=None,
|
|
|
432
464
|
sid = str(uuid.uuid4())
|
|
433
465
|
cmd, spawn_cwd = _build_wake_cmd(workdir, agent, sid, cfg, fork_source,
|
|
434
466
|
fork_cwd, session_dir, first_wake,
|
|
435
|
-
|
|
467
|
+
extension_policy, prompt, fork_mode, topic)
|
|
436
468
|
|
|
437
469
|
log_dir = os.path.join(base, "wake-logs")
|
|
438
470
|
os.makedirs(log_dir, exist_ok=True)
|
|
@@ -509,7 +541,7 @@ def _read_perspective_brief(workdir, agent):
|
|
|
509
541
|
return brief or None
|
|
510
542
|
|
|
511
543
|
|
|
512
|
-
def make_responder(
|
|
544
|
+
def make_responder(extension_policy, fork_source=None, fork_cwd=None,
|
|
513
545
|
fork_mode=meeting_fs.DEFAULT_FORK_MODE, topic=""):
|
|
514
546
|
"""构造真实 LLM responder:唤醒 pi,LLM 写内容文件。
|
|
515
547
|
|
|
@@ -534,7 +566,7 @@ def make_responder(with_extensions, fork_source=None, fork_cwd=None,
|
|
|
534
566
|
if mem_available_mb() < MIN_MEM_MB:
|
|
535
567
|
log(agent, "内存不足——抛可恢复异常(不代写 freezing,下轮重试)")
|
|
536
568
|
raise RecoverableWakeError("内存不足")
|
|
537
|
-
wake_llm(workdir, agent, prompt,
|
|
569
|
+
wake_llm(workdir, agent, prompt, extension_policy,
|
|
538
570
|
fork_source=fork_source, fork_cwd=fork_cwd)
|
|
539
571
|
return True
|
|
540
572
|
if rr_turn:
|
|
@@ -554,7 +586,7 @@ def make_responder(with_extensions, fork_source=None, fork_cwd=None,
|
|
|
554
586
|
if mem_available_mb() < MIN_MEM_MB:
|
|
555
587
|
log(agent, "内存不足——抛可恢复异常(不代写 freezing,下轮重试)")
|
|
556
588
|
raise RecoverableWakeError("内存不足")
|
|
557
|
-
wake_llm(workdir, agent, prompt,
|
|
589
|
+
wake_llm(workdir, agent, prompt, extension_policy,
|
|
558
590
|
fork_source=fork_source, fork_cwd=fork_cwd,
|
|
559
591
|
fork_mode=fork_mode, topic=topic)
|
|
560
592
|
return True
|
|
@@ -574,12 +606,16 @@ if __name__ == "__main__":
|
|
|
574
606
|
if len(sys.argv) < 3:
|
|
575
607
|
print("用法: python3 meeting_loop.py <workdir> <agent> "
|
|
576
608
|
"[--max-meeting N] [--max-rr N] [--stall-timeout S]"
|
|
577
|
-
" [--
|
|
609
|
+
" [--extension-policy none|mc-tools|all]")
|
|
578
610
|
sys.exit(1)
|
|
579
611
|
workdir, agent = sys.argv[1], sys.argv[2]
|
|
580
612
|
recover_git_lock(workdir, agent)
|
|
581
|
-
#
|
|
582
|
-
|
|
613
|
+
# 扩展策略:CLI 先填,协议字段(若有)覆盖——与 forkMode 同款
|
|
614
|
+
extension_policy = meeting_fs.DEFAULT_EXTENSION_POLICY
|
|
615
|
+
if "--extension-policy" in sys.argv:
|
|
616
|
+
i = sys.argv.index("--extension-policy")
|
|
617
|
+
if i + 1 < len(sys.argv):
|
|
618
|
+
extension_policy = sys.argv[i + 1]
|
|
583
619
|
mm, mr, st = 10, 7, 600
|
|
584
620
|
# 协议从 **bare HEAD** 读(单一来源 = 共享事实;本地副本 LLM 可改)——
|
|
585
621
|
# 与 engine participants()/check_status 同一原语
|
|
@@ -589,8 +625,14 @@ if __name__ == "__main__":
|
|
|
589
625
|
print(f"[fatal] protocol.json 读取失败(bare HEAD 无有效内容): {bare}",
|
|
590
626
|
flush=True)
|
|
591
627
|
sys.exit(1)
|
|
592
|
-
if proto.get("
|
|
593
|
-
|
|
628
|
+
if proto.get("extensionPolicy"):
|
|
629
|
+
extension_policy = proto["extensionPolicy"]
|
|
630
|
+
if extension_policy not in meeting_fs.EXTENSION_POLICIES:
|
|
631
|
+
# 值域守卫(与 forkMode 同款四层守卫之一:loop 门)——非法值不进 engine
|
|
632
|
+
# 重试路径,直接 fatal 退出(配置错误就该在启动时响)
|
|
633
|
+
print(f"[fatal] 非法 extensionPolicy: {extension_policy!r}"
|
|
634
|
+
f"(合法值: {'/'.join(meeting_fs.EXTENSION_POLICIES)})", flush=True)
|
|
635
|
+
sys.exit(1)
|
|
594
636
|
if proto.get("maxMeetingRounds"):
|
|
595
637
|
mm = proto["maxMeetingRounds"]
|
|
596
638
|
if proto.get("maxRRRounds"):
|
|
@@ -615,7 +657,7 @@ if __name__ == "__main__":
|
|
|
615
657
|
sys.exit(1)
|
|
616
658
|
try:
|
|
617
659
|
agent_loop(workdir, agent,
|
|
618
|
-
make_responder(
|
|
660
|
+
make_responder(extension_policy,
|
|
619
661
|
fork_source=fork_source,
|
|
620
662
|
fork_cwd=proto.get("forkCwd") or "",
|
|
621
663
|
fork_mode=fork_mode_cfg,
|
package/mv_cli.py
CHANGED
|
@@ -42,6 +42,7 @@ PROG = os.environ.get("MV_INVOKED_AS") or "scripts/mv.sh"
|
|
|
42
42
|
USAGE = f"""用法:
|
|
43
43
|
{PROG} --prepare "<问题>" [--background "<背景>"] [--agents "a,b,c"|4]
|
|
44
44
|
{PROG} --start <spec目录> [--fork-mode compaction|budget|full]
|
|
45
|
+
{PROG} --start <spec目录> [--extension-policy none|mc-tools|all]
|
|
45
46
|
{PROG} --status [dir]
|
|
46
47
|
{PROG} --report [dir]
|
|
47
48
|
{PROG} --wait [dir]
|