pi-multi-viewers 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +37 -1
- package/README.md +7 -5
- package/docs/design.md +207 -2
- package/docs/reviews/2026-09-12-e2e17-thinking-level-analysis.md +374 -0
- package/docs/reviews/2026-09-13-e2e19-scoped-config-review.md +126 -0
- package/docs/reviews/2026-09-13-e2e20-pure-run-review.md +159 -0
- package/docs/reviews/2026-09-13-e2e21-postfix-review.md +257 -0
- package/docs/reviews/2026-09-13-e2e23-time-breakdown-analysis.md +175 -0
- package/docs/reviews/2026-09-14-e2e24-extension-policy-review.md +146 -0
- package/docs/reviews/README.md +8 -2
- package/docs/test-methodology.md +85 -0
- package/meeting_fs.py +202 -5
- package/meeting_loop.py +90 -26
- package/mv_cli.py +1 -0
- package/observability.py +394 -29
- package/package.json +1 -1
- package/prompts/multi-viewers.md +1 -1
- package/scripts/check-residue.sh +39 -17
- package/scripts/pi-probe.sh +134 -0
- package/spec_gen.py +28 -21
- package/start_discussion.py +44 -25
- package/templates/spec-readme.md.tpl +17 -7
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# pi-probe.sh —— LLM 探针(把临时 pi 运行变成**可追溯、经同意的测试产物**)
|
|
4
|
+
#
|
|
5
|
+
# 它管三件事(都是吃过的教训):
|
|
6
|
+
#
|
|
7
|
+
# 1. **闸门:没有当次同意就不跑**(2026-09-13 教训)
|
|
8
|
+
# 我在未征得用户同意的情况下连跑 6 个真实 pi 进程(合计 ~9.5 分钟,其中一个
|
|
9
|
+
# 458s),理由是"在后台跑、不占用户终端"。判据本应是**预估时长**(用户规则:
|
|
10
|
+
# 秒级冒烟免批;分钟级/多轮必须当次确认),且**不因后台运行而豁免**。
|
|
11
|
+
# 现在:不带 `--approved "<凭据>"` 直接**拒绝执行**并提示先去问。
|
|
12
|
+
#
|
|
13
|
+
# 2. **登记:跑过的 LLM 运行留下账本**
|
|
14
|
+
# `~/.pi/pi-multi-viewers-llm-runs.log` 每行一次运行:起止时间、时长、退出码、
|
|
15
|
+
# 凭据、命令摘要。汇报时给账本,不靠记忆。
|
|
16
|
+
#
|
|
17
|
+
# 3. **收尾提醒:跑完先汇报,再决定下一步**
|
|
18
|
+
# 结束时打印一行显式提醒——2026-09-13 的第二个错误正是"跑完没汇报就接着跑
|
|
19
|
+
# 第二批实验"。
|
|
20
|
+
#
|
|
21
|
+
# 另外它把新建的 session 登记进 `~/.pi/pi-multi-viewers-test-sessions.log`
|
|
22
|
+
# (与 build_bootstrap 同一份),使 check-residue 能判定归属(否则白名单 cwd
|
|
23
|
+
# 下的探针 session 完全不可见)。
|
|
24
|
+
#
|
|
25
|
+
# 用法:
|
|
26
|
+
# ./scripts/pi-probe.sh --approved "用户 2026-09-13 15:20 同意跑 AFT 隔离实验" \
|
|
27
|
+
# --print "ok"
|
|
28
|
+
# ./scripts/pi-probe.sh --approved "..." --no-extensions --print "ok"
|
|
29
|
+
#
|
|
30
|
+
# 退出码 = pi 的退出码(闸门拒绝时为 2)。
|
|
31
|
+
set -u
|
|
32
|
+
|
|
33
|
+
SESS_DIR="$HOME/.pi/agent/sessions"
|
|
34
|
+
REGISTRY="$HOME/.pi/pi-multi-viewers-test-sessions.log"
|
|
35
|
+
LEDGER="$HOME/.pi/pi-multi-viewers-llm-runs.log"
|
|
36
|
+
|
|
37
|
+
usage() {
|
|
38
|
+
cat >&2 <<'EOS'
|
|
39
|
+
用法: pi-probe.sh --approved "<凭据>" [pi 参数...]
|
|
40
|
+
|
|
41
|
+
--approved "<凭据>" 必填:用户**当次**同意的凭据(原话/时间/授权范围)。
|
|
42
|
+
没有它就说明还没问——先去问,不要绕过这里直接调 pi。
|
|
43
|
+
|
|
44
|
+
判据(用户规则):预估**时长**——秒级冒烟免批;预计 ≥30s、多轮唤醒、
|
|
45
|
+
整场分析一律**当次**确认,且不因"后台跑、不占用户终端"豁免。
|
|
46
|
+
EOS
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
APPROVED=""
|
|
50
|
+
if [ "$#" -gt 0 ] && [ "$1" = "--approved" ]; then
|
|
51
|
+
APPROVED="${2:-}"
|
|
52
|
+
shift 2 2>/dev/null || true
|
|
53
|
+
fi
|
|
54
|
+
if [ -z "$APPROVED" ]; then
|
|
55
|
+
echo "拒绝执行:没有 --approved(= 未取得用户当次同意)。" >&2
|
|
56
|
+
usage
|
|
57
|
+
exit 2
|
|
58
|
+
fi
|
|
59
|
+
if [ "$#" -eq 0 ]; then
|
|
60
|
+
echo "拒绝执行:缺少 pi 参数。" >&2
|
|
61
|
+
usage
|
|
62
|
+
exit 2
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
cmd_brief="pi $*"
|
|
66
|
+
|
|
67
|
+
BEFORE="$(mktemp)"
|
|
68
|
+
AFTER="$(mktemp)"
|
|
69
|
+
trap 'rm -f "$BEFORE" "$AFTER"' EXIT
|
|
70
|
+
find "$SESS_DIR" -name '*.jsonl' -print 2>/dev/null | sort > "$BEFORE"
|
|
71
|
+
|
|
72
|
+
T0=$(date +%s)
|
|
73
|
+
pi "$@"
|
|
74
|
+
rc=$?
|
|
75
|
+
T1=$(date +%s)
|
|
76
|
+
|
|
77
|
+
# 账本(运行即登记:时长 / 退出码 / 凭据 / 命令摘要)
|
|
78
|
+
python3 - "$LEDGER" "$T0" "$T1" "$rc" "$APPROVED" "$cmd_brief" <<'PYEOF'
|
|
79
|
+
import json, sys
|
|
80
|
+
from datetime import datetime, timezone
|
|
81
|
+
ledger, t0, t1, rc, approved, cmd = sys.argv[1:7]
|
|
82
|
+
rec = {
|
|
83
|
+
"started": datetime.fromtimestamp(int(t0), timezone.utc).isoformat(),
|
|
84
|
+
"seconds": int(t1) - int(t0),
|
|
85
|
+
"rc": int(rc),
|
|
86
|
+
"approved": approved,
|
|
87
|
+
"cmd": cmd[:400],
|
|
88
|
+
}
|
|
89
|
+
with open(ledger, "a", encoding="utf-8") as f:
|
|
90
|
+
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
|
91
|
+
print(f"[pi-probe] 账本已记:{rec['seconds']}s rc={rec['rc']} | 同意凭据:{approved[:60]}")
|
|
92
|
+
PYEOF
|
|
93
|
+
|
|
94
|
+
# session 登记(归属判定用)
|
|
95
|
+
find "$SESS_DIR" -name '*.jsonl' -print 2>/dev/null | sort > "$AFTER"
|
|
96
|
+
NEW=$(comm -13 "$BEFORE" "$AFTER")
|
|
97
|
+
if [ -n "$NEW" ]; then
|
|
98
|
+
echo "$NEW" | while IFS= read -r f; do
|
|
99
|
+
[ -n "$f" ] || continue
|
|
100
|
+
python3 - "$f" "$REGISTRY" <<'PYEOF'
|
|
101
|
+
import json, os, sys
|
|
102
|
+
from datetime import datetime, timezone
|
|
103
|
+
path, registry = sys.argv[1], sys.argv[2]
|
|
104
|
+
sid = cwd = ""
|
|
105
|
+
with open(path, encoding="utf-8") as f:
|
|
106
|
+
for line in f:
|
|
107
|
+
try:
|
|
108
|
+
ev = json.loads(line)
|
|
109
|
+
except ValueError:
|
|
110
|
+
continue
|
|
111
|
+
if ev.get("type") == "session":
|
|
112
|
+
sid = ev.get("id") or ""
|
|
113
|
+
cwd = ev.get("cwd") or ""
|
|
114
|
+
break
|
|
115
|
+
rec = {
|
|
116
|
+
"created": datetime.now(timezone.utc).isoformat(),
|
|
117
|
+
"session_id": sid,
|
|
118
|
+
"path": os.path.abspath(path),
|
|
119
|
+
"cwd": cwd,
|
|
120
|
+
"origin": "pi-probe",
|
|
121
|
+
}
|
|
122
|
+
with open(registry, "a", encoding="utf-8") as f:
|
|
123
|
+
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
|
124
|
+
print(f"[pi-probe] session 已登记: {path}")
|
|
125
|
+
print(f"[pi-probe] session_id={sid} cwd={cwd}(用完删掉该文件)")
|
|
126
|
+
PYEOF
|
|
127
|
+
done
|
|
128
|
+
else
|
|
129
|
+
echo "[pi-probe] 未新建 session(用了 --session/--session-id 续接既有会话?)"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# 收尾提醒(2026-09-13 教训:跑完先汇报,别接着跑下一批)
|
|
133
|
+
echo "[pi-probe] ⚠ 先把本次结果汇报给用户并停下等他指示——不要连续追加第二批实验。"
|
|
134
|
+
exit "$rc"
|
package/spec_gen.py
CHANGED
|
@@ -17,12 +17,11 @@ import sys
|
|
|
17
17
|
|
|
18
18
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
19
19
|
TPL_DIR = os.path.join(HERE, "templates")
|
|
20
|
-
|
|
21
|
-
os.path.expanduser("~/.pi/agent"))
|
|
20
|
+
# pi agent 目录的**单一实现**在 meeting_fs.pi_agent_dir()(F9:此前两处各拼一次)
|
|
22
21
|
MAX_AGENT_NAME_LEN = 32
|
|
23
22
|
|
|
24
23
|
import meeting_fs
|
|
25
|
-
from meeting_fs import run_git, DEFAULT_STALL_TIMEOUT
|
|
24
|
+
from meeting_fs import run_git, DEFAULT_STALL_TIMEOUT, DEFAULT_THINKING
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
def _join_model_ref(provider, model_id):
|
|
@@ -56,7 +55,7 @@ def _default_model():
|
|
|
56
55
|
获取失败(pi 不可用/无 settings)→ 返回 None。
|
|
57
56
|
"""
|
|
58
57
|
try:
|
|
59
|
-
with open(os.path.join(
|
|
58
|
+
with open(os.path.join(meeting_fs.pi_agent_dir(), "settings.json")) as f:
|
|
60
59
|
cfg = json.load(f)
|
|
61
60
|
provider = cfg.get("defaultProvider") or ""
|
|
62
61
|
model = cfg.get("defaultModel") or ""
|
|
@@ -123,7 +122,7 @@ def pi_sessions_dir(cwd):
|
|
|
123
122
|
AGENTS.md 明言"编码错一根横线 = 静默解析不到"——风险点不应复制。
|
|
124
123
|
"""
|
|
125
124
|
enc = "--" + cwd.strip("/").replace("/", "-") + "--"
|
|
126
|
-
return os.path.join(
|
|
125
|
+
return os.path.join(meeting_fs.pi_agent_dir(), "sessions", enc)
|
|
127
126
|
|
|
128
127
|
|
|
129
128
|
def current_session_file():
|
|
@@ -276,24 +275,27 @@ def gen_spec_skeleton(spec_dir, participants, topic=None, background=None,
|
|
|
276
275
|
"本行是说明行,不会注入。\n\n")
|
|
277
276
|
if background:
|
|
278
277
|
f.write(background + "\n")
|
|
279
|
-
# models.md(用户 8024/9204/9271:预列各 agent,每行 agent名: model
|
|
280
|
-
# variant
|
|
281
|
-
#
|
|
278
|
+
# models.md(用户 8024/9204/9271:预列各 agent,每行 agent名: model,
|
|
279
|
+
# variant;model/thinking 预填主 pi 当前值,用户少改一个文件)
|
|
280
|
+
# **两个槽都永远显式写出**(含探测失败的路径):variant 槽留空会静默落到
|
|
281
|
+
# DEFAULT_THINKING,而 spec 文件表面完全正常(甚至更"干净")——意图与
|
|
282
|
+
# 生效值之间没有留痕(e2e17 评审 §7.3)。探测失败另打一行可见提示。
|
|
282
283
|
pm, pt = _detect_pi_model_thinking()
|
|
284
|
+
variant = pt or DEFAULT_THINKING
|
|
283
285
|
with open(os.path.join(spec_dir, "models.md"), "w") as f:
|
|
284
|
-
lines = ["# models.md——模型配置(可选)。每行:agent名: model
|
|
285
|
-
"model
|
|
286
|
+
lines = ["# models.md——模型配置(可选)。每行:agent名: model, variant。"
|
|
287
|
+
"model 写 default = 继承本机默认;variant 不写 = "
|
|
288
|
+
f"{DEFAULT_THINKING}(**两个槽都显式写出**,一眼可见)。"
|
|
286
289
|
"本行是说明行,不会注入。"]
|
|
287
290
|
for p in participants:
|
|
288
|
-
|
|
289
|
-
lines.append(f"{p}: {pm}, {pt}")
|
|
290
|
-
elif pm:
|
|
291
|
-
lines.append(f"{p}: {pm}")
|
|
292
|
-
elif pt:
|
|
293
|
-
lines.append(f"{p}: default, {pt}")
|
|
294
|
-
else:
|
|
295
|
-
lines.append(f"{p}: default")
|
|
291
|
+
lines.append(f"{p}: {pm or 'default'}, {variant}")
|
|
296
292
|
f.write("\n".join(lines) + "\n")
|
|
293
|
+
if not pt:
|
|
294
|
+
# 探测失败**可见**(不是静默取档):打印在使用者能看到的地方,
|
|
295
|
+
# 不阻断(终端直用时 PI_* 环境变量本就不存在,报错会断掉合法路径)。
|
|
296
|
+
print(f"[spec-gen] 未探测到主 pi 的 thinking 档位——models.md 用默认 "
|
|
297
|
+
f"{DEFAULT_THINKING}(如需其它档位请直接编辑该文件)",
|
|
298
|
+
file=sys.stderr)
|
|
297
299
|
# agents/X.md 占位 + .order(仅显式 --agents 时;快照路径的 .order
|
|
298
300
|
# 由 _snapshot_viewers 写入,此处不得重写)
|
|
299
301
|
if participants and not viewers_dir:
|
|
@@ -367,7 +369,8 @@ def gen_question(topic, stances, background, questions):
|
|
|
367
369
|
return "\n".join(lines)
|
|
368
370
|
|
|
369
371
|
|
|
370
|
-
def gen_protocol(topic, participants, max_meeting, max_rr,
|
|
372
|
+
def gen_protocol(topic, participants, max_meeting, max_rr,
|
|
373
|
+
extension_policy=meeting_fs.DEFAULT_EXTENSION_POLICY,
|
|
371
374
|
result_writer=None,
|
|
372
375
|
stall_timeout=DEFAULT_STALL_TIMEOUT,
|
|
373
376
|
fork_source=None, fork_cwd=None,
|
|
@@ -385,8 +388,12 @@ def gen_protocol(topic, participants, max_meeting, max_rr, pure=False,
|
|
|
385
388
|
"stallTimeoutSeconds": stall_timeout,
|
|
386
389
|
"commitPolicy": "one-message-per-commit",
|
|
387
390
|
}
|
|
388
|
-
|
|
389
|
-
|
|
391
|
+
# 显式写出扩展策略(值域与默认值的家在 meeting_fs;此处只写选定值)。
|
|
392
|
+
# 非法值就地报错(不落盘半成品)——与 forkMode 同款:配置错误不进运行时
|
|
393
|
+
if extension_policy not in meeting_fs.EXTENSION_POLICIES:
|
|
394
|
+
raise ValueError(f"非法 extension_policy: {extension_policy!r}"
|
|
395
|
+
f"(合法值: {'/'.join(meeting_fs.EXTENSION_POLICIES)})")
|
|
396
|
+
proto["extensionPolicy"] = extension_policy
|
|
390
397
|
if fork_source:
|
|
391
398
|
# fork 模式(多视角):首唤挂载主 session + cwd=主项目
|
|
392
399
|
# forkMode 取值域与默认值的定义在 meeting_fs(FORK_MODES /
|
package/start_discussion.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
用法:
|
|
5
5
|
python3 start_discussion.py --dir mymeet --topic "主题" --agents a,b \
|
|
6
|
-
[--stances '{"a": "立场1", "b": "立场2"}'] [--start]
|
|
6
|
+
[--stances '{"a": "立场1", "b": "立场2"}'] [--start] \
|
|
7
|
+
[--extension-policy none|mc-tools|all] \
|
|
7
8
|
[--models '{"a": "provider/model"}'] [--max-meeting 10] [--max-rr 7]
|
|
8
9
|
|
|
9
10
|
复杂内容用 spec 规格目录(设计 16,与 CLI 内容参数互斥):
|
|
@@ -70,8 +71,11 @@ def _spec_models(spec_dir, participants):
|
|
|
70
71
|
"""解析 models.md(容错,用户 8024/9204 定):返回 {agent: (model, variant)}。
|
|
71
72
|
|
|
72
73
|
每行格式:`agent名: model, variant`(model 与 variant 逗号分隔)。
|
|
73
|
-
- model 缺省/'default' → None
|
|
74
|
-
|
|
74
|
+
- model 缺省/'default' → None(创建时填本机默认模型)——`default`
|
|
75
|
+
这个别名**只在 model 槽有意义**
|
|
76
|
+
- variant 缺省 → DEFAULT_THINKING;**没有 `default` 别名**(同词在相邻
|
|
77
|
+
两行反义会让读者必错;且空已是缺省,别名不增加表达力)——写了 `default`
|
|
78
|
+
就按原值透传给 pi(可见失败,而非静默重解释)
|
|
75
79
|
容错:空行/无 ':'/agent 不在 participants → 跳过;单字段行只有 model。
|
|
76
80
|
规则:第一行说明跳过(_spec_read)。
|
|
77
81
|
"""
|
|
@@ -93,8 +97,13 @@ def _spec_models(spec_dir, participants):
|
|
|
93
97
|
parts = [p.strip() for p in rest.split(",")]
|
|
94
98
|
model = parts[0] if parts and parts[0] else ""
|
|
95
99
|
variant = parts[1] if len(parts) > 1 else ""
|
|
100
|
+
# `default` **只在 model 槽有意义**(= 继承本机)。variant 槽不做
|
|
101
|
+
# 同名别名(e2e17 评审 §7.2:相邻两行同词反义——model 的 default
|
|
102
|
+
# = 继承,variant 的 default = max,按上一行直觉读下一行必错);
|
|
103
|
+
# 且别名并不增加表达力:空 = max 已是缺省。写 `default` 不再是
|
|
104
|
+
# 别名 → 原值传给 pi(可见的失败,而不是静默重解释成 max)。
|
|
96
105
|
m = None if (not model or model == "default") else model
|
|
97
|
-
v =
|
|
106
|
+
v = variant or meeting_fs.DEFAULT_THINKING
|
|
98
107
|
out[agent] = (m, v)
|
|
99
108
|
return out
|
|
100
109
|
|
|
@@ -303,21 +312,23 @@ def setup_environment(args, participants, base, spec_dir=None,
|
|
|
303
312
|
for p in participants}
|
|
304
313
|
agent_extra = {k: v for k, v in agent_extra.items() if v}
|
|
305
314
|
# models:spec 模式从 models.md 读(自包含,{agent: (model, variant)}),
|
|
306
|
-
# CLI --models 已互斥({agent: model} 旧格式——variant
|
|
315
|
+
# CLI --models 已互斥({agent: model} 旧格式——variant 用默认档)
|
|
307
316
|
if spec_dir:
|
|
308
317
|
models = _spec_models(spec_dir, participants)
|
|
309
318
|
else:
|
|
310
|
-
models = {p: (m,
|
|
311
|
-
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
models = {p: (m, meeting_fs.DEFAULT_THINKING)
|
|
320
|
+
for p, m in (args.models or {}).items()}
|
|
321
|
+
# 归一:每个 participant 都有 (model, variant) 条目,**此后只读**——
|
|
322
|
+
# 此前 :320 补默认模型与 :384 写 pi-agent.json 各自再写一遍
|
|
323
|
+
# `models.get(p, (None, "max"))`(同层同义重复,值两处声明,e2e17
|
|
324
|
+
# 评审 §7.10)。
|
|
325
|
+
# default 模型 → 创建时实时获取 Pi 默认模型填入:pi-agent.json 带
|
|
326
|
+
# model 后,meeting_loop 才会传 --model;骨架期 models.md 仍写 default
|
|
327
|
+
# (--spec-gen 不获取),创建时(--spec)才解析。运行期固化不变。
|
|
328
|
+
dm = _default_model() if spec_dir else None
|
|
329
|
+
for p in participants:
|
|
330
|
+
m, v = models.get(p, (None, meeting_fs.DEFAULT_THINKING))
|
|
331
|
+
models[p] = (m or dm, v)
|
|
321
332
|
# stance_ref(agent 定义"立场见 question.md"提示):spec 模式一律保留
|
|
322
333
|
# (设计 16.6:无法程序判断 question.md 有无立场节 → 一律提示;
|
|
323
334
|
# 互斥下 CLI stances 必为 None,传占位 dict 触发生成)
|
|
@@ -340,7 +351,7 @@ def setup_environment(args, participants, base, spec_dir=None,
|
|
|
340
351
|
# 共享配置(work-a 提交,setup commit 进 bare)
|
|
341
352
|
with open(os.path.join(wa, "protocol.json"), "w") as f:
|
|
342
353
|
json.dump(gen_protocol(spec_topic or args.topic, participants, args.max_meeting,
|
|
343
|
-
args.max_rr, args.
|
|
354
|
+
args.max_rr, args.extension_policy, args.result_writer,
|
|
344
355
|
args.stall_timeout,
|
|
345
356
|
fork_source=getattr(args, "fork_source", None),
|
|
346
357
|
fork_cwd=os.getcwd(),
|
|
@@ -381,14 +392,14 @@ def setup_environment(args, participants, base, spec_dir=None,
|
|
|
381
392
|
with open(os.path.join(workdir, "AGENTS.md"), "w") as f:
|
|
382
393
|
f.write(gen_agents_md(args, p, participants, spec_background,
|
|
383
394
|
main_pi_cwd=os.getcwd()))
|
|
384
|
-
mv = models
|
|
395
|
+
mv = models[p] # 归一后必有条目(见上方归一循环)
|
|
385
396
|
with open(os.path.join(workdir, ".pi/agent", f"{p}.md"), "w") as f:
|
|
386
397
|
f.write(gen_agent_def(p, participants, {p: mv[0]} if mv[0] else None,
|
|
387
398
|
stances_arg, agent_extra.get(p)))
|
|
388
399
|
with open(os.path.join(workdir, "pi-agent.json"), "w") as f:
|
|
389
400
|
json.dump({
|
|
390
401
|
"model": mv[0] or "",
|
|
391
|
-
"thinking": mv[1]
|
|
402
|
+
"thinking": mv[1] or meeting_fs.DEFAULT_THINKING,
|
|
392
403
|
"prompt_file": f".pi/agent/{p}.md",
|
|
393
404
|
}, f, indent=2, ensure_ascii=False)
|
|
394
405
|
|
|
@@ -409,7 +420,8 @@ def setup_environment(args, participants, base, spec_dir=None,
|
|
|
409
420
|
rw = args.result_writer or participants[-1]
|
|
410
421
|
print(f"[setup] 环境就绪: {base}({len(participants)} agents: {', '.join(participants)})")
|
|
411
422
|
print(f"[setup] resultWriter={rw}, maxMeeting={args.max_meeting}, maxRR={args.max_rr}, "
|
|
412
|
-
f"立场={'有' if (args.stances or spec_dir) else '无'},
|
|
423
|
+
f"立场={'有' if (args.stances or spec_dir) else '无'}, "
|
|
424
|
+
f"extensionPolicy={args.extension_policy}")
|
|
413
425
|
|
|
414
426
|
|
|
415
427
|
def _preserve_result_md(base):
|
|
@@ -501,7 +513,7 @@ from spec_gen import ( # noqa: F401
|
|
|
501
513
|
viewer_set_error, gen_agents_md, gen_protocol,
|
|
502
514
|
pi_sessions_dir, current_session_file, _join_model_ref,
|
|
503
515
|
_default_model, _detect_pi_model_thinking, resolve_fork_source,
|
|
504
|
-
|
|
516
|
+
MAX_AGENT_NAME_LEN,
|
|
505
517
|
)
|
|
506
518
|
from observability import ( # noqa: F401
|
|
507
519
|
_loop_pids, _loops_alive, check_status, build_report,
|
|
@@ -539,7 +551,13 @@ def main():
|
|
|
539
551
|
"protocol.json,各 agent 首唤由本地生成 fork "
|
|
540
552
|
"源挂载主上下文;不传 = 从主 pi 环境自动解析"
|
|
541
553
|
"(PI_SESSION_ID;解析失败明确报错)")
|
|
542
|
-
parser.add_argument(
|
|
554
|
+
parser.add_argument(
|
|
555
|
+
"--extension-policy",
|
|
556
|
+
choices=list(meeting_fs.EXTENSION_POLICIES),
|
|
557
|
+
default=meeting_fs.DEFAULT_EXTENSION_POLICY,
|
|
558
|
+
help="agents 的扩展策略:mc-tools=默认,只要 MC 的只读检索工具 "
|
|
559
|
+
"ctx_search(缺 MC 时可见降级为零扩展);none=零扩展(零依赖);"
|
|
560
|
+
"all=走 pi 默认发现")
|
|
543
561
|
parser.add_argument("--start", action="store_true", help="创建后启动讨论")
|
|
544
562
|
parser.add_argument("--skip-setup", action="store_true",
|
|
545
563
|
help="跳过环境生成,只启动已有环境(需 --dir)")
|
|
@@ -691,12 +709,13 @@ def main():
|
|
|
691
709
|
workdir = os.path.join(base, f"work-{p}")
|
|
692
710
|
cmd = [sys.executable, os.path.join(base, "meeting_loop.py"),
|
|
693
711
|
workdir, p]
|
|
694
|
-
|
|
695
|
-
|
|
712
|
+
# 扩展策略**不转发**:协议字段(gen_protocol 恒写)是唯一权威,
|
|
713
|
+
# 转发只会多一条通道(S3)。例外语义已写进 decision 20:旧协议
|
|
714
|
+
# 目录(无该字段)按默认档启动。
|
|
696
715
|
# 配额(max-meeting/max-rr/stall-timeout)是环境属性:创建时
|
|
697
716
|
# 固化在 protocol.json,启动继承(loop 读 protocol 优先)。
|
|
698
717
|
# 不传 CLI —— 避免无条件覆盖 protocol.json 的固化值
|
|
699
|
-
# (审核 C1:配额单一事实源;与
|
|
718
|
+
# (审核 C1:配额单一事实源;与 extension-policy 处理一致)
|
|
700
719
|
with open(os.path.join(base, f"loop-{p}.log"), "w") as f:
|
|
701
720
|
procs.append(subprocess.Popen(cmd, stdout=f,
|
|
702
721
|
stderr=subprocess.STDOUT,
|
|
@@ -42,14 +42,19 @@ fork 机制已让每个 agent 携带发起分析时的对话上下文(默认 b
|
|
|
42
42
|
|
|
43
43
|
- 分析只聚焦某些方面("不讨论 API 设计")
|
|
44
44
|
- 已知的硬约束("必须保持向后兼容")
|
|
45
|
-
- 用户指定的优先级("
|
|
45
|
+
- 用户指定的优先级("效率问题优先级最高")
|
|
46
46
|
|
|
47
47
|
没有就留空。
|
|
48
48
|
|
|
49
49
|
## models.md —— 模型配置(可选)
|
|
50
50
|
|
|
51
|
-
每行:`agent名: model
|
|
52
|
-
variant
|
|
51
|
+
每行:`agent名: model, variant`(**两个槽都显式写出**,一眼可见本场跑在
|
|
52
|
+
什么模型/档位上)。model 写 `default` = 继承本机默认;variant 不写 =
|
|
53
|
+
`max`。想换档位(如 `high`)改这一行即可——**逐 agent 一行**,可以只改
|
|
54
|
+
某一个视角。
|
|
55
|
+
|
|
56
|
+
> 这里写的就是**声明值**;实际生效值由 pi 解析(它自己的优先级链)。
|
|
57
|
+
> `--report` 会把声明值与 session 里的生效值对照列出。
|
|
53
58
|
|
|
54
59
|
## agents/X.md —— 视角任务书(每个 agent 一个)
|
|
55
60
|
|
|
@@ -64,7 +69,7 @@ variant 默认 max。只有不用默认/-max 时才需要改。
|
|
|
64
69
|
|
|
65
70
|
写好视角任务书的要点(实测有效的措辞模式):
|
|
66
71
|
|
|
67
|
-
1. **单一视角**:明确写出这个 agent 用什么 lenses
|
|
72
|
+
1. **单一视角**:明确写出这个 agent 用什么 lenses 看(效率/简单化/
|
|
68
73
|
安全/成本/用户体验/……),并要求"所有观点必须从该视角出发"
|
|
69
74
|
2. **不越界**:写明"其它视角由别的参与者负责,你不要越界展开"——
|
|
70
75
|
实验证明这句能避免视角串味(不要列举具体是哪几个视角——
|
|
@@ -72,10 +77,15 @@ variant 默认 max。只有不用默认/-max 时才需要改。
|
|
|
72
77
|
3. **交锋义务**:写明"对其它视角的观点可以认同或反驳,但要用本视角
|
|
73
78
|
的论据"——防止附和式讨论
|
|
74
79
|
|
|
75
|
-
范例(agents
|
|
80
|
+
范例(agents/效率.md 全文——身份那句由脚本加,不在正文里;本例内容较长,
|
|
81
|
+
要点是"把该视角盯住的东西逐项写明"):
|
|
82
|
+
|
|
83
|
+
你的所有观点必须从运行效率角度出发:时间效率(一次分析从开始到收尾要
|
|
84
|
+
多久、哪些环节在吃时间、哪些等待可消除)与运行效率(不必要的计算与
|
|
85
|
+
等待、可并行而串行的部分、随规模增长的代价)。
|
|
86
|
+
能用已有数据说出"省多少、占多少比例"就说;拿不到数据就写明需要什么
|
|
87
|
+
数据,不要凭感觉下结论。
|
|
76
88
|
|
|
77
|
-
你的所有观点必须从性能角度出发:复杂度、热点、不必要的计算、扩展性。
|
|
78
|
-
如果其它视角的优化建议会显著损害性能,你应该明确反对并说明理由。
|
|
79
89
|
其它视角由别的参与者负责,你不要越界展开。
|
|
80
90
|
对其它视角的观点可以认同或反驳,但要用本视角的论据。
|
|
81
91
|
|