# 分层诊断：信号打标、故障分类和专项 Skill 拆分设计

> Status: Design draft  
> Date: 2026-05-21  
> Audience: Investibook / TriagePilot / Replay / Diagnosis skill maintainers  
> Scope: 诊断 skill 分层、metric/log/query 信号打标、粗粒度故障分类、专项诊断 skill 拆分、evaluation 回归检查点

## 1. 背景

我们已经有了第一版分层诊断代码和 evaluation 结果。现在主要欠缺的不是继续往 `tidb-clinic-diagnosis/SKILL.md` 里加更多单条规则，而是把“证据收集之后如何稳定进入正确诊断路径”这件事做成可治理的结构。

当前主要问题有两个：

1. 证据收集后，Agent 直接开始推断根因。它在收集过程中很容易提前形成先验，后续即使看到反证，也很难回到更早状态重新分类。
2. 原诊断 skill 像代码里的 `main()`，包含太多单条规则、例子和 guard。Agent 一次读入大量内容后，不可能稳定注意到所有规则。

因此下一阶段的重点应放在两层：

1. **分层第一步：信号打标和故障分类。**  
   metric、log、slow query、TopSQL、event 等证据只要出现异常，就先转换成结构化标签。标签组合形成一个类似“向量”的诊断先验，用来指向少量粗粒度故障分类。

2. **分层第二步：分类后的专项诊断。**  
   根据故障分类读取对应专项 skill，把原大 skill 的内容拆成小的、触发条件清晰的诊断单元。不要依赖 Agent 自己去“捞 nutshell”；需要的 nutshell、guard、KB facts 应该成为 manifest 可索引的一等资产。

## 2. 设计输入

本设计继承以下历史文档，但关注点更偏 runtime 诊断路径：

| 文档 | 相关结论 |
|---|---|
| `docs/research/2026-04-21-diagnosis-knowledge-orchestration.zh.md` | Phase 0 明确提出把 `tidb-clinic-diagnosis/SKILL.md` 拆成目录页和阶段文件 |
| `docs/plans/2026-05-06-diagnosis-knowledge-orchestration-phase0-plan.zh.md` | 定义短入口、orchestration、KB 资产目录和按需读取原则 |
| `docs/plans/2026-05-06-diagnosis-knowledge-orchestration-code-architecture.zh.md` | 定义 Diagnosis Skill、Manifest、Frame、Action、Guard、Evidence、KB 的职责边界 |
| `docs/plans/2026-05-06-prompt-context-mcp-consolidation-design.zh.md` | 说明拆分后的 context 入口：`SKILL.md` 是目录页，正文按需 read |
| `docs/plans/2026-05-18-triagepilot-manual-regression-and-skill-layering.zh.md` | 定义 manual issue 的 stage 分类、harness 检查点和 skill 分层方向 |

这份文档在上述基础上进一步回答：

- Metric 或其他证据异常之后，应该打什么标签？
- 标签如何组合成粗粒度故障分类？
- 分类如何决定读取哪个专项诊断 skill？
- Evaluation 应该如何检查分层后的每一层是否真的工作？

## 3. 设计目标

核心目标：

| 目标 | 说明 |
|---|---|
| 降低早熟归因 | 在 root cause 推断前，先生成异常标签和候选分类 |
| 缩小上下文 | 每次只读取当前分类相关的专项 skill，而不是整本大 skill |
| 让诊断可回归 | Evaluation 不只评最终 RCA，也评标签、分类、专项证据链 |
| 保留 Agent 推理 | 分类给出强先验和约束，但不把诊断做成硬编码 if-else |
| 支持渐进扩展 | 首版只保留 5-6 个粗分类，后续通过 ISS 和 evaluation 再拆细 |

非目标：

- 不在首版实现完整 backend workflow engine。
- 不要求每个指标都有确定性阈值。
- 不把分类器设计成不可解释的 embedding/RAG。
- 不一次性迁移所有诊断知识。
- 不让分类结果直接等于最终根因。

## 4. 总体架构

### 4.1 和 PR 549 现有 Sub-Agent 编排的关系

PR 549 已经实现了第一层“角色分工”：

```text
diagnosis-orchestrator
  -> Task(evidence-agent)
  -> read evidences/agent-runs/evidence-agent.json
  -> Task(diagnosis-analyst)
  -> read evidences/agent-runs/diagnosis-analyst.json
  -> answer user
```

这条调用链由 OpenCode 配置驱动，而不是 backend Python 代码直接 spawn 子进程：

- `agentkit/config/opencode.json.template` 注册三个 agent：
  - `diagnosis-orchestrator` 是 `primary`
  - `evidence-agent` 和 `diagnosis-analyst` 是 hidden `subagent`
- `diagnosis-orchestrator` 只有 Task 权限，可以调用 `evidence-agent` 和 `diagnosis-analyst`，但不能直接 bash、edit 或 load skills。
- `evidence-agent` 可以使用 data-access skills，负责收集证据并写 `evidences/agent-runs/evidence-agent.json`。
- `diagnosis-analyst` 可以使用 diagnosis skills，不能使用 data-access skills，负责读取证据、分析根因并写 `evidences/agent-runs/diagnosis-analyst.json` 和 `evidences/workspace_evidence_report.json`。
- `agentkit/config/SKILL_CATALOG.md` 定义哪些 skill 属于 `core`、`data-access`、`diagnosis`，orchestrator 根据这个 catalog 生成 Task prompt 的 `Required skills to load`。

本文基于 PR 549 已有的 sub-agent 编排，继续补齐这条调用链内部的“诊断逻辑分层”：

| 当前 PR 549 角色 | 已有职责 | 本设计要补的职责 |
|---|---|---|
| `diagnosis-orchestrator` | 判定诊断意图、调用两个 subagent、读取 JSON 文件回答用户 | 保持不变；最多要求最终输出引用 analyst 的 `signal_tags`、`fault_classification` |
| `evidence-agent` | 收集 metrics/logs/query/events，写 evidence summary | 输出更明确的 `evidence_inventory`，记录数据源可用性、缺失项、关键 observation |
| `diagnosis-analyst` | 读取证据和 diagnosis skills，写 RCA | 在 RCA 前先执行 `SignalTag[]`、`FaultClass candidates`、专项 skill routing，再进入根因分析 |

需要特别说明：PR 549 目前已经避免了 `diagnosis-orchestrator` 直接读取 diagnosis skill，也避免了 `evidence-agent` 使用 diagnosis skill 做推理；但是 `diagnosis-analyst` 的默认 required skills 仍然包含 `tidb-clinic-diagnosis`，`opencode.json.template` 也允许 analyst load 这个 skill。因此当前实现并不等于“分层后完全不读 `tidb-clinic-diagnosis/SKILL.md`”。

当前状态更准确地说是：

```text
diagnosis-orchestrator
  - 不读 tidb-clinic-diagnosis/SKILL.md
  - 只做调度和最终回复

evidence-agent
  - 不读取 tidb-clinic-diagnosis 的诊断正文
  - 允许把 tidb-clinic-diagnosis/scripts/collect_evidence.py 当作标准证据收集脚本执行

diagnosis-analyst
  - 仍然可能读取 tidb-clinic-diagnosis/SKILL.md
  - 仍然可能把它当作主要诊断正文
```

所以下一阶段的目标是调整 `tidb-clinic-diagnosis` 的职责：

```text
Before:
  tidb-clinic-diagnosis/SKILL.md = 大诊断正文 + 大量规则 + examples + guards

After:
  tidb-clinic-diagnosis/SKILL.md = 短入口 / routing index
  workflow/signal-tagging.md = 异常信号打标协议
  workflow/fault-classification.md = 粗粒度故障分类协议
  diagnoses/*.md = 分类后的专项诊断正文
  guards/*.md = 按 claim/action 触发的 guard
```

也就是说，`diagnosis-analyst` 可以继续 load `tidb-clinic-diagnosis` 这个 skill 名称，但 load 到的 `SKILL.md` 不应该再是一篇大而全的诊断手册，而应该只是引导 analyst 先产出 `SignalTag[]` 和 `FaultClass candidates`，再按分类读取更小的专项诊断文件。

换句话说：

- **Sub-agent 分层**解决“谁做证据收集、谁做分析、谁回答用户”。
- **SignalTag/FaultClass 分层**解决“分析 agent 在写 RCA 前如何稳定分类和路由”。

首版不建议新增第四个 `diagnosis-classifier` agent。原因是 PR 549 的设计明确保持三角色，避免 backend 和 OpenCode 编排复杂度继续上升。更合适的做法是让 `diagnosis-analyst` 内部按阶段输出结构化中间产物；等 evaluation 证明分类阶段足够稳定、且确实需要独立并行或独立评分时，再考虑拆出新的 classifier subagent。

### 4.2 目标执行链

建议把诊断流程拆成四层：

```text
主 Agent 编排
  -> 证据收集层 Evidence Collection
  -> 信号打标层 Signal Tagging
  -> 故障分类层 Fault Classification
  -> 专项诊断层 Specialized Diagnosis
```

更具体地说：

```text
User issue / workspace
  -> collect common evidence
  -> produce Evidence Inventory
  -> extract SignalTag[]
  -> match FaultClass candidates
  -> read specialized skill by class
  -> run class-specific diagnosis
  -> crosscheck / recommendation / report
```

这里最关键的变化是：**证据收集之后不直接写 RCA，而是先写 `SignalTag[]` 和 `FaultClass candidates`。**

结合 PR 549 的实际执行链，目标形态应是：

```text
diagnosis-orchestrator
  -> evidence-agent
       - collect/reuse evidence
       - write evidence_inventory into evidence-agent.json or evidence_summary_for_agent.json
  -> diagnosis-analyst
       - read evidence inventory
       - produce signal_tags
       - produce fault_classification
       - read selected diagnosis skills
       - produce RCA, rejected causes, recommendations
  -> diagnosis-orchestrator
       - answer from diagnosis-analyst.json final_response
```

这意味着下一步代码改造的重点不是“怎么调用 subagent”，而是：

1. 扩展 `evidence-agent` 输出契约，让 evidence inventory 更稳定。
2. 扩展 `diagnosis-analyst` 输出契约，加入 `signal_tags`、`fault_classification`、`selected_diagnosis_skills`。
3. 拆分 `tidb-clinic-diagnosis` 和 nutshell/diagnosis skills，让 analyst 在分类后只读相关专项内容。
4. 更新 evaluation，使它能评分这些中间产物，而不是只看最终 RCA。

## 5. 第一层：证据收集

证据收集层负责建立公共上下文，不负责判断最终根因。

### 5.1 输入

- Jira issue / alert title / alert rule / comments
- workspace metadata
- cluster ID、tenant、时间窗口、cluster type
- Clinic metrics
- slow query / TopSQL
- logs
- activity events
- diagnostic files
- tool call status

### 5.2 输出

输出应该是 Evidence Inventory，例如：

```yaml
evidence_inventory:
  time_window:
    start: "..."
    end: "..."
    confidence: high
  cluster:
    id: "..."
    type: dedicated
  data_sources:
    metrics: available
    topsql: available
    slow_query: available
    logs: partial
    events: available
  notable_observations:
    - source: metrics
      summary: "tikv-0 CPU is higher than peer TiKV nodes"
    - source: metrics
      summary: "tikv-0 grpc p99 latency is worse than peer nodes"
    - source: metrics
      summary: "unified_read thread CPU is high"
```

### 5.3 禁止事项

- 不在这一层输出最终根因。
- 不因为 Jira comment 有结论就跳过证据收集。
- 不把单个 metric 异常当成 root cause proof。
- 不把工具失败静默吞掉。

## 6. 第二层：信号打标

信号打标层把 Evidence Inventory 中的异常转成结构化标签。这里的“向量”不是 embedding，而是一组可解释、可回归的标签。

### 6.1 SignalTag Schema

首版建议用轻量 YAML/JSON 表达：

```yaml
signal_tags:
  - id: tikv_cpu_hot_node
    source: metrics
    component: tikv
    metric_family: cpu
    symptom: saturation
    locality: single_node
    direction: high
    duration: sustained
    confidence: high
    evidence_ref: metrics/tikv_cpu
    notes: "tikv-0 is significantly higher than peer nodes"
```

建议字段：

| 字段 | 说明 |
|---|---|
| `id` | 稳定标签名，用于 evaluation 和分类规则 |
| `source` | `metrics` / `logs` / `topsql` / `slow_query` / `events` / `tool_status` |
| `component` | `tidb` / `tikv` / `pd` / `tiflash` / `client` / `unknown` |
| `metric_family` | `cpu` / `latency` / `thread` / `rocksdb` / `raft` / `grpc` / `sql` 等 |
| `symptom` | `saturation` / `stall` / `error` / `skew` / `regression` / `missing` |
| `locality` | `single_node` / `node_group` / `cluster_wide` / `unknown` |
| `direction` | `high` / `low` / `spike` / `drop` / `empty` / `failed` |
| `duration` | `instant` / `sustained` / `correlated_window` / `unknown` |
| `baseline` | `absolute` / `previous_day_same_time` / `previous_week_same_time` / `historical_p95` / `peer_nodes` / `unknown` |
| `change` | `new` / `higher_than_baseline` / `lower_than_baseline` / `same_as_baseline` / `periodic_pattern` / `unknown` |
| `confidence` | `high` / `medium` / `low` |
| `evidence_ref` | 指向具体 evidence 文件、tool output 或图表 |
| `notes` | 简短解释，不能写 root cause |

### 6.2 标签粒度

标签应该描述“观察到的异常”，而不是直接描述“根因”。

好的标签：

- `tikv_cpu_hot_node`
- `tikv_cpu_hot_node_regression_vs_yesterday`
- `tidb_qps_drop_vs_previous_week_same_time`
- `tikv_unified_read_high`
- `tikv_grpc_latency_skew`
- `slow_query_heavy_range_scan`
- `pd_etcd_disk_latency_spike`
- `tool_loki_partial_404`
- `activity_modify_iops_event`

不好的标签：

- `root_cause_is_read_hotspot`
- `customer_sql_bug`
- `tikv_bad`
- `agent_should_check_gc`

### 6.3 环比和基线标签

首版打标不能只看当前窗口的绝对值。很多诊断需要区分三种情况：

1. 当前值绝对高，并且相对历史也明显升高。
2. 当前值绝对高，但和昨天或上周同一时间接近，可能是周期性业务高峰。
3. 当前值不算高，但相对该集群平时水平明显异常，例如低流量时段突然翻倍。

因此 `SignalTag` 应显式记录 baseline 语义。建议优先支持三类比较：

| 比较方式 | 作用 | 典型标签 |
|---|---|---|
| peer node comparison | 判断单节点偏斜、hot node、局部瓶颈 | `tikv_cpu_hot_node`, `grpc_latency_single_node_skew` |
| previous day same time | 判断是否偏离昨天同一业务周期 | `tidb_cpu_regression_vs_yesterday`, `qps_drop_vs_yesterday` |
| previous week same time | 判断是否偏离周周期业务模式 | `latency_regression_vs_previous_week`, `slow_query_volume_new_vs_weekly_baseline` |

示例：

```yaml
signal_tags:
  - id: tikv_cpu_hot_node_regression_vs_yesterday
    source: metrics
    component: tikv
    metric_family: cpu
    symptom: skew
    locality: single_node
    direction: high
    duration: sustained
    baseline: previous_day_same_time
    change: higher_than_baseline
    confidence: high
    evidence_ref: metrics/tikv_cpu
    notes: "tikv-0 is higher than peer nodes and higher than the same window yesterday"

  - id: tidb_qps_periodic_high_same_as_weekly_baseline
    source: metrics
    component: tidb
    metric_family: sql
    symptom: regression
    locality: cluster_wide
    direction: high
    duration: sustained
    baseline: previous_week_same_time
    change: same_as_baseline
    confidence: medium
    evidence_ref: metrics/tidb_qps
    notes: "QPS is high in the incident window but matches the weekly same-time baseline"
```

环比标签不应该自动变成根因判断。它只回答“这个异常是不是相对历史也异常”。例如：

- `same_as_baseline` 可以降低“突发故障”的置信度，但不能排除业务高峰下暴露出的资源瓶颈。
- `higher_than_baseline` 可以提高异常置信度，但仍需要专项诊断证明瓶颈机制。
- 如果 baseline 数据不可用，必须打 `baseline=unknown`，不要假设当前值就是异常。

### 6.4 为什么先打标

打标有三个作用：

1. **延迟根因判断。** Agent 必须先描述异常集合，而不是直接写结论。
2. **提供分类输入。** 故障分类根据标签组合命中，不依赖单个指标。
3. **让 evaluation 可检查。** Judge 可以判断某个 case 是否识别出关键异常标签。

## 7. 第三层：粗粒度故障分类

分类阶段根据 `SignalTag[]` 选择候选 `FaultClass`。首版不要分太细，建议保留 5-6 类。

### 7.1 首批 FaultClass

| FaultClass | 适用范围 | 典型标签组合 | 后续专项 skill |
|---|---|---|---|
| `read_path_hotspot` | TiKV read path、coprocessor、range scan、热点读 | `tikv_cpu_hot_node` + `tikv_unified_read_high` + `tikv_grpc_latency_skew` + `topsql_or_slow_query_scan_heavy` | `diagnoses/read-path-hotspot.md` |
| `write_ingest_bottleneck` | ingest、write、raft/apply、RocksDB、compaction/write stall | `tikv_write_thread_high` + `rocksdb_stall_or_compaction_high` + `raft_apply_or_store_pressure` | `diagnoses/write-ingest-bottleneck.md` |
| `sql_plan_or_query_workload` | SQL plan、TopSQL、slow query、expensive query、索引选择 | `topsql_dominant_sql` + `slow_query_plan_suspicious` + `tidb_or_tikv_resource_correlated` | `diagnoses/sql-plan-query-workload.md` |
| `tidb_runtime_or_compute` | TiDB CPU、GC、session pressure、executor 层问题 | `tidb_cpu_high` + `topsql_cpu_high_or_absent` + `tidb_gc_signal_checked` | `diagnoses/tidb-runtime-compute.md` |
| `control_plane_or_storage_io_stall` | PD/etcd、disk I/O stall、metadata/control plane 影响 | `pd_or_etcd_latency_high` + `disk_io_stall_signal` + `timeline_correlated_event` | `diagnoses/control-plane-storage-io-stall.md` |
| `evidence_unreliable_or_missing` | 工具失败、数据缺失、时间窗口不可信、cluster 映射不可信 | `tool_failed_or_empty` + `missing_required_source` 或 `metadata_conflict` | `diagnoses/evidence-unreliable.md` |

### 7.2 分类输出格式

分类输出应该显式包含候选、置信度、命中标签、缺失标签和反例。

```yaml
fault_classification:
  primary:
    class: read_path_hotspot
    confidence: medium
    matched_tags:
      - tikv_cpu_hot_node
      - tikv_unified_read_high
      - tikv_grpc_latency_skew
      - slow_query_heavy_range_scan
    missing_or_weak_tags:
      - topsql_hot_node_correlation
    counter_evidence:
      - tidb_cpu_not_high
  secondary:
    - class: sql_plan_or_query_workload
      confidence: medium
      reason: "Suspicious DELETE plan may be driving read path load"
  next_skill:
    - diagnoses/read-path-hotspot.md
    - diagnoses/sql-plan-query-workload.md
```

### 7.3 分类规则原则

- 分类可以有多个候选，但必须排序。
- 分类不等于根因，只决定下一步读取哪个专项 skill。
- 分类必须说明缺失证据，不能把缺失项当作已经满足。
- 如果证据不可靠，应优先进入 `evidence_unreliable_or_missing`，而不是强行诊断。
- 分类规则首版可以是 Markdown 表格或 YAML manifest，不需要先做代码规则引擎。

## 8. 第四层：专项诊断 Skill

专项诊断层负责在已分类的上下文中做 RCA 和 workaround。

### 8.1 目录结构建议

建议把 `tidb-clinic-diagnosis` 改成短入口 + manifest + 专项文件：

```text
agentkit/skills/builtin/tidb-clinic-diagnosis/
  SKILL.md
  workflow/
    evidence-collection.md
    signal-tagging.md
    fault-classification.md
    crosscheck-and-report.md
  manifest/
    fault-classes.yaml
    signal-tags.yaml
    guard-index.yaml
  diagnoses/
    read-path-hotspot.md
    write-ingest-bottleneck.md
    sql-plan-query-workload.md
    tidb-runtime-compute.md
    control-plane-storage-io-stall.md
    evidence-unreliable.md
  guards/
    metric-semantics.md
    claim-requirements.md
    recommendation-safety.md
```

### 8.2 SKILL.md 的职责

`SKILL.md` 只做目录页和硬边界：

- 什么时候进入诊断。
- 必须先读 `workflow/evidence-collection.md`。
- 证据收集后必须读 `workflow/signal-tagging.md` 和 `workflow/fault-classification.md`。
- 根据分类读取 `diagnoses/*.md`。
- 最终输出前读 `workflow/crosscheck-and-report.md`。

它不应该继续承载所有专项 SOP。

### 8.3 专项 skill 的职责

每个专项 skill 应包含：

| 模块 | 内容 |
|---|---|
| 进入条件 | 需要哪些 `FaultClass` 和关键 `SignalTag` |
| 必要证据 | 必须收集哪些 metrics/logs/query/events |
| 反例排除 | 哪些情况不能下这个结论 |
| 根因模式 | 该分类下常见 RCA pattern |
| 输出要求 | 置信度、证据链、缺失证据、workaround |
| harness hooks | evaluation 应检查哪些中间状态 |

例如 `read-path-hotspot.md` 应明确：

- 不能只看到 TiKV CPU 高就说 read hotspot。
- 需要 read path thread、gRPC/cop latency、TopSQL/slow query、节点偏斜或 region/query 相关证据。
- 如果 SQL plan 显示 range scan，需要说明为什么 scan 会落到 TiKV read path。
- 如果 TopSQL 缺失，需要降级置信度或说明替代证据。

## 9. 关于 Nutshell 和 KB 的处理

当前一个实际问题是：我们希望 Agent 去捞“nutshell”，但运行时更容易捞到的是 `tidb-knowledge-base` 这种 skill。结果是：

- Agent 可能读到泛知识，但没读到当前分类真正需要的诊断摘要。
- KB 资产承担了流程职责。
- 大 skill 继续变长，因为每个 nutshell 都被塞回主入口。

建议把 nutshell 变成 manifest 可索引的一等资产：

```text
tidb-clinic-diagnosis/
  nutshells/
    read-path-hotspot.yaml
    write-ingest-bottleneck.yaml
    tidb-gc-cpu.yaml
    pd-etcd-disk-stall.yaml
```

每个 nutshell 只写极短内容：

```yaml
id: read_path_hotspot_nutshell
fault_class: read_path_hotspot
summary: "Read hotspot is a TiKV read path saturation pattern, not simply high CPU."
must_check:
  - tikv_cpu_hot_node
  - tikv_unified_read_high
  - grpc_or_cop_latency_skew
  - topsql_or_slow_query_scan_correlation
must_not_claim:
  - "Do not claim read hotspot from TiKV CPU alone."
read_next:
  - diagnoses/read-path-hotspot.md
  - guards/metric-semantics.md
```

`tidb-knowledge-base` 继续负责事实资产，例如 tier architecture、official docs index、incorrect conclusions、metric glossary。它不负责决定流程，也不承担专项诊断 SOP。

## 10. Diagnosis Orchestrator 编排方式

这里的主 Agent 指 PR 549 中的 `diagnosis-orchestrator`。它的职责是编排 subagent，而不是自己读取所有诊断规则、收集证据或撰写根因。

`diagnosis-orchestrator` 每次诊断应遵循：

```text
1. Read workspace context and .opencode/investibook/SKILL_CATALOG.md.
2. Invoke Task(evidence-agent) with core + data-access skills.
3. Read evidences/agent-runs/evidence-agent.json and evidence summary files.
4. If evidence output is missing or invalid, retry evidence-agent once.
5. Invoke Task(diagnosis-analyst) with core + diagnosis skills.
6. Read evidences/agent-runs/diagnosis-analyst.json.
7. If analyst needs more evidence, run at most one supplemental evidence-agent pass and one final analyst pass.
8. Reply to the user from diagnosis-analyst.json final_response and structured fields.
```

`SignalTag[]`、`FaultClass candidates` 和 `selected_diagnosis_skills` 不应由 `diagnosis-orchestrator` 直接生成。它们是 `diagnosis-analyst` 的结构化中间产物。也就是说：

```text
orchestrator 编排 subagent 调用
analyst 编排诊断逻辑阶段
```

`diagnosis-analyst` 在一次分析中应按下面的内部阶段运行：

```text
1. Read evidence-agent output and evidence files.
2. Read tidb-clinic-diagnosis/SKILL.md as a short routing index.
3. Produce Evidence Inventory if evidence-agent output does not already provide one.
4. Read workflow/signal-tagging.md.
5. Produce SignalTag[].
6. Read workflow/fault-classification.md and manifest/fault-classes.yaml.
7. Produce FaultClass candidates.
8. Read selected diagnoses/*.md and related nutshells/guards.
9. Run specialized diagnosis.
10. Read workflow/crosscheck-and-report.md.
11. Write diagnosis-analyst.json and workspace_evidence_report.json.
```

这里可以允许 analyst 回跳，但回跳必须更新中间产物：

- 新证据出现后，可以新增或降级 SignalTag。
- FaultClass 可以调整，但需要说明原因。
- 如果专项诊断发现分类不成立，必须回到 fault classification，而不是硬写 RCA。

## 11. Evaluation 和 Harness 设计

分层后，evaluation 不应该只问最终答案是否正确。需要把评分拆到各层。

### 11.1 建议评分点

| 层 | 评分点 | 示例 |
|---|---|---|
| Evidence Collection | 是否收集关键数据源，是否记录失败和缺失 | metrics、TopSQL、slow query、logs、events |
| Signal Tagging | 是否识别关键异常标签，是否避免把结论写进标签 | `tikv_unified_read_high`、`slow_query_heavy_range_scan` |
| Fault Classification | 是否选对 primary/secondary class，是否列出缺失证据和反例 | `read_path_hotspot` vs `write_ingest_bottleneck` |
| Skill Routing | 是否读取了正确专项 skill 和 guard | `diagnoses/read-path-hotspot.md` |
| Specialized Diagnosis | 是否满足专项证据链，是否解释因果链 | CPU/thread/latency/query/time correlation |
| Crosscheck | 是否排除常见错误归因 | 没把 Jira comment 当证据，没把 event 语义读错 |
| Recommendation | workaround 是否可执行、低风险、有验证方式 | SQL/index、限流、扩容、profile、补采数据 |

### 11.2 Evaluation Case 字段

建议在 replay/evaluation case 中逐步补充：

```yaml
expected_signal_tags:
  must_include:
    - tikv_cpu_hot_node
    - tikv_unified_read_high
  must_not_include:
    - write_ingest_bottleneck_claim

expected_fault_class:
  primary: read_path_hotspot
  acceptable_secondary:
    - sql_plan_or_query_workload

expected_skill_reads:
  - diagnoses/read-path-hotspot.md
  - diagnoses/sql-plan-query-workload.md

must_collect:
  - tikv_cpu
  - tikv_thread_cpu
  - grpc_or_cop_latency
  - slow_query_or_topsql

must_not_claim:
  - "TiKV CPU high alone proves root cause"
```

### 11.3 和 Manual ISS 的关系

Manual ISS 可以映射到这套分层：

| Manual issue stage | 对应 evaluation 层 |
|---|---|
| `data_retrieval` | Evidence Collection |
| `data_correctness` | Evidence Collection + deterministic tool validator |
| `data_semantics` | Signal Tagging + Guard |
| `classification` | Fault Classification |
| `specialized_diagnosis` | Specialized Diagnosis |
| `recommendation` | Recommendation |
| `template` | Crosscheck / Report |

这能让我们回答“分层后具体差在哪，哪里明显增强”：

- 如果标签错了，是 signal semantics 问题。
- 如果标签对但分类错了，是 fault classification 问题。
- 如果分类对但 RCA 错了，是专项 skill 或 evidence chain 问题。
- 如果 RCA 对但建议差，是 recommendation/runbook 问题。

## 12. 分阶段落地计划

### Phase A：文档和结构落地

目标：不大改 runtime，先把 skill 结构拆出来。

任务：

1. 把 `tidb-clinic-diagnosis/SKILL.md` 缩短为目录页。
2. 新增 `workflow/evidence-collection.md`、`signal-tagging.md`、`fault-classification.md`、`crosscheck-and-report.md`。
3. 新增首批 5-6 个 `diagnoses/*.md`。
4. 新增 `manifest/fault-classes.yaml` 和 `manifest/signal-tags.yaml`。
5. 为 3-5 个 P1 case 补 expected tags / fault class / skill reads。

### Phase B：Evaluation 接入

目标：让分层是否生效可量化。

任务：

1. Judge 输出或解析 `SignalTag[]`。
2. Judge 评分 `FaultClass` 是否命中。
3. 记录实际读取的专项 skill。
4. 在 TriagePilot Board 展示 failure layer。
5. 对 P1 manual issue 跑 main baseline vs layered branch diff。

### Phase C：轻量 runtime state

目标：降低 Agent 漏读、漏分类、漏 guard 的概率。

任务：

1. 在 workspace/session 中记录 evidence inventory、signal tags、fault class。
2. 在继续诊断时注入短 guidance，例如当前分类和应读 skill。
3. 最终输出前检查 selected class 是否有对应 evidence chain。
4. 对高置信 deterministic guard 产生 warning。

## 13. 风险和取舍

| 风险 | 说明 | 缓解 |
|---|---|---|
| 分类过粗 | 不足以区分所有故障 | 首版只作为路由先验，允许 secondary class 和 unknown |
| 分类过细 | 又变成另一种大规则库 | 限制首批 5-6 类，通过 evaluation 再拆 |
| 标签质量不稳定 | Agent 打标可能漂移 | 定义稳定 tag id，case 中写 expected tags |
| Agent 仍然跳步 | 直接进入 RCA | SKILL.md 明确禁止，evaluation 检查中间产物 |
| KB 和专项 skill 边界混乱 | KB 又开始写流程 | KB 只放 facts/glossary，流程和 SOP 放 diagnoses |
| 工具数据错导致分类错 | 分类建立在错误证据上 | `evidence_unreliable_or_missing` 作为独立分类，优先暴露数据问题 |

## 14. 成功标准

短期成功标准：

- P1 manual case 能产出稳定的 `SignalTag[]` 和 `FaultClass`。
- Agent 不再在证据收集后直接跳到 RCA。
- 至少 3 个专项 skill 被实际按分类读取。
- Evaluation 能指出失败发生在打标、分类、专项诊断还是建议层。

中期成功标准：

- TriagePilot Board 能展示 manual issue 的 failure layer。
- 分层分支相对 main baseline 在低分 case 上有明确提升。
- 新增诊断规则优先进入 tag/class/diagnosis/guard 结构，而不是继续扩写大 `SKILL.md`。
- 对重复出现的问题，可以通过 case + expected tags + fault class 形成稳定回归。

## 15. 一句话总结

这套设计的核心不是让 Agent 少推理，而是让 Agent 在推理前先完成可检查的“异常打标”和“故障分类”。分类提供强先验，专项 skill 提供窄上下文，evaluation 固定每一层的回归信号。这样 manual issue 才能从“诊断感觉不对”变成“哪一层失败、该修哪里、修完如何证明”的工程对象。
