diff --git a/assets/expert-panel/icon.png b/assets/expert-panel/icon.png new file mode 100644 index 0000000..56f3d89 Binary files /dev/null and b/assets/expert-panel/icon.png differ diff --git a/external_plugins/expert-panel/.zcode-plugin/plugin.json b/external_plugins/expert-panel/.zcode-plugin/plugin.json new file mode 100644 index 0000000..0f44ceb --- /dev/null +++ b/external_plugins/expert-panel/.zcode-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "expert-panel", + "version": "0.1.2", + "description": "专家团共识评审:多位不同视角专家独立作答→真匿名互评→仲裁归纳(共识率脚本计算、不足自动加辩)→主席综合→事实复核;quick/standard/deep 三档,零配置,结论按一致/分歧分离绝不硬合成。", + "description_i18n": { + "en": "Expert-panel consensus review: independent answers from multiple expert lenses → truly anonymous cross-critique → arbitration (consensus rate computed by script, extra debate round below threshold) → chair synthesis → fact verification; quick/standard/deep profiles, zero config, conclusions separate agreed items from explicit disagreements.", + "zh-CN": "专家团共识评审:多位不同视角专家独立作答→真匿名互评→仲裁归纳(共识率脚本计算、不足自动加辩)→主席综合→事实复核;quick/standard/deep 三档,零配置,结论按一致/分歧分离绝不硬合成。" + }, + "author": { "name": "Zhang24y" }, + "license": "MIT", + "skills": "./skills", + "commands": "./commands" +} diff --git a/external_plugins/expert-panel/LICENSE b/external_plugins/expert-panel/LICENSE new file mode 100644 index 0000000..e883e6f --- /dev/null +++ b/external_plugins/expert-panel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Zhang24y + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external_plugins/expert-panel/README.md b/external_plugins/expert-panel/README.md new file mode 100644 index 0000000..da2576c --- /dev/null +++ b/external_plugins/expert-panel/README.md @@ -0,0 +1,101 @@ +# expert-panel — Expert Panel Consensus Review (ZCode Plugin) + +Put an important decision in front of a panel of experts with distinct lenses: **independent answering → truly anonymous cross-critique → arbitration (consensus rate computed deterministically by script, extra debate round when below threshold) → chair synthesis → fact verification**. The output separates **stable agreed conclusions from explicitly stated disagreements**. Zero configuration — every role runs on your current session model. + +## Why + +The failure modes of a single model answer: conformity (anchoring on the first framing), authority bias (deferring to whoever sounds senior), and forced synthesis (mushing disagreement into "generally feasible"). This workflow counters each one: + +| Problem | Countermeasure | +|---|---| +| Conformity | Every expert **answers independently first**, before seeing anyone else's position | +| Authority bias | Critique inputs contain only anonymous labels (Expert A/B/C) + position + key points — **no lens name, no model** | +| False consensus | Consensus rate = agreed/(agreed+disputed), **computed deterministically by script — never self-reported by a model** | +| Splitting the difference | Below threshold → one extra debate round; still below → **disagreements are stated explicitly for human adjudication, never force-merged** | +| Fabricated facts | Objectively verifiable agreed items are checked by a fact verifier (it reads files / runs commands when it can); failures are dropped | + +A single expert failing is tolerated; the conclusion is annotated with sample completeness (absent lenses / valid expert count). + +## Installation + +**Local directory marketplace** (put this repo anywhere on your machine): + +1. ZCode → Plugin Marketplace → Add → Add Plugin Marketplace, paste the repo **directory** path +2. Personal → the marketplace → expert-panel → Install +3. Takes effect in a new session + +**CLI** (after the marketplace is added): + +```bash +zcode plugins install expert-panel@ +``` + +Requirements: ZCode desktop (the dynamic-workflows runtime is bundled). The run ledger uses `python3` if present (missing python3 does not block conclusions; the run is just not logged). + +## Usage + +Three ways to trigger it once installed: + +- Natural language: "use the expert panel to evaluate X", "have the expert panel settle this", "give this decision a multi-perspective check" +- Slash command: `/expert-panel ` +- Direct call: `CreateWorkflow(path=, args={...})` + +### Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| question | string | required | The question/decision the panel should settle | +| profile | string | standard | quick (3 experts/1 round, minutes) \| standard (2 rounds) \| deep (auto-adds contrarian + synthesizer lenses, 2 rounds) | +| experts | json | [务实工程师, 风险与边界审查, 产品价值] | Expert lens names (no duplicates) | +| threshold | number | 0.6 | Consensus-rate threshold; below it one extra debate round runs | +| max_rounds | number | quick=1, else 2 | Max rounds including the first | +| evidence | json | empty | Evidence pack: texts or file paths (experts/verifier read files with their own tools) | +| basis | json | empty | Review rubric: file paths or entries; when given, verdicts must cite the rubric item by item | +| ledger_path | string | ~/.expert-panel/ledger.jsonl | Run ledger JSONL path | +| simulate_failures | json | empty | Test-only: indexes of experts to simulate as failed | + +### Model selection + +By default every role uses the session default model — this is deliberate: **zero config, runs on any ZCode environment**. To switch models uniformly, pass `subagent_model` on the `CreateWorkflow` call (look up ids with `ListModels` on the current provider). Per-role model routing is intentionally unsupported by the host: a subagent's model must be known before the run starts. + +### Interpreting results + +- `agreementRate ≥ threshold`: consensus reached. `agreed` holds verified stable conclusions; `unverifiable` holds value judgements (cannot be objectively verified — they are the panel's opinion only). +- `< threshold`: `disputed` lists each side's arguments for human adjudication — **this is a feature, not a failure**. +- `absent` non-empty or fewer than 2 valid experts: the conclusion is annotated with sample completeness; mind the caveat when quoting. + +Each run appends one ledger line (`ledger_path`): question digest / profile / expert counts / rounds / consensus rate / agreed·disputed·rejected counts / stable flag. + +### 30-second verification + +In a new session, send: + +``` +Use the expert panel, quick profile, to evaluate: Markdown files vs a notes app for a personal knowledge base? +``` + +Expected (within minutes): an "Expert Panel Conclusion" card with a consensus rate (e.g. 74%), agreed items (with verification marks), disputed items (if any), an executive summary and a final recommendation; while running, the expert board advances from "independent answering" to "converged". A rate below 60% triggers an extra debate round and explicit disagreements — that is also normal behavior. + +## Data behavior & uninstall + +- Each run appends one JSONL line to `~/.expert-panel/ledger.jsonl` (relocate via `ledger_path`); beyond that it **writes nothing** — no hooks, no MCP server, no outbound network requests (review subagents only use your current session's own model channel). +- Uninstall: Settings → Plugins → expert-panel → Uninstall. The only residue is `~/.expert-panel/` (the ledger dir); delete it to remove everything. + +## Design notes + +- **Anonymity protocol**: critique inputs carry only anonymous labels/positions/key points; lens names exist only inside each expert's own system prompt during independent answering. +- **Deterministic consensus rate**: the script computes `agreed/(agreed+disputed)`; round gating keys off that value alone — a model cannot shade the arithmetic. +- **Deep-profile guardrail**: a contrarian lens is mandatory (to counter "debate amplifying biased consensus"); a synthesizer is added when headcount is short. +- **Origins**: the mechanism draws on the anonymous cross-critique and chair ideas of karpathy/llm-council, plus the author's own expert-panel workflow iterated in production on ZCode (v2.1: rubric injection, single-expert fault tolerance, sample-completeness annotation). No code was copied from llm-council (which currently ships no license); this plugin is an independent implementation, MIT-licensed. + +## Changelog + +Version is whatever the marketplace shows (the source of truth is plugin.json). + +- 0.1.2: ledger schema v2 — dual consensus rates (rate_pre at arbitration time / rate_post recomputed after verification, fixing the old unrecomputable-rate bug), per-round convergence series, and threshold/question_id fields logged in full. +- 0.1.1: fix ledger write for bare-filename custom paths; pre-release cleanup (neutral git identity). +- 0.1.0: first releasable version, full six-stage mechanism. + +## License + +MIT — see [LICENSE](LICENSE) diff --git a/external_plugins/expert-panel/README_CN.md b/external_plugins/expert-panel/README_CN.md new file mode 100644 index 0000000..80035d8 --- /dev/null +++ b/external_plugins/expert-panel/README_CN.md @@ -0,0 +1,106 @@ +# expert-panel —— 专家团共识评审(ZCode 插件) + +把一个重要决策交给多位不同视角的专家:**独立作答 → 真匿名互评 → 仲裁归纳 → 共识率门控加辩 → 主席综合 → 事实复核**,产出「稳定一致项 + 明示分歧」的结构化结论。零配置,装完即用,所有角色跑在你当前的会话模型上。 + +## 为什么需要它 + +单个模型一次回答的问题:从众(先入为主)、权威偏置(谁名气大听谁的)、硬合成(把分歧和稀泥成"总体可行")。本工作流逐条对治: + +| 问题 | 对策 | +|---|---| +| 从众 | 每位专家**先独立作答**,之后才看到别人的立场 | +| 权威偏置 | 互评输入只有「专家A/B/C」匿名标签 + 立场 + 要点,**不含视角名与模型** | +| 虚假共识 | 共识率 = 一致项/(一致项+分歧项),**由脚本确定性计算,不信模型自报** | +| 和稀泥 | 共识率低于阈值自动加辩一轮;仍不足则**明示分歧交人工裁决,绝不硬合成** | +| 编造事实 | 可客观验证的一致项由事实复核员核验(能读文件/跑命令就去验),不实剔除 | + +单专家失败自动容错,结论标注样本完整度(缺席视角/有效专家数)。 + +## 安装 + +**本地目录市场**(先把本仓库放到本机任意目录): + +1. ZCode → 插件市场 → 添加 → 添加插件市场,粘贴本仓库所在**目录**路径 +2. 个人 → 该市场 → expert-panel → 安装 +3. 新会话生效 + +**CLI**(已添加市场后): + +```bash +zcode plugins install expert-panel@<市场名> +``` + +要求:ZCode 桌面版(自带 dynamic workflows 运行时)。台账落盘用到 `python3`(缺失不阻断结论,仅不入账)。 + +## 使用 + +装好后三种触发方式: + +- 自然语言:「用专家团评估一下 X」「让专家团给个结论」「这个问题帮我多视角把关」 +- 斜杠命令:`/expert-panel <你的问题>` +- 直接调用:`CreateWorkflow(path=<插件内 workflows/expert-panel.dwf.ts>, args={...})` + +### 参数 + +| 参数 | 类型 | 默认 | 说明 | +|---|---|---|---| +| question | string | 必填 | 要专家团给出稳定结论的问题/决策 | +| profile | string | standard | quick(3 专家/1 轮,数分钟)\| standard(2 轮)\| deep(自动补反方质询+综合连接原型,2 轮) | +| experts | json | [务实工程师, 风险与边界审查, 产品价值] | 专家视角名数组(勿重复) | +| threshold | number | 0.6 | 共识率阈值,低于自动加辩一轮 | +| max_rounds | number | quick=1,其余=2 | 最大轮数(含首轮) | +| evidence | json | 空 | 证据包:文本或文件路径数组(专家/复核员用工具自行读取) | +| basis | json | 空 | 评测依据:文件路径或条目数组,要求逐条对照判定并引用条目 | +| ledger_path | string | ~/.expert-panel/ledger.jsonl | 运行台账 JSONL 落盘路径 | +| simulate_failures | json | 空 | 测试用:模拟作答失败的专家下标 | + +### 模型选择 + +默认所有角色用会话默认模型——这是刻意设计:**零配置、在任何 ZCode 环境都能跑**。要统一换模型,在 `CreateWorkflow` 调用上传 `subagent_model`(用 `ListModels` 查当前 provider 可用的 id)。按角色分模型(如互评用轻量模型)宿主刻意不支持:子代理模型必须在运行前已知。 + +### 结果解读 + +- `agreementRate ≥ threshold`:达成共识。`agreed` 为经复核的稳定结论;`unverifiable` 是价值判断类(无法客观复核,仅代表专家团意见)。 +- `< threshold`:`disputed` 已列明各方论据,交人工裁决——**这是特性不是失败**。 +- `absent` 非空或有效专家 < 2:结论已标注样本完整度,引用时注意口径。 + +### 30 秒验证(装完即测) + +新会话里发一句: + +``` +用专家团 quick 档评估:个人知识库用 Markdown 文件还是笔记软件? +``` + +预期输出(数分钟内):一张「专家团结论」卡片,含共识率百分比(如 74%)、一致项清单(带复核标记)、分歧项清单(若有)、执行摘要与最终建议;运行中可见专家状态看板从「独立作答」推进到「已收敛」。共识率低于 60% 时会自动加辩一轮并明示分歧——同样算工作正常。 + +每次运行一行追加台账(`ledger_path`):问题摘要 / 档位 / 专家数 / 有效数 / 轮数 / 共识率 / 一致·分歧·剔除计数 / 是否达成。 + +## 输出形态 + +运行中:专家团状态看板(独立作答→交叉互评→修订表态→已收敛)+ 共识率收敛曲线 + 指标条。 +结束后:主产物为一份结论卡片(一致项/分歧项/复核剔除/执行摘要/最终建议/仲裁附录),并返回结构化 JSON(供编排方程序化消费)。 + +## 数据行为与卸载 + +- 每次运行追加一行 JSONL 到 `~/.expert-panel/ledger.jsonl`(可用 `ledger_path` 改路径);除此之外**不写任何文件**,无 hooks、无 MCP server、无出站网络请求(评审子代理只用你当前会话本身的模型通道)。 +- 卸载:Settings → Plugins → expert-panel → 卸载。残留物仅 `~/.expert-panel/`(台账目录),删除即彻底清除。 + +## 设计说明 + +- **匿名协议**:互评阶段输入仅含匿名标签/立场/要点;视角名只在独立作答阶段存在于各自的 system prompt 中。 +- **共识率确定性**:脚本按 `一致/(一致+分歧)` 计算,轮间比较与加辩门控全部基于该值,模型无法影响分母分子之外的东西。 +- **deep 档护栏**:强制包含反共识原型(Contrarian),对冲「辩论放大偏差共识」;人数不足再补综合连接(Synthesizer)。 +- **渊源**:机制蓝本来自 karpathy/llm-council 的匿名互评与主席制思想 + 本作者在 ZCode 上长期使用的 expert-panel 工作流实证迭代(v2.1:评测依据注入、单专家容错、样本完整度标注)。 + +## Changelog + +版本以插件市场显示为准(事实源在 plugin.json)。 + +- 0.1.2:台账 schema v2——双口径共识率(rate_pre 仲裁时点 / rate_post 复核后重算,修复旧版 rate 不可复算问题)、逐轮收敛序列、threshold/question_id 等字段全量入账。 +- 0.1.1:修复自定义 ledger_path 为裸文件名时无法落账的边界问题;开源前清理(git 历史中性身份)。 +- 0.1.0:首个可发布版本,六阶段机制全量移植。 + +## License + +MIT(见 [LICENSE](LICENSE)) diff --git a/external_plugins/expert-panel/commands/expert-panel.md b/external_plugins/expert-panel/commands/expert-panel.md new file mode 100644 index 0000000..0d3a620 --- /dev/null +++ b/external_plugins/expert-panel/commands/expert-panel.md @@ -0,0 +1,8 @@ +--- +description: 把一个问题交给专家团共识评审(独立作答→匿名互评→仲裁→加辩→主席→复核) +--- + +按 expert-panel skill(本插件 `skills/expert-panel/SKILL.md`)的流程调用专家团工作流: + +- 用户消息中除指令外的内容就是要评审的问题(question);用户点名了档位就用(quick/standard/deep),没点名按 skill 默认 standard,急活可用 quick。 +- 调用方式、完整参数表、结果解读一律以 skill 为准。 diff --git a/external_plugins/expert-panel/skills/expert-panel/SKILL.md b/external_plugins/expert-panel/skills/expert-panel/SKILL.md new file mode 100644 index 0000000..99ce4f9 --- /dev/null +++ b/external_plugins/expert-panel/skills/expert-panel/SKILL.md @@ -0,0 +1,63 @@ +--- +name: expert-panel +description: 专家团共识评审:把重要决策/方案交给多位不同视角专家——独立作答→真匿名互评→仲裁归纳(共识率脚本确定性计算、不足自动加辩)→主席综合→事实复核,产出「稳定一致项 + 明示分歧」的结构化结论。当用户要"更稳"的结论、说 用专家团评估X / 让专家团给结论 / 多视角评审 / 共识评审 / 重要决策把关 时使用;急活用 quick 档(数分钟)。 +--- + +# expert-panel —— 专家团共识评审 + +## 机制(为什么可信) + +1. **独立作答**:N 位不同视角专家先各自独立表态,互不知晓(无从众)。 +2. **真匿名互评**:互评输入只有「专家A/B/C」标签 + 立场 + 要点,不含视角名(防权威偏置);各自提出反对/补充并按说服力排序。 +3. **仲裁归纳**:归纳为一致项/分歧项。**共识率 = 一致/(一致+分歧),由脚本确定性计算,不信模型自报**。 +4. **共识率门控加辩**:低于阈值(默认 0.6)自动加辩一轮,仍不足则**明示分歧交人工裁决,绝不硬合成**。 +5. **主席综合**:执行摘要 + 最终建议。 +6. **事实复核**:可客观验证的一致项由复核员核验(能读文件/跑命令就去验),不实的剔除;价值判断类标 unverifiable 原样保留。 + +单专家失败自动容错,结论标注样本完整度(缺席视角/有效专家数)。 + +## 怎么调用 + +工作流文件随本插件分发:**本 skill 目录的 `../../workflows/expert-panel.dwf.ts`**(相对本 SKILL.md)。 + +用 `CreateWorkflow` 的 **path 方式**调用(不要复制进用户的 workflows 目录,升级由插件统一管): + +``` +CreateWorkflow( + path = <按上述相对位置解析出的绝对路径>, + name = "专家团", + args = { question, profile, ... } +) +``` + +### 参数 + +| 参数 | 类型 | 默认 | 说明 | +|---|---|---|---| +| question | string | 必填 | 要专家团给出稳定结论的问题/决策 | +| profile | string | standard | quick(3 专家/1 轮,数分钟)\| standard(2 轮)\| deep(自动补反方质询+综合连接原型,2 轮) | +| experts | json | [务实工程师, 风险与边界审查, 产品价值] | 专家视角名数组(勿重复) | +| threshold | number | 0.6 | 共识率阈值,低于自动加辩一轮 | +| max_rounds | number | quick=1,其余=2 | 最大轮数(含首轮) | +| evidence | json | 空 | 证据包:文本或文件路径数组(专家/复核员用工具自行读取) | +| basis | json | 空 | 评测依据:文件路径或条目数组,给了就要求逐条对照判定并引用条目 | +| ledger_path | string | ~/.expert-panel/ledger.jsonl | 运行台账 JSONL 落盘路径 | +| simulate_failures | json | 空 | 测试用:要模拟作答失败的专家下标数组 | + +### 模型选择(可选) + +所有角色默认用**会话默认模型,零配置可跑**。要统一换模型:在 `CreateWorkflow` 调用上传 `subagent_model`(id 用 `ListModels` 查当前 provider 的行)。 +注意:按角色分模型(如互评用轻量模型)是宿主刻意不支持的——子代理模型必须在运行前已知;请勿尝试在脚本里做运行时路由。 + +### 结果解读 + +- `agreementRate ≥ threshold`:达成共识,`agreed` 为经复核的稳定结论;`unverifiable` 是价值判断类。 +- `< threshold`:`disputed` 已列明各方论据,交人工裁决——这是特性不是失败。 +- `absent` 非空或有效专家 < 2:结论已标注样本完整度,引用时注意口径。 +- 每轮运行一行追加台账(`ledger_path`):问题摘要/档位/专家数/轮数/共识率/一致分歧计数。 + +## 边界 + +- 价值判断类结论无法客观复核(标 unverifiable),仅代表专家团意见。 +- 台账写入失败不阻断结论(结果 `verified` 里会注明未入账)。 +- 深档会显著增加 token 消耗,重要决策再用。 diff --git a/external_plugins/expert-panel/workflows/expert-panel.dwf.ts b/external_plugins/expert-panel/workflows/expert-panel.dwf.ts new file mode 100644 index 0000000..f856ec9 --- /dev/null +++ b/external_plugins/expert-panel/workflows/expert-panel.dwf.ts @@ -0,0 +1,427 @@ +/* zcode-workflow +description: 专家团共识工作流:N 位不同视角的专家独立作答 → 真匿名交叉互评(只见 专家A/B/C 标签, + 不见视角名)→ 仲裁人归纳一致项/分歧项(共识率由脚本确定性计算,低于阈值自动加辩)→ 主席执行摘要 → + 可验证事实复核。单专家失败自动容错并标注样本完整度;profile(quick/standard/deep)控制轮数与专家构成。 + 可移植版:所有角色用会话默认模型,零配置可跑;要统一换模型在调用层用 CreateWorkflow 的 subagent_model。 + 输出按「一致(稳定)/分歧(明示)」分离,防硬合成与偏见共识。 +whenToUse: 用户要"更稳"的结论/重要决策/方案评审/多视角评估时;说"用专家团评估 X / 让专家团给结论"。 + 急活用 profile=quick(数分钟),重要产出用 standard,最高把关用 deep。共识率不足自动加辩, + 仍不足则明示分歧交人工裁决。 +args: + basis: + type: json + description: 评测依据,文件路径或条目数组;给了就要求专家与复核员逐条对照依据判定并引用所在条目(空数组=明确不用,默认不注入) + evidence: + type: json + description: 证据包,文本或文件路径数组(路径由专家/复核员用工具自行读取),默认空 + experts: + type: json + description: 专家视角名数组(勿重复),默认 [务实工程师, 风险与边界审查, 产品价值] + ledger_path: + type: string + description: 运行台账 JSONL 落盘路径(每轮一行),默认 ~/.expert-panel/ledger.jsonl + max_rounds: + type: number + description: 最大轮数(含首轮),不填由 profile 决定(quick=1,其余=2) + profile: + type: string + description: quick(3专家/1轮)| standard(默认,2轮)| deep(自动补足反方质询与综合连接两类原型/2轮) + default: standard + question: + type: string + description: 要专家团给出稳定结论的问题/决策 + required: true + question_id: + type: string + description: 题目标识(基准跑批/回溯归因用),默认空 + simulate_failures: + type: json + description: 测试用,要模拟作答失败的专家下标数组,默认空 + threshold: + type: number + description: 共识率阈值,低于则再辩一轮,仍低则明示分歧交人工裁决 + default: 0.6 +*/ +interface ExpertAnswer { + /** 对问题的立场或方案(一句话)。 */ + position: string; + /** 支撑要点,每条尽量独立可核。 */ + keyPoints: string[]; + /** 自评置信度 0-1。 */ + confidence: number; +} + +interface Objection { + /** 针对哪位匿名专家,填其标签(如 专家B)。 */ + target: string; + /** 反对或补充意见。 */ + comment: string; +} + +interface Critique { + /** 对其他专家立场的反对与补充。 */ + objections: Objection[]; + /** 其他专家按说服力从高到低的标签排序(如 ["专家C","专家A"])。 */ + ranking: string[]; +} + +interface Revision { + /** 修订后的立场(可维持原判)。 */ + position: string; + /** 修订后要点。 */ + keyPoints: string[]; + /** 是否改变了立场。 */ + changed: boolean; +} + +interface Dispute { + /** 分歧主题。 */ + topic: string; + /** 各方论据摘要(用专家标签指代)。 */ + sides: string; +} + +interface Consensus { + /** 各专家均认可的结论——稳定输出。 */ + agreed: string[]; + /** 仍存分歧的结论。 */ + disputed: Dispute[]; + /** 仲裁人的最终建议。 */ + recommendation: string; +} + +interface Verification { + /** 复核后保留的一致项。 */ + agreed: string[]; + /** 复核未通过的项及原因。 */ + rejected: { item: string; reason: string }[]; + /** 价值判断类或无法客观复核的项,原样保留。 */ + unverifiable: string[]; +} + +interface ChairSummary { + /** 3-5 句执行摘要:共识是什么、分歧在哪、怎么收敛的。 */ + executiveSummary: string; + /** 面向用户的明确最终建议,含适用条件。 */ + finalRecommendation: string; +} + +/** 共识率由脚本确定性计算,不信仲裁人自报的数字。 */ +function computeRate(c: Consensus): number { + const a = c.agreed.length; + const d = c.disputed.length; + return a + d === 0 ? 0 : a / (a + d); +} + +/** 专家下标 → 匿名标签(专家A/B/C…)。 */ +function labelOf(i: number): string { + return `专家${String.fromCharCode(65 + i)}`; +} + +// ---------- 参数解析 ---------- +const question = String(args.question); +const profileRaw = String(args.profile ?? "standard"); +const profile: "quick" | "standard" | "deep" = profileRaw === "quick" ? "quick" : profileRaw === "deep" ? "deep" : "standard"; +const defaultLenses = ["务实工程师", "风险与边界审查", "产品价值"]; +let lenses: string[] = Array.isArray(args.experts) && (args.experts as unknown[]).length > 0 + ? (args.experts as unknown[]).map(String) + : defaultLenses; +if (profile === "quick") lenses = lenses.slice(0, 3); +if (profile === "deep") { + // 护栏:deep 档必须含反共识原型(对冲「辩论放大偏差共识」),人数不足再补综合型 + if (!lenses.some((l) => /反方|反对|质询|质疑|Contrarian|魔鬼/i.test(l))) lenses = [...lenses, "反方质询(Contrarian)"]; + if (lenses.length < 4) lenses = [...lenses, "综合连接(Synthesizer)"]; +} +const threshold = typeof args.threshold === "number" && args.threshold > 0 && args.threshold <= 1 ? args.threshold : 0.6; +const maxRounds = typeof args.max_rounds === "number" && args.max_rounds >= 1 + ? Math.floor(args.max_rounds) + : profile === "quick" ? 1 : 2; +const simulateFailures: number[] = Array.isArray(args.simulate_failures) + ? (args.simulate_failures as unknown[]).map(Number).filter((n) => Number.isInteger(n)) + : []; +const evidence: string[] = Array.isArray(args.evidence) + ? (args.evidence as unknown[]).map(String).filter((s) => s.trim().length > 0) + : []; +const evidenceBlock = evidence.length > 0 + ? `\n证据包(${evidence.length} 项;文件路径请用你的工具读取后再引用,结论须标注证据出处):\n${evidence.map((e, i) => `[${i + 1}] ${e}`).join("\n")}` + : ""; +const basisGiven = Array.isArray(args.basis); +const basis: string[] = basisGiven + ? (args.basis as unknown[]).map(String).filter((s) => s.trim().length > 0) + : []; +const basisBlock = basis.length > 0 + ? `\n评测依据(调用方指定;评审前用工具读取与问题相关的原文,结论须对照依据逐条判定并引用所在条目,不凭印象打分;与问题无关的条目可略过并说明):\n${basis.map((b, i) => `[${i + 1}] ${b}`).join("\n")}` + : ""; +const ledgerPath = typeof args.ledger_path === "string" && args.ledger_path.trim().length > 0 + ? args.ledger_path.trim() + : "~/.expert-panel/ledger.jsonl"; + +// ---------- 仪表盘 ---------- +artifact.board("panel", { + title: "专家团状态", + key: "name", + status: "stage", + columns: ["独立作答", "交叉互评", "修订表态", "已收敛", "缺席"], + cardTitle: "name", + detail: [{ field: "position", label: "立场" }], +}); +artifact.chart("consensus", { + title: "共识率收敛曲线", + x: { field: "round", label: "轮次" }, + y: { field: "rate", label: "共识率" }, + baseline: { field: "target", label: "阈值" }, +}); +artifact.metrics("stats", { + title: "专家团指标", + metrics: [ + { field: "ratePct", label: "共识率" }, + { field: "round", label: "轮次" }, + { field: "alive", label: "有效专家" }, + { field: "profileLabel", label: "档位" }, + ], +}); + +const experts = lenses.map((lens) => + agent(`专家·${lens}`, { + system: + `你是「${lens}」视角的资深专家。先独立给出立场、3-6 条支撑要点和置信度;` + + "只凭自己的专业判断,不要猜测别人会怎么说,也不要为了和谐改变有依据的立场;确实被说服才改。用中文回答。", + }), +); + +phase("独立作答:每位专家独立给出立场与论据"); +// 仲裁需要全部有效专家的独立立场,此处必须等齐;单个专家失败不拖垮整体(allSettled) +const settled = await Promise.allSettled( + lenses.map((lens, i) => + simulateFailures.includes(i) + ? Promise.reject(new Error("模拟作答失败(测试注入)")) + : experts[i].ask(`问题:${question}\n视角:${lens}${evidenceBlock}${basisBlock}`), + ), +); +const active: number[] = []; +const answers: ExpertAnswer[] = []; +settled.forEach((s, i) => { + if (s.status === "fulfilled") { + active.push(i); + answers.push(s.value); + report({ name: lenses[i], stage: "独立作答", position: s.value.position }, "panel"); + } else { + report({ name: lenses[i], stage: "缺席", position: `作答失败:${String(s.reason).slice(0, 80)}` }, "panel"); + } +}); +if (active.length === 0) throw new Error("所有专家均未完成作答,无法仲裁"); +const absent = lenses.filter((_, i) => !active.includes(i)); +log(`${active.length}/${lenses.length} 位专家完成独立作答${absent.length > 0 ? `(缺席:${absent.join("、")})` : ""}`); + +/** 第 m 位有效专家能看到的他人立场:只有匿名标签、立场、要点——不含视角名与模型。 */ +function othersFor(m: number) { + return active + .map((idx, n) => ({ idx, n })) + .filter((o) => o.n !== m) + .map((o) => ({ 专家: labelOf(o.idx), position: answers[o.n].position, keyPoints: answers[o.n].keyPoints })); +} + +phase("交叉互评:匿名互看立场,提出反对与补充"); +const critiqueSettled = await Promise.allSettled( + active.map((idx, m) => + agent(`互评·${lenses[idx]}`, { + system: + `你是「${lenses[idx]}」视角的独立评审。只针对其他专家的匿名立场提出反对与补充,` + + "不重复自己的论点,就事论事。用中文回答。", + }).ask( + `问题:${question}\n你此前的立场:${answers[m].position}\n` + + `其他专家的匿名立场:${JSON.stringify(othersFor(m))}\n` + + "请只针对他们的立场提出反对与补充(target 填对方标签),并给出 ranking(对方标签数组,说服力从高到低)。", + ), + ), +); +const critiques: Critique[] = critiqueSettled.map((s) => (s.status === "fulfilled" ? s.value : { objections: [], ranking: [] })); +active.forEach((idx, m) => report({ name: lenses[idx], stage: "交叉互评", position: answers[m].position }, "panel")); + +phase("仲裁归纳:区分一致项与分歧项"); +const arbitrator = agent("仲裁人", { + system: + "你是仲裁人:把各位专家的表态归纳为『一致项』(所有人都认可,视为稳定结论)与『分歧项』(列明各方论据,不强行折中)," + + "并给出最终建议。宁可如实呈现分歧,不要制造虚假共识。用中文回答。", +}); +const positionsOf = () => + active.map((idx, m) => ({ + 专家: labelOf(idx), + position: answers[m].position, + keyPoints: answers[m].keyPoints, + confidence: answers[m].confidence, + })); +let consensus = await arbitrator.ask( + `问题:${question}\n专家表态(匿名):${JSON.stringify(positionsOf())}\n相互意见:${JSON.stringify(critiques)}\n请归纳共识与分歧。`, +); +let rate = computeRate(consensus); +// 逐轮共识率序列(schema v2:台账记录收敛轨迹) +const roundsSeq: number[] = [Math.round(rate * 1000) / 1000]; +let round = 1; +report({ round, rate, target: threshold }, "consensus"); +report({ ratePct: Math.round(rate * 100) + "%", round, alive: `${active.length}/${lenses.length}`, profileLabel: `${profile}` }, "stats"); +log(`第 1 轮共识率 ${Math.round(rate * 100)}%(一致 ${consensus.agreed.length} / 分歧 ${consensus.disputed.length})`); + +phase("修订表态与再仲裁:共识率不足自动加辩"); +while (rate < threshold && round < maxRounds) { + round += 1; + const revised = await Promise.allSettled( + active.map((idx, m) => + experts[idx].ask( + `仲裁归纳出的分歧项:${JSON.stringify(consensus.disputed)}\n你此前的立场:${answers[m].position}\n` + + "请针对分歧重新表态(可坚持,被说服则修改并说明)。", + ), + ), + ); + const revisedPositions = active.map((idx, m) => { + const r = revised[m]; + if (r.status === "fulfilled") { + answers[m] = { position: r.value.position, keyPoints: r.value.keyPoints, confidence: answers[m].confidence }; + report({ name: lenses[idx], stage: "修订表态", position: r.value.position }, "panel"); + return { 专家: labelOf(idx), position: r.value.position, keyPoints: r.value.keyPoints, changed: r.value.changed }; + } + return { 专家: labelOf(idx), position: answers[m].position, keyPoints: answers[m].keyPoints, changed: false }; + }); + consensus = await arbitrator.ask( + `第 ${round} 轮专家表态(匿名):${JSON.stringify(revisedPositions)}\n上一轮归纳:${JSON.stringify(consensus)}\n请重新归纳一致项与分歧项。`, + ); + rate = computeRate(consensus); + roundsSeq.push(Math.round(rate * 1000) / 1000); + report({ round, rate, target: threshold }, "consensus"); + report({ ratePct: Math.round(rate * 100) + "%", round, alive: `${active.length}/${lenses.length}`, profileLabel: `${profile}` }, "stats"); + log(`第 ${round} 轮收敛后共识率 ${Math.round(rate * 100)}%`); +} +active.forEach((idx, m) => report({ name: lenses[idx], stage: "已收敛", position: answers[m].position }, "panel")); + +phase("主席综合:产出执行摘要与最终建议"); +const chair = agent("主席", { + system: + "你是专家团主席。综合专家立场、互评排序与仲裁归纳,为用户写终稿:执行摘要 + 明确的最终建议;" + + "分歧未解决时如实呈现分歧而非硬折中;有效专家不足 2 位时须明示结论未经多方独立验证。用中文回答。", +}); +const rankings = critiques.map((c, m) => ({ 评审: labelOf(active[m]), ranking: c.ranking })); +const chairOut = await chair.ask( + `问题:${question}\n有效专家 ${active.length}/${lenses.length}\n` + + `专家最终立场(匿名):${JSON.stringify(positionsOf())}\n互评排序:${JSON.stringify(rankings)}\n` + + `最终仲裁归纳:${JSON.stringify(consensus)}\n请给出执行摘要与最终建议。`, +); + +phase("事实复核与落账:复核可验证一致项,记录本次运行"); +const verification = await agent("事实复核员", { + system: + "你是事实复核员:只复核可客观验证的事实型断言(读代码/查文件/跑命令能判定的就去做;" + + "给了证据包则逐条对证并标注出处);价值判断类结论归入 unverifiable 原样保留。不改任何文件。用中文回答。", +}).ask(`问题语境:${question}\n待复核一致项:${JSON.stringify(consensus.agreed)}${evidenceBlock}${basisBlock}`); + +const stable = rate >= threshold; +// schema v2(2026-10-23 P0 修复):双口径共识率——rate_pre=仲裁时点(复核前)、rate_post=复核后重算, +// 消除旧版三时点混用(rate 不可复算)问题。tokens 由回填脚本补齐;adopted 为采纳标记预留。 +const ratePre = Math.round(rate * 1000) / 1000; +const ratePostDenom = verification.agreed.length + consensus.disputed.length; +const ratePost = ratePostDenom === 0 ? 0 : Math.round((verification.agreed.length / ratePostDenom) * 1000) / 1000; +const ledgerEntry = { + schema: 2, + version: "0.1.2", + question: question.slice(0, 120), + question_id: String(args.question_id ?? ""), + profile, + model: "session-default", + experts: lenses.length, + alive: active.length, + rounds: round, + rounds_seq: roundsSeq, + threshold, + rate_pre: ratePre, + rate_post: ratePost, + rate: ratePre, + agreed: verification.agreed.length, + disputed: consensus.disputed.length, + rejected: verification.rejected.length, + unverifiable: verification.unverifiable.length, + stable, + tokens: null, + adopted: null, +}; +const LEDGER_CODE = [ + "import sys, json, os, datetime", + "p = os.path.expanduser(sys.argv[2])", + "d = os.path.dirname(p)", + "if d: os.makedirs(d, exist_ok=True)", + "e = json.loads(sys.argv[1]); e['ts'] = datetime.datetime.now().isoformat(timespec='seconds')", + "open(p, 'a', encoding='utf-8').write(json.dumps(e, ensure_ascii=False) + '\\n')", + "print(p)", +].join("\n"); +const ledger = await world.run("python3", ["-c", LEDGER_CODE, JSON.stringify(ledgerEntry), ledgerPath]); +log(ledger.exitCode === 0 ? `台账已追加:${ledger.stdout.trim()}` : `台账写入失败(不阻断结论):${ledger.stderr.slice(0, 120)}`); +const sampleNote = absent.length > 0 + ? `样本完整度 ${active.length}/${lenses.length}(缺席:${absent.join("、")}),结论未经全体专家独立验证` + : active.length < 2 + ? "仅 1 位有效专家,共识率反映的是无异议而非多方独立验证" + : ""; + +await artifact.markdown( + "summary", + [ + `# 专家团结论·${question.slice(0, 40)}`, + "", + `- 共识率:${Math.round(rate * 100)}%(阈值 ${Math.round(threshold * 100)}%,${round} 轮收敛,${stable ? "达成共识" : "未达阈值"})`, + `- 档位 ${profile} · 有效专家 ${active.length}/${lenses.length}:${active.map((i) => lenses[i]).join("、")}`, + `- 一致项(稳定结论):${consensus.agreed.length} 条;分歧项:${consensus.disputed.length} 条;复核剔除:${verification.rejected.length} 条`, + ...(sampleNote ? [`- ⚠️ ${sampleNote}`] : []), + "", + "## 一致项(经复核)", + ...verification.agreed.map((s) => `- ✅ ${s}`), + ...verification.unverifiable.map((s) => `- ❓ ${s}(价值判断/无法客观复核)`), + "", + "## 仍存分歧(明示,不强行折中)", + ...consensus.disputed.map((d) => `- ⚠️ ${d.topic}:${d.sides}`), + ...(verification.rejected.length > 0 + ? ["", "## 复核未通过", ...verification.rejected.map((r) => `- ❌ ${r.item}(${r.reason})`)] + : []), + "", + "## 执行摘要(主席)", + chairOut.executiveSummary, + "", + "## 最终建议(主席)", + chairOut.finalRecommendation, + "", + "## 仲裁建议(附录)", + consensus.recommendation, + ].join("\n"), + { + title: `专家团结论·${question.slice(0, 24)}`, + primary: true, + description: `共识率 ${Math.round(rate * 100)}%,一致 ${consensus.agreed.length} 条 / 分歧 ${consensus.disputed.length} 条,有效专家 ${active.length}/${lenses.length}。`, + }, +); + +return { + conclusion: stable + ? `专家团达成共识(共识率 ${Math.round(rate * 100)}%,${round} 轮):${verification.agreed.length} 条稳定结论 + ${consensus.disputed.length} 条明示分歧;仲裁建议已给出。${sampleNote ? "⚠️ " + sampleNote : ""}` + : `共识率 ${Math.round(rate * 100)}% 低于阈值,未强行合成:${consensus.disputed.length} 条分歧已列明各方论据,建议人工裁决。${sampleNote ? "⚠️ " + sampleNote : ""}`, + agreed: verification.agreed, + unverifiable: verification.unverifiable, + disputed: consensus.disputed, + rejected: verification.rejected, + executiveSummary: chairOut.executiveSummary, + recommendation: chairOut.finalRecommendation || consensus.recommendation, + arbiterNote: consensus.recommendation, + agreementRate: rate, + rounds: round, + profile, + participants: active.map((i) => lenses[i]), + absent, + verified: [ + "每位专家独立作答后才进入互评(无从众)", + "互评输入只含匿名标签/立场/要点,不含视角名(防权威偏置)", + "共识率由脚本按一致项/(一致项+分歧项) 确定性计算,非模型自报", + "可验证事实经事实复核员把关", + ...(basis.length > 0 ? ["评测依据已按 basis 参数注入,结论要求逐条对照依据"] : []), + ledger.exitCode === 0 ? `运行台账已追加 ${ledger.stdout.trim()}` : "台账写入失败,本次运行未入账", + ], + notCovered: [ + stable ? "" : "共识不足,结论未定", + "价值判断类结论无法客观复核,仅代表专家团意见", + absent.length > 0 ? `缺席专家视角未覆盖:${absent.join("、")}` : "", + ].filter(Boolean), +}; diff --git a/marketplace.json b/marketplace.json index 350c942..740636d 100644 --- a/marketplace.json +++ b/marketplace.json @@ -1386,6 +1386,25 @@ "unity-cli", "skills" ] + }, + { + "name": "expert-panel", + "displayName": "专家团共识评审 Expert Panel", + "displayName_i18n": { + "en": "Expert Panel Consensus Review", + "zh-CN": "专家团共识评审" + }, + "version": "0.1.2", + "source": "./external_plugins/expert-panel", + "description": "把重要决策交给多位不同视角专家:独立作答→真匿名互评→仲裁归纳(共识率由脚本确定性计算,不足自动加辩)→主席综合→事实复核,产出稳定一致项+明示分歧,绝不硬合成。零配置,默认用会话模型。", + "description_i18n": { + "en": "Expert-panel consensus review: independent answers from multiple expert lenses → truly anonymous cross-critique → arbitration (consensus rate computed by script, extra debate round below threshold) → chair synthesis → fact verification; quick/standard/deep profiles, zero config, conclusions separate agreed items from explicit disagreements.", + "zh-CN": "专家团共识评审:多位不同视角专家独立作答→真匿名互评→仲裁归纳(共识率脚本计算、不足自动加辩)→主席综合→事实复核;quick/standard/deep 三档,零配置,结论按一致/分歧分离绝不硬合成。" + }, + "category": "productivity", + "author": { + "name": "Zhang24y" + } } ] }