Skip to content

ADR-012 · 多场景评委体系 · Scene 作为 Panel 的上层部署单元

字段
状态accepted · 2026-06-25
日期2026-06-25
决策者CTO + 项目主理
承接ADR-001 (multi-anchor) · ADR-007 (REVIEW 模式)
相关scripts/scene_loader.py · scripts/panel_loader.py · scripts/op2_output.py · scripts/smoke_e2e.py · scenes/ · panels/default.yaml

1. Context

1.1 · 起源

boss 判断流水线最初是单场景架构: 一套 panels/default.yaml, 一个飞书机器人绑定, 一套评分维度。随着使用场景扩展, 出现了无法用单 panel 覆盖的结构性差异:

差异维度常规评审竞赛 / Workshop
是否有锚点基准有 (anchor_judge: tian)无 (anchor_judge: null)
评分维度固定 5 镜头 (推理质量)自定义 (业务价值 / 创新度等)
成绩是否公开内部公开展示
机器人入口独立独立
总分制通常 100任意 (如 10)

同时, 同一评审模式下也有多个并列场景: 公司级 / 各事业群 / 各专项评审 — 它们评委组合不同, 报告品牌不同, 但引擎完全一样。

核心矛盾: panel 是"评审规则", 但缺少"部署单元"的概念 — 没有地方描述"哪个飞书机器人触达哪套规则"。

1.2 · 在 Panel 上加 Scene 层的动机

直接往 panel.yaml 里塞机器人 app_id / 白名单 / scene_type 等字段不合适:

  • panel 是评审规则, 理论上可以被多个入口共享
  • 机器人凭据属于部署配置, 不应进入"判断引擎"核心
  • scene_type (review vs competition) 是入口级约束, panel 层感知不到

2. Decision

引入 Scene 作为 Panel 的上层部署单元。

2.1 · Scene 文件结构

scenes/
└── <scene-slug>/
    ├── scene.yaml          ← 场景级配置 (scene_type / bot 绑定 / 白名单 / 报告配置)
    └── panel.yaml          ← 评审规则 (可 extends 默认 panel)

scenes/shared-judges/ 存放跨场景可复用的专项评委 SKILL.md (不绑定特定场景)。

2.2 · scene.yaml 最小结构

yaml
name: <scene-slug>
scene_type: review          # review | competition

report:
  anchor_judge: tian        # competition 场景必须为 null
  show_scores_publicly: false

feishu:
  app_id_env: LARK_APP_ID_<ENV_VAR>
  app_secret_env: LARK_APP_SECRET_<ENV_VAR>

2.3 · scene_type 的强制约束 (scene_loader 在加载时校验)

约束reviewcompetition
anchor_judge任意 (默认 tian)必须为 null
show_scores_publicly任意必须为 true

违反约束 → scene_loader.load_scene()ValueError, 阻断后续流水线。

2.4 · Panel 继承机制

场景 panel 通过 extends 字段继承基础 panel:

yaml
# scenes/<slug>/panel.yaml
name: <slug>
extends: panels/default.yaml     # 继承, 未覆盖字段沿用

anchor_judge: tian
scoring_mode: sum_max_score

judges_override:                  # 完整替换评委列表 (非 merge)
  - slug: tian
    judge_category: anchor
    skill_path: anchors/tian/perspective/SKILL.md
    always_active: true
  - slug: first-principles
    judge_category: dimension
    skill_path: scenes/shared-judges/first-principles/SKILL.md

scoring_lenses_override:          # 完整替换评分维度
  - { slug: strategic_fit, display_name_cn: 战略契合, max_score: 30 }
  - { slug: feasibility,   display_name_cn: 落地可行, max_score: 40 }
  - { slug: one_page_quality, display_name_cn: 一页表达, max_score: 30 }

panel_loader.resolve_panel() 负责展开继承链, 返回完整 panel dict。

2.5 · 评分模式与成绩渲染

当前仅支持 scoring_mode: sum_max_score:

  • total_max_score(panel) = 所有 scoring_lensesmax_score 之和
  • 等级计算: pct = score / total_max * 100 (与 total_max 无关, 兼容 10 / 100 等任意总分制)
  • 等级档: S ≥ 95 / A ≥ 85 / B ≥ 70 / C ≥ 55 / D < 55

op2_output.render_op2_6section() 渲染 6 段式报告:

  • §1 总分 · §2 分项得分 · §3 必改项 · §4 补充建议 · §5 删减建议 · §6 一页汇总表

2.6 · Smoke 校验入口

scripts/smoke_e2e.py smoke-scene <slug> 对单场景执行静态全链路校验 (不调 LLM, 不写磁盘):

  1. scene_loader.load_scene(slug) — 加载 + 约束校验
  2. panel_loader.resolve_panel(panel_path) — 继承链展开
  3. 所有 judge skill_path 文件存在性检查
  4. sum_max_score 维度总分汇总
  5. anchor_judge 一致性 (scene vs panel)
bash
make smoke-scene SCENE=<scene-slug>

3. Consequences

3.1 · 优势

维度落地后能力
部署隔离多个飞书机器人各自路由到对应场景, 互不干扰
配置差异可读panel.yaml 只写差异, 继承自 default.yaml, diff 即设计意图
competition 约束硬编码scene_loader 在加载时校验, 无法绕过
共享评委复用scenes/shared-judges/ 评委 SKILL.md 跨场景引用, 不重复维护
CI 静态校验smoke-scene 在任何 LLM 调用前完成全链路静态检查, 适合 PR gate

3.2 · 接受的 tradeoff

  • judges_override 是完整替换 (非 merge): 每个 panel 必须显式列出所有评委, 不能只追加一位。代价是配置稍冗长, 收益是配置可读、无隐含合并逻辑。
  • scene_type 仅 2 种 (review / competition): 不支持自定义枚举。如未来出现第 3 类场景, 需修改 scene_loader.py 约束逻辑 + 本 ADR。
  • scoring_mode 仅支持 sum_max_score: 加权平均 / 取最高等其他模式未实现。现有场景均适用 sum, 延迟实现。

3.3 · 不变的承诺

  • 无 scene.yaml 时回退到单机器人模式 — 完全向后兼容
  • panel.yaml 直接使用 (不走 scene 层) 仍支持
  • wiki 知识库、锚点心智模型、5 镜头、30/90/365 attribution 不变
  • versions/ 不可变冻结不变
  • redact_check + check_public_safe 闸不变

3.4 · Invariant impact ★

本变更让以下 invariant 失效:

  • inv-old: "一个 boss 实例绑定一套 panel" → 失效 · Scene 层解耦后, 一个进程可同时服务多个场景 / 多个 panel
  • inv-old: "anchor_judge 由 panel 决定, 无外部约束" → 失效 · competition scene_type 强制 anchor_judge=null, scene 层凌驾于 panel 层
  • inv-old: "评分维度总和默认 100" → 失效 · 任意 total_max 均合法; 成绩等级按 pct 计算, 与总分无关

本变更引入以下新 invariant:

  • inv-1: scene_type: competition 必须满足 anchor_judge is null AND show_scores_publicly is true — scene_loader 强制校验
  • inv-2: scenes/shared-judges/<slug>/SKILL.md 的评委必须包含 adversarial_view 三字段 (同 CLAUDE.md §4.5) — skill_lint 校验
  • inv-3: smoke_e2e.smoke_scene(slug) 必须在任何该 scene 的 LLM 调用之前通过 — CI gate
  • inv-4: 所有 panel scoring_mode: sum_max_score 场景的 total_max_score() 必须与 render_op2_6section() 渲染的总分一致

4. Alternatives Considered

方案为什么没选
A1 · 直接在 panel.yaml 加 app_id / scene_type混淆"评审规则"与"部署配置"两个关注点; panel 理论上可多入口共享, 塞入 app_id 就绑死了
A2 · scenes/ 目录仅放 scene.yaml, panel.yaml 留在 panels/panel 与 scene 是一对一关系 (目前), 分目录存放让 diff 不直观; 同一 slug 下的 scene.yaml + panel.yaml 放在一起更清晰
A3 · judges_override 改为 merge (只写增量)合并逻辑隐含继承顺序, 难以推理"这个 panel 实际有哪些评委"; 完整替换让 panel.yaml 即文档即配置
A4 · 多个 scene_type 值 (review / competition / showcase / ...)过早泛化; review 与 competition 已覆盖所有现有场景, 其余类型留到出现真实需求时再加
A5 · smoke 校验内嵌到 scene_loader 导入时导入即触发校验会阻断 pytest collect 阶段; 独立 smoke_scene() 函数让测试与工具分离

5. Migration Plan

5.1 · 已实施 (v1.2.0, 2026-06-25)

步骤文件状态
scene_loader.pyscripts/scene_loader.pySceneConfig dataclass, load_scene(), list_scenes()
panel_loader.pyscripts/panel_loader.pyresolve_panel(), total_max_score()
op2_output.pyscripts/op2_output.pyrender_op2_6section(), DimensionScore, Op2ReviewData
smoke_e2escripts/smoke_e2e.pysmoke_scene() + smoke-scene CLI 子命令
shared-judgesscenes/shared-judges/ — 6 类专项评委 SKILL.md
多个场景scenes/ — review 场景 × N + competition 场景 × 1
e2e 测试tests/unit/test_scene_e2e.py — 40 tests (review / competition / smoke-all / server / isolation)
Makefilemake smoke-scene SCENE=<slug>

5.2 · 待办 (M5)

  • VM 联调: ≥2 飞书机器人分别触发 → 各自 panel → 报告用对应场景评委。验收见 PRD §12。

6. 关键反共识立场

"Scene 层多余, 直接给 panel 加 scene_type 字段就够了" — 错。

scene_type 是入口级约束, 它决定的不是"评审规则"(panel 的职责), 而是"这套规则允许出现在什么上下文里"。competition 场景的 anchor_judge: null 约束必须在 panel 被载入之前检查, 不能依赖 panel 内部自律。Scene 层是这条约束的自然归属地。

"panel 继承用 merge 更灵活" — 错。

灵活的代价是隐含性。"这个 panel 实际有哪些评委"如果需要追溯继承链才能回答, 就给排查问题带来了不必要的认知负担。judges_override 的完整替换语义让 panel.yaml 即文档即配置 — 打开文件即知真相。

"smoke-scene 应该调真 LLM 做端到端测试" — 错。

静态校验 (文件存在 / 字段完整 / 约束满足) 应该在 LLM 调用之前完成并且快速。smoke_scene() 的价值在于: CI 门控 + 本地开发时的即时反馈 + 不产生 LLM 费用。真实的 LLM 端到端测试是另一层, 不应混进 smoke。


7. 何时回看本 ADR

  • 出现第 3 种 scene_type (如 showcase / audit) 时: 修订 §2.2 约束表 + scene_loader
  • total_max_score 计算逻辑变化时: 修订 §2.5 等级档说明
  • M5 VM 联调完成后: 验证 inv-1~4 是否在真实飞书环境下守住
  • 任何 smoke_scene() 检查漏掉的静态错误在生产暴露时: 加 inv-N + 补 smoke 检查项

ADR-012 · accepted · 2026-06-25 · 多场景评委体系 · Scene 上层架构 · 10 场景 / 790 tests

判断力工程化 · Judgement, Engineered · 主站 · GitHub