# Evaluation Data Flow 与 Judge 说明

本文面向当前仓库里已经落地的实现，回答这几个问题：

- evaluation 运行时主要依赖 agent 侧哪些文件输出
- `workflow`、`evidence_collection`、`evidence_routing`、`diagnosis` 这几个 judge bucket 分别做什么
- evaluation 的输出有哪些
- 各 judge bucket 之间的依赖关系是什么
- `case.yaml`、`judge.yaml`、评分系统、最终 report 分别扮演什么角色

相关代码入口：

- `investibook/scripts/eval.py`
- `investibook/core/eval/eval_cli.py`
- `investibook/core/eval/eval_runner.py`
- `investibook/core/eval/eval_registry.py`
- `investibook/core/eval/eval_judge.py`
- `investibook/core/eval/eval_artifacts.py`
- `investibook/core/eval/semantic_evaluator.py`

## 1. Registry 输入：suite / case / judge / baseline

formal registry 在 `evaluation/regression/` 下，至少有 4 类对象：

```text
evaluation/regression/
  suites/<suite_id>.yaml
  cases/<case_id>/
    case.yaml
    judge.yaml
    expected.md
  baselines/<suite_id>.yaml
```

先看职责分层：

- `suite`
  - 定义“这次 evaluation 跑哪一组 case”
- `case`
  - 定义单个诊断任务的 run-visible 输入
- `judge`
  - 定义单个 case 的 ground truth 和判分规则
- `baseline`
  - 记录某个 suite 在特定 `suite_version` 下的基线结果

其中 `suite` 是 runner 的直接入口。

- `eval.py --suite <suite_id>`
- `eval_registry.load_suite(<suite_id>)`

runner 会先读取 `suite`，再按 suite 中列出的 case id 去加载每个 case 的 `case.yaml` 和 `judge.yaml`。

单个 case 目录结构为：

```text
evaluation/regression/cases/<case_id>/
  case.yaml
  judge.yaml
  expected.md
```

职责边界：

- `suites/<suite_id>.yaml`
  - suite 级入口文件
  - 定义 case 集合、状态、baseline eligibility、执行策略元数据
- `case.yaml`
  - run-visible。
  - 提供 `prompt.text`、`environment.cluster_id`、`time_window`、`execution`、`followup`。
  - 这些内容会进入实际 ACP 执行。
- `judge.yaml`
  - judge-only。
  - 提供 `ground_truth.root_cause` 和 `judge.*` 各类 bucket 规则。
  - 不暴露给 agent prompt。
- `expected.md`
  - 给人读，不参与自动判分。

`eval_registry.load_case()` 会在加载时把两份 YAML 合并成一个 `EvalCaseSpec`：

- `case.yaml` 的运行输入保留
- `judge.yaml` 的 `ground_truth` 和 `judge` 注入到 `case.data`

所以从代码视角看，runner/judge 读到的是一个合并后的 case；但从信息边界看，agent 运行时仍只依赖 `case.yaml` 暴露的信息。

如果只看 registry 的加载顺序，可以简单记成：

```text
suite.yaml
  -> 选出 case_id 列表
  -> 逐个读取 cases/<case_id>/case.yaml
  -> 逐个读取 cases/<case_id>/judge.yaml
  -> 合并成 EvalCaseSpec
```

## 2. 端到端数据流

### 2.1 高层视角：Runner / Agent / Judger

如果从更抽象的系统视角看，当前 evaluation 可以先理解成 3 个主体：

- `runner`
  - 负责读取 registry、创建 fresh workspace、驱动 agent 执行、镜像 audit、汇总 report
- `agent`
  - 负责在 workspace 中完成实际诊断，包括确认元信息、采集证据、补充搜证、输出最终结论
- `judger`
  - 负责读取 audit 产物、抽取 facts、按 bucket 判分，并生成最终 judgment

对应的高层 data flow 可以先看这张图：

```mermaid
flowchart LR
    A[case.yaml + judge.yaml] --> B[Runner]
    B --> B1[创建 Fresh Workspace]
    B1 --> D[Agent]
    D --> E[诊断过程]
    E --> E1[Phase 1<br/>Meta 确认]
    E --> E2[Phase 2<br/>Collect Evidence]
    E --> E3[Phase 3<br/>后续搜证 / 定向验证]
    E --> E4[Phase 4<br/>最终结论]
    E1 --> F[Workspace Outputs]
    E2 --> F
    E3 --> F
    E4 --> F
    F --> B2[Runner: Audit Mirror]
    B2 --> H[Judger]
    H --> B3[Runner: 汇总 Judgment / Score / Report]
```

这里最重要的是：

- `runner` 不负责“诊断对不对”，它主要负责编排和落盘
- `agent` 不直接“打分”，它主要负责产生诊断过程中的 artifact 和 final answer
- `judger` 不参与执行，它只消费 run 产物并做结构化判断
- `audit mirror` 不是独立主体，它只是 `runner` 在 agent 执行完成后做的一步产物镜像

### 2.2 Agent 诊断过程的主 phase

如果只看 agent 在一次 case 中的诊断主线，可以进一步抽象成下面几个 phase：

```mermaid
flowchart TD
    A[用户请求 / case prompt] --> B[Phase 1: Meta 确认]
    B --> B1[确认 cluster_id]
    B --> B2[确认 incident time window]
    B --> B3[初始化 diagnosis run]
    B --> C[Phase 2: Collect Evidence]
    C --> C1[批量采集 metrics / queries / logs]
    C --> C2[生成 evidence summary / workspace evidence report]
    C --> D[Phase 3: 后续搜证]
    D --> D1[针对 panic / restart / hotspot 做定向查询]
    D --> D2[补充 log / query / metric 交叉验证]
    D --> D3[必要时由 backend/workspace post-pass 补充 analysis patch 或结构化引用]
    D --> E[Phase 4: 最终结论]
    E --> E1[根因判断]
    E --> E2[关键证据链]
    E --> E3[影响判断]
    E --> E4[处理建议]
```

这几个 phase 和 judge 的关系大致是：

- `Phase 1: Meta 确认`
  - 主要被 `contract`、`workflow` 消费
- `Phase 2: Collect Evidence`
  - 主要被 `workflow`、`execution_contract`、`evidence_collection` 消费
- `Phase 3: 后续搜证`
  - 主要被 `critical_evidence`、`evidence_routing` 消费
- `Phase 4: 最终结论`
  - 主要被 `diagnosis` 消费

### 2.3 实现视角：细粒度数据流

```mermaid
flowchart TD
    A[eval.py / eval_cli] --> B[eval_runner.run_eval_suite]
    B --> C[eval_registry.load_suite/load_case]
    C --> C1[case.yaml]
    C --> C2[judge.yaml]
    B --> D[create fresh workspace]
    D --> E[_run_acp_case / run_chat_and_wait]
    E --> F[workspace runtime outputs]
    F --> F1[evidences/*.json]
    F --> F2[evidences/workspace_evidence_report.json]
    F --> F3[evidences/evidence_summary_for_agent.txt/json]
    F --> F4[.opencode/telemetry.log]
    B --> G[_mirror_eval_audit_artifacts]
    G --> H[data/eval_audits/<run>/<case>/]
    H --> I[judge_case_output]
    I --> J[resolve_run_bundle]
    J --> K[trace facts]
    J --> L[evidence facts]
    I --> M[output facts]
    I --> N[routing facts]
    K --> O[contract]
    K --> P[workflow]
    K --> Q[execution_contract]
    L --> Q
    L --> R[evidence_collection]
    L --> S[critical_evidence]
    L --> N
    N --> T[evidence_routing]
    M --> U[diagnosis]
    I --> V[initial deterministic packet]
    V --> W[semantic_evaluator]
    W --> W1[semantic_context_v1.json]
    W --> W2[semantic_packet_v1.json]
    W2 --> U
    O --> X[final verdict + score]
    P --> X
    Q --> X
    R --> X
    S --> X
    T --> X
    U --> X
    X --> Y[eval_judgment DB]
    Y --> Z[report.txt / report_detail.txt]
```

一句话概括：`case.yaml` 提供 run-visible 输入，agent 在 fresh workspace 上跑 live 诊断，workspace 产出 evidence 与 telemetry，judge 将这些产物归一化成 facts，再按 bucket 判分，最后写入 judgment 和 report。

## 3. Agent 侧主要输出文件

当前 runner 会把 workspace 内这些内容镜像到 `data/eval_audits/<eval_run_id>/<eval_case_run_id>/`：

- `evidences/workspace_evidence_report.json`
  - 当前 judge 的核心 diagnosis report。
  - `trace facts` 会从这里读取 cluster、time window、analysis 等元数据。
- `evidences/metrics.json`
  - metric evidence 主输入之一。
- `evidences/queries.json`
  - slow query / TopSQL / decoded plan 等 query evidence 主输入之一。
- `evidences/logs.json`
  - 日志汇总输入；同时可配合 supplemental logs 使用。
- `evidences/collection_errors.json`
  - 记录采集错误，供 evidence facts 使用。
- `evidences/gate_checks.json`
  - 记录 infra / hotspot 等 gate 信号。
- `evidences/component_health.json`
  - 组件重启等直接证据，常被 `critical_evidence` 使用。
- `evidences/evidence_summary_for_agent.txt`
  - 给 agent 阅读的摘要，当前 workflow facts 会通过 telemetry 或 summary artifact 是否存在来判断是否读过 summary。
- `evidences/evidence_summary_for_agent.json`
  - 摘要 JSON 版本。
- `evidences/cluster_meta.json`、`workload.json`、`resource_control.json`、`tikv_thread_cpu.json`、`files.json`、`links.json`
  - 补充证据文件，部分 case 的 `critical_evidence` 或后续人工审阅会用到。
- `.opencode/telemetry.log`
  - workflow judge 的关键输入，用来判断 agent 是否加载了架构上下文、guardrails、是否初始化 diagnosis run、是否跑过 evidence collection 等。
- `semantic_context_v1.json`
  - semantic shadow 的输入快照，由 deterministic judgment 生成后写入。
- `semantic_packet_v1.json`
  - semantic shadow 的输出，只补充 diagnosis bucket，不替代 deterministic 主判链路。

## 4. Judge 处理分层

### 4.1 Facts 提取层

在真正进入 bucket 之前，judge 会先抽取四类 facts：

- `trace facts`
  - 来自 `workspace_evidence_report.json`、manifest fallback、`telemetry.log`
  - 负责表达 cluster/time window 对齐情况、workflow milestone、artifact 是否存在
- `evidence facts`
  - 来自 `evidences/*.json` 与补充日志
  - 负责表达证据包完整性、channel 覆盖、panic/kill-query 等直接信号
- `output facts`
  - 来自 agent 最终 `output_text`
  - 通过 regex/keyword 把自由文本转成布尔判断
- `routing facts`
  - 来自 `analysis_patch_path` 与 metric/query/log/event evidence 的可解析性
  - 这里的 `analysis patch` 不是 evaluation 产物，而是 backend/workspace evidence post-pass 对 report.analysis 的结构化补丁
  - 负责判断这些结构化证据引用是否能落回真实采集产物

### 4.2 主要 bucket 说明

为了更适合和同事分享，可以把当前 judge 分成 4 个更抽象的评估维度；括号里保留当前代码中的 bucket 名，便于和实现对照。

| 抽象维度 | 当前代码名 | 主要输入 | 做的处理 | 输出 |
| --- | --- | --- | --- | --- |
| `Execution Validity` | `workflow` | `trace["workflow"]` + `judge.workflow.checks` | 判断 agent 是否走到预期执行阶段，如加载 guardrails、初始化 diagnosis run、完成 evidence collection、读 summary、完成 targeted log query | `status`、`checks`、`score(0-10)` |
| `Evidence Coverage` | `evidence_collection` | `evidence_facts` + `artifact_facts` + `channel_facts` + `judge.evidence_collection.checks` | 判断核心 evidence bundle 是否齐全、preferred channel 是否至少出现、是否全部覆盖 | `status`、`checks`、`score(0-5)` |
| `Evidence Grounding` | `evidence_routing` | `routing_facts` + `judge.evidence_routing.checks` | 判断 backend/workspace post-pass 生成的 analysis patch 是否存在、JSON 是否合法、item shape 是否合法、metric/query/log/event 引用是否能解析到本次 evidence | `status`、`checks`、`score(0-5)` |
| `Diagnosis Quality` | `diagnosis` | `output_text`、`output_facts`、`judge.diagnosis.checks`、`semantic_shadow` | 判断最终回答是否识别主根因、是否解释关键因果链、是否避免误诊、是否给出对齐 remediation；支持 `deterministic_only` 和 `blended` 两种模式 | `status`、`checks`、`score(0-50)` |

另外还有两个不能忽略的层：

| 抽象层 | 当前代码名 | 作用 |
| --- | --- | --- |
| `Case Alignment Gate` | `contract` | 判断本次 run 是否对齐该 case 的 cluster 和 allowed window，不对齐直接 `invalid` |
| `Minimum Execution Gate` | `execution_contract` | 判断 required workflow / required artifacts / minimum channels 是否满足，不满足直接 `invalid` |
| `Critical Evidence Gate` | `critical_evidence` | 从 artifact JSON / logs 中找直接证据，如 restart、panic、kill query；它是 diagnosis 之前的硬门槛之一，得分范围 `0-30` |

如果只想用一页向同事解释，可以把整体判分理解成：

1. 先过 3 道 gate
2. 再看 4 个主要维度
3. 最后汇总成 verdict 和 score

对应关系如下：

```mermaid
flowchart TD
    A[Case Alignment Gate<br/>contract] --> B[Minimum Execution Gate<br/>execution_contract]
    B --> C[Execution Validity<br/>workflow]
    C --> D[Evidence Coverage<br/>evidence_collection]
    D --> E[Critical Evidence Gate<br/>critical_evidence]
    E --> F[Evidence Grounding<br/>evidence_routing]
    F --> G[Diagnosis Quality<br/>diagnosis]
    G --> H[Final Verdict + Score]
```

补充说明：

- `analysis patch` 的职责是对 `workspace_evidence_report.json` 里的 `analysis` 区域做结构化补丁
- 它属于 workspace/backend evidence post-pass 链路，不属于 evaluation runner 或 judger
- evaluation 只负责读取它、校验它引用的 evidence 是否真实可解析，并把这部分能力体现在 `evidence_routing` bucket

## 5. 各 bucket 的依赖关系

重点不是“前一个 bucket 的 verdict 作为后一个 bucket 的输入”，而是“多个 bucket 共享前面抽取出的 facts”。

### 5.1 直接依赖

- `workflow`
  - 只依赖 `trace facts`
- `evidence_collection`
  - 依赖 `evidence facts`
  - 也依赖从 trace/evidence 派生出的 `artifact_facts`、`channel_facts`
- `evidence_routing`
  - 依赖 `routing facts`
  - 而 `routing facts` 又依赖 backend/workspace post-pass 产出的 `analysis_patch_path`、metric/query evidence、channel observed 状态
- `diagnosis`
  - 依赖 `output facts`
  - 若 semantic shadow 可用，还会叠加 `semantic_packet_v1.json`

### 5.2 间接依赖

- `execution_contract` 间接依赖 `workflow`，因为它读取的是 workflow checks 的 pass/fail 结果。
- `evidence_routing` 间接依赖 `evidence_collection` 的输入层，因为 routing 是否可解析要看 metrics / queries / logs 是否真的被采到。
- `diagnosis` 不直接依赖 `workflow` 或 `evidence_collection` 的 verdict，但 semantic evaluator 的 context 会把 deterministic packet 中的 verdicts、critical evidence checks、routing checks 一并带进去。

### 5.3 Runner 层面的特殊依赖

`semantic shadow` 不是和 deterministic judge 并行跑的。实际顺序是：

1. 先跑一次 `judge_case_output()`，得到初始 deterministic judgment
2. 用初始 judgment 生成 `semantic_context_v1.json`
3. 调用 semantic evaluator，生成 `semantic_packet_v1.json`
4. 如果 `shadow_available=true`，再跑第二次 `judge_case_output()`，让 diagnosis bucket 读到 semantic shadow

所以 diagnosis bucket 对 semantic 的依赖，发生在第二轮 judgment。

## 6. 评分系统

当前 bucket 分值上限：

- `workflow`: 10
- `evidence_collection`: 5
- `critical_evidence`: 30
- `evidence_routing`: 5
- `diagnosis`: 50

总分满分 100，先按各 bucket 的原始得分归一化，再汇总：

```text
raw_total_score
  = workflow_score
  + evidence_collection_score
  + critical_evidence_score
  + evidence_routing_score
  + diagnosis_score
```

分数还会经过一层 banding：

- 如果 `overall_verdict=invalid`，最终分数强制为 `0`
- 如果 hard gate 通过，最终分数至少是 `70`
- 如果 hard gate 未通过，最终分数最高只能是 `69`

这里的 hard gate 指：

- `critical_evidence_status == pass`
- 且 `diagnosis_status == pass`

### 6.1 Verdict precedence

最终 verdict 不是简单看总分，而是按固定优先级决策：

1. `contract=fail` -> `invalid`
2. `execution_contract=fail` -> `invalid`
3. `workflow in {fail, partial}` -> `invalid`
4. `evidence_collection=fail` -> `invalid`
5. `evidence_collection=partial` -> `fail`
6. `critical_evidence=fail` -> `fail`
7. `critical_evidence=partial` -> `fail`
8. `evidence_routing=fail` -> `fail`
9. `evidence_routing=partial` -> `fail`
10. `diagnosis=pass` -> `pass`
11. 其他情况 -> `fail`

这意味着：

- `partial` 永远不能升级成 overall `pass`
- 总分高也不能覆盖掉前面 bucket 的硬失败
- `invalid` 保留给不可比较 run

## 7. Evaluation 输出

当前 evaluation 的输出可以分成四层。

### 7.1 运行期 DB 记录

- `eval_runs`
- `eval_case_runs`
- `eval_judgment`

它们保存 run 级状态、case 级执行结果、最终 judgment packet。

### 7.2 Audit 目录

每次 run 会落到：

```text
backend/data/eval_audits/<eval_run_id>/
```

每个 case 会落到：

```text
backend/data/eval_audits/<eval_run_id>/<eval_case_run_id>/
```

这里保存：

- 镜像后的 `evidences/`
- `.opencode/telemetry.log`
- `semantic_context_v1.json`
- `semantic_packet_v1.json`

这是 judge 与后续人工审计最关键的落盘目录。

### 7.3 Run 级 report

runner 最后会生成：

- `report.txt`
- `report_detail.txt`

当前位置：

```text
backend/data/eval_audits/<eval_run_id>/report.txt
backend/data/eval_audits/<eval_run_id>/report_detail.txt
```

`report.txt` 侧重 run 摘要：

- suite / version
- execution/comparison status
- case 总数、pass/fail/invalid 数
- total / avg score
- timing
- token usage / cache tokens / cost
- baseline comparison summary
- 每个 case 的 verdict / score / failure / duration

`report_detail.txt` 侧重 case 级细节：

- case 级 verdict / subtype / workspace id
- diagnosis penalties
- baseline diff
- missing cases from current run

### 7.4 Baseline 导出

如果 run 被 promote 成 repo baseline，还会写入：

- `evaluation/regression/baselines/<suite_id>.yaml`
- `evaluation/regression/baselines/runs/<eval_run_id>/`

导出的 baseline snapshot 会保留：

- `report.txt`
- `report_detail.txt`
- per-case `judgment.json`
- per-case `output.txt`
- per-case `case_result.yaml`
- per-case `semantic_context_v1.json`
- per-case `semantic_packet_v1.json`
- per-case `evidences/`
