P0 — Incident 状态机 + 分层自适应调查引擎#
总纲:
docs/REDESIGN_CLOSEDLOOP_20260708.md§2 + §3 + §4(L2/L3) 优先级:P0(当前最大空白,后续 P1/P1.5/P2 全部依赖此骨架) 目标:写完本文后可以直接编码——每个模块有 DDL、Pydantic model、接口契约、与现有代码的 diff 清单。
1. Incident 状态机#
1.1 状态定义#
class IncidentStatus(StrEnum):
# ── 新增(替换现有 open/mitigated/closed/suppressed) ──
DETECTED = "detected" # L1 接入层产出,原始告警已归一化
TRIAGING = "triaging" # L2 预处理中(聚合/关联/impact 评分)
INVESTIGATING = "investigating" # L3 调查中(Tier 1→2→3 递进)
RCA_READY = "rca_ready" # 调查完成,根因已定,待决策
REMEDIATING = "remediating" # 处置执行中
VERIFYING = "verifying" # 三级验证门禁中
RESOLVED = "resolved" # 验证通过,事件关闭
ESCALATED = "escalated" # 人工接管(审批拒绝/置信不足/验证失败回滚后)python1.2 状态迁移矩阵#
| 当前状态 | 事件 | 目标状态 | 守卫条件 |
|---|---|---|---|
DETECTED | preprocess_start | TRIAGING | IncidentContext 开始构建 |
TRIAGING | preprocess_done | INVESTIGATING | IncidentContext 产出完成 |
INVESTIGATING | rca_confirmed | RCA_READY | Tier 1/2/3 任一命中且置信达标 |
INVESTIGATING | rca_insufficient | ESCALATED | Tier 3 全部假设未达标 |
INVESTIGATING | new_alert_merged | INVESTIGATING(自迁移) | 新告警并入,增量再调查 |
RCA_READY | autonomy_auto | REMEDIATING | 自治等级 = AUTO |
RCA_READY | autonomy_approval | REMEDIATING | 飞书审批 approved |
RCA_READY | autonomy_denied | ESCALATED | 审批拒绝 or 超时降级 |
RCA_READY | autonomy_handoff | ESCALATED | 自治等级 = MANUAL/HANDOFF |
REMEDIATING | execution_done | VERIFYING | 事务化执行完成(含补偿准备) |
REMEDIATING | execution_failed | ESCALATED | CAS 漂移检测失败 or 执行异常 |
VERIFYING | verify_passed | RESOLVED | 三级门禁全部通过 |
VERIFYING | verify_failed | ESCALATED | 门禁失败 + 自动回滚完成 |
RESOLVED | new_alert_merged | INVESTIGATING | 已关闭事件被新告警拉回 |
ESCALATED | human_resolved | RESOLVED | 人工介入后手动关闭 |
1.3 并发规则#
- 同 Incident 串行:同一个
incident_id在任意时刻只有一个活跃状态迁移(PostgresFOR UPDATE行锁)。 - 跨 Incident 并行:不同 Incident 的调查/处置互不阻塞,受全局执行槽限流(现有 Redis Streams + Worker 机制)。
- 增量再调查合并语义:
INVESTIGATING态收到new_alert_merged时,新告警附加到IncidentContext.source_alerts,已有证据保留不清空,Tier 调查从当前 Tier 继续(不回退到 Tier 1)。 - RESOLVED 拉回:
RESOLVED态收到同 correlation_key 的新告警时,状态迁回INVESTIGATING,但resolved_count += 1记录拉回次数,防止无限循环(超过 3 次拉回直接 ESCALATED)。
1.4 Postgres DDL 变更#
现有 incidents 表 status 字段值从 open/mitigated/closed/suppressed 迁移到新状态枚举。新增字段:
-- incidents 表新增字段(ALTER TABLE ADD COLUMN IF NOT EXISTS 平滑升级)
ALTER TABLE incidents ADD COLUMN IF NOT EXISTS
investigation_tier TEXT NOT NULL DEFAULT ''; -- 当前调查停在哪个 Tier (tier1/tier2/tier3/done)
ALTER TABLE incidents ADD COLUMN IF NOT EXISTS
investigation_id TEXT; -- 关联 investigations 表
ALTER TABLE incidents ADD COLUMN IF NOT EXISTS
rca_confidence DOUBLE PRECISION; -- 根因置信度
ALTER TABLE incidents ADD COLUMN IF NOT EXISTS
resolved_count INTEGER NOT NULL DEFAULT 0; -- 被拉回次数(防无限循环)
ALTER TABLE incidents ADD COLUMN IF NOT EXISTS
escalation_reason TEXT NOT NULL DEFAULT ''; -- ESCALATED 原因
-- 状态迁移日志(审计 + 调试)
CREATE TABLE IF NOT EXISTS incident_transitions (
id TEXT PRIMARY KEY,
incident_id TEXT NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,
from_status TEXT NOT NULL,
to_status TEXT NOT NULL,
event TEXT NOT NULL, -- 触发事件名
actor TEXT NOT NULL DEFAULT 'system', -- system/human/<user_id>
context JSONB NOT NULL DEFAULT '{}'::jsonb, -- 事件上下文
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_incident_transitions_incident
ON incident_transitions(incident_id, created_at);sql1.5 与现有代码的 diff 清单#
| 文件 | 改动 | 说明 |
|---|---|---|
app/incidents/models.py | 改 IncidentStatus 枚举 | 从 open/mitigated/closed/suppressed 替换为 §1.1 的 8 个状态 |
app/incidents/models.py | 删 DiagnosisMode 枚举 | fast/deep 模式归零,不再需要 |
app/incidents/models.py | 改 DiagnosisTaskRecord | 删 diagnosis_mode 字段,改为 investigation_tier |
app/incidents/repository.py | 改 _upsert_incident | status 默认值从 'open' 改为 'detected' |
app/incidents/repository.py | 改 _create_or_get_task | 删 diagnosis_mode 参数和字段 |
app/incidents/repository.py | 加 状态迁移方法 | transition(incident_id, event) -> new_status,含行锁 + 迁移矩阵校验 + 审计日志 |
app/db/postgres.py | 加 DDL | §1.4 的 ALTER TABLE + incident_transitions 表 |
app/orchestration/diagnosis_runner.py | 大改 | 见 §3(核心:删 fast/deep 分流,换成 Tier 1→2→3 链路) |
2. L2 预处理层(纯规则,不进 LLM)#
2.1 IncidentContext 完整 Pydantic model#
class AlertGroupReason(StrEnum):
"""告警聚合原因。"""
ALERTMANAGER_GROUP = "alertmanager_group" # Alertmanager 原生 groupKey
TIME_WINDOW = "time_window" # 时间窗聚合(同 service + N秒内)
TOPOLOGY = "topology" # 拓扑级联关联
DEDUP = "dedup" # 重复告警去重
class TopologyRelation(BaseModel):
"""拓扑关联关系。"""
source_service: str
target_service: str
relation_type: str = "depends_on" # depends_on / same_node / same_namespace
confidence: float = 1.0
class ImpactScore(BaseModel):
"""影响面评分(决定各 Tier 的置信门槛)。"""
score: float = Field(ge=0.0, le=1.0) # 0=低影响, 1=高影响
factors: dict[str, float] = Field(default_factory=dict) # 各因子权重明细
# 因子示例: severity_weight, service_criticality, alert_count_weight, is_flapping_penalty
class IncidentContext(BaseModel):
"""L2 → L3 的产出。预处理层收敛后的根因候选集。"""
incident_id: str
source_alerts: list[NormalizedAlert] # 聚合后的原始告警(≥1条)
alert_group_reason: AlertGroupReason
correlation_key: str
root_cause_candidates: list[str] = Field(default_factory=list) # L2 拓扑关联产出的候选组件
impact_score: ImpactScore
is_flapping: bool = False # 抖动检测结果
topology_context: list[TopologyRelation] = Field(default_factory=list)
matched_runbooks: list[str] = Field(default_factory=list) # Tier 1 预匹配的 Runbook ID
primary_service: str = ""
primary_alertname: str = ""
created_at: datetimepython2.2 预处理规则配置格式#
对标 Alertmanager 语义,Python dataclass 配置,写在 app/config.py 的 Settings 里:
class PreprocessingConfig(BaseModel):
"""L2 预处理规则配置(对标 Alertmanager group_wait/group_interval/inhibit_rules)。"""
# 时间窗聚合
group_wait_sec: int = 30 # 首条告警后等待 N 秒,聚合同 service 告警
group_interval_sec: int = 300 # 同一 group 两次推送的最小间隔
# 去重
dedup_window_sec: int = 600 # 同 fingerprint 告警在 N 秒内视为重复
# 抑制规则(简化版 inhibit_rules)
inhibit_rules: list[InhibitRule] = Field(default_factory=list)
# 抖动检测
flapping_threshold: int = 5 # N 秒窗口内 firing→resolved 翻转 ≥ 此值判定抖动
flapping_window_sec: int = 600
# Impact 评分因子权重
severity_weights: dict[str, float] = Field(
default_factory=lambda: {"critical": 1.0, "warning": 0.5, "info": 0.1}
)
class InhibitRule(BaseModel):
"""抑制规则:当高优先级告警存在时,抑制低优先级告警。"""
source_match: dict[str, str] # 抑制源标签匹配 {"severity": "critical"}
target_match: dict[str, str] # 被抑制目标标签匹配
equal: list[str] = Field(default_factory=list) # 要求两者相同的 label 列表python2.3 拓扑关联#
当前项目状态:无 K8s API / OTel Service Graph 接入。
Day 1 策略:
- 拓扑关联标记为可选插件,默认关闭(
topology_enabled: bool = False)。 - 先实现静态拓扑配置:
topology_rules.yaml手写service_a depends_on service_b,满足 demo 需要。 - K8s / OTel 接入列为 backlog,接口预留
TopologyProvider抽象。
2.4 新增代码位置#
| 文件 | 说明 |
|---|---|
app/preprocessing/__init__.py | 新建模块 |
app/preprocessing/rules.py | 去重/抑制/时间窗聚合/抖动检测 |
app/preprocessing/topology.py | 拓扑关联(Day 1 = 静态配置) |
app/preprocessing/impact.py | Impact 评分 |
app/preprocessing/engine.py | 入口:接收 NormalizedAlert → 产出 IncidentContext |
app/incidents/models.py | 加 IncidentContext / ImpactScore / AlertGroupReason 等 model |
3. L3 分层自适应调查引擎(核心改动)#
3.1 Tier 1 — Runbook Schema#
class RunbookMatchCondition(BaseModel):
"""Tier 1 匹配条件:告警指纹 + 前置条件校验。"""
alertname_pattern: str # 正则匹配 alertname
service_pattern: str = ".*" # 正则匹配 service
label_matchers: dict[str, str] = Field(default_factory=dict) # label 精确匹配
severity_in: list[str] = Field(default_factory=lambda: ["critical", "warning"])
class RunbookStep(BaseModel):
"""Runbook 中的一个处置步骤。"""
action_type: str # 资源域操作 (container.restart / file.write / ...)
resource_pattern: str # 资源 URI 模板 (container://{service_name})
params: dict[str, Any] = Field(default_factory=dict)
description: str = ""
timeout_sec: int = 60
class RunbookVerifyStep(BaseModel):
"""Runbook 的验证步骤(跑完 ≠ 解决)。"""
gate: str # command_success / service_health / metric_recovery
params: dict[str, Any] = Field(default_factory=dict)
timeout_sec: int = 60
class Runbook(BaseModel):
"""结构化 Runbook(Tier 1 确定性处置的单元)。"""
id: str
name: str
display_name: str
version: str = "v1"
source: str = "seed" # seed(种子导入) / auto(Tier 3自增长) / manual(人工创建)
status: str = "active" # active / disabled / pending_review
# detect: 什么时候匹配
match_conditions: list[RunbookMatchCondition]
cooldown_sec: int = 300 # 冷却期(同 Incident 命中后 N 秒内不再重复触发)
# diagnose: 前置条件校验(可选,跑处置前确认环境)
precondition_checks: list[RunbookStep] = Field(default_factory=list)
# action: 处置步骤序列
action_steps: list[RunbookStep]
# verify: 验证步骤(跑完后验证是否真的解决了)
verify_steps: list[RunbookVerifyStep]
# escalate: 失败后降级策略
escalate_to_human: bool = True # verify 失败是否通知人工
max_auto_retries: int = 0 # 允许自动重试次数(0=不重试,直接递进 Tier 2)
# rollback: 回滚步骤(失败时自动执行)
rollback_steps: list[RunbookStep] = Field(default_factory=list)
# 元数据
description: str = ""
tags: list[str] = Field(default_factory=list)
created_at: datetime | None = None
updated_at: datetime | None = None
created_by: str = "" # seed/llm-auto/<user_id>
hit_count: int = 0 # 命中次数(统计用)
success_count: int = 0 # 命中后处置成功次数python与现有 app/skills/models.py 的关系:
| Skill (现有) | Runbook (新增) | 关系 |
|---|---|---|
| 给 LLM Router 看的”菜单卡” | Tier 1 确定性处置单元 | Runbook 是 Skill 的子集——只有能被结构化为确定性步骤的 Skill 才能转成 Runbook |
triggers 启发式匹配 | match_conditions 正则+标签精确匹配 | Runbook 匹配更严格 |
playbook 是自由 Markdown | action_steps 是结构化动作序列 | Runbook 不含自由文本 |
| 保留(Tier 3 仍需 Router 选 Skill) | 新增 | 并存,不替换 |
种子 Runbook 冷启动:从现有 app/skills/definitions/ 下的 9 个 Skill 中,提取可结构化的高频处置(磁盘清理、容器重启、OOM 处理),转为 Runbook 格式。预计冷启动 5-8 个种子 Runbook。
3.2 Tier 2 — 限定 3 轮定向验证#
Tier 2 复用现有 app/rag/ 混合检索(pgvector + BM25 + RRF),核心新增逻辑:
class Tier2VerificationRound(BaseModel):
"""Tier 2 单轮验证结果。"""
round_number: int # 1/2/3
retrieved_incident_id: str # 检索到的历史 Incident
similarity_score: float
applicability_verdict: str # applicable / not_applicable / uncertain
reasoning: str # 小模型适用性复核的推理过程
tokens_used: int = 0
class Tier2Result(BaseModel):
"""Tier 2 的完整结果。"""
hit: bool = False
reused_rca: dict[str, Any] | None = None # 命中时复用的历史 RCA
verification_rounds: list[Tier2VerificationRound] = Field(default_factory=list)
negative_samples: list[str] = Field(default_factory=list) # 误命中的 incident_id(反馈给 L6)python- 限定 3 轮:检索 Top-3 相似历史 Incident,每条用便宜档小模型(Qwen-turbo)做适用性复核(“这条历史根因是否适用于当前告警上下文?”),判定 applicable / not_applicable / uncertain。
- 命中判定:任一轮
applicable且相似度 > 阈值 → Tier 2 命中。 - 未命中:3 轮全部
not_applicable或uncertain→ 递进 Tier 3,误命中记入负样本。 - 阈值配置:
tier2_similarity_threshold: float = 0.75,tier2_max_rounds: int = 3。
3.3 Tier 3 — Hypothesis 数据模型#
class HypothesisStatus(StrEnum):
PENDING = "pending" # 待验证
INVESTIGATING = "investigating" # 取证中
CONFIRMED = "confirmed" # 已确认(置信度达标)
REFUTED = "refuted" # 已证伪
INSUFFICIENT_EVIDENCE = "insufficient_evidence" # 证据不足
class Hypothesis(BaseModel):
"""Tier 3 假设驱动调查中的单条根因假设。"""
id: str
description: str # 假设描述(如"Redis 内存溢出导致服务不可用")
target_component: str # 涉及组件
fault_domain: str # 故障域(host/container/network/datastore/...)
prior_rank: int # 可能性排序(1=最可能)
required_evidence_types: list[str] # 需要哪类证据(log/metric/trace/config/...)
status: HypothesisStatus = HypothesisStatus.PENDING
confidence: float = 0.0 # 置信度 [0, 1]
evidence_chain: list[str] = Field(default_factory=list) # evidence IDs
refutation_reason: str = "" # 证伪原因
verification_rounds: int = 0 # 本假设已验证的轮次
class Investigation(BaseModel):
"""L3 → L4 的产出。调查层的完整结果。"""
incident_id: str
tier_completed: str # tier1 / tier2 / tier3
hypotheses: list[Hypothesis] = Field(default_factory=list) # Tier 3 时填充
root_cause: RootCause | None = None
total_iterations: int = 0
evidence_ids: list[str] = Field(default_factory=list)
tier1_result: dict[str, Any] | None = None # Tier 1 命中时的 Runbook 执行结果
tier2_result: Tier2Result | None = None # Tier 2 的验证结果
inherited_evidence: list[str] = Field(default_factory=list) # 从前序 Tier 继承的证据
class RootCause(BaseModel):
"""结构化根因。"""
component: str # 故障组件
failure_mode: str # 故障模式
description: str # 人读描述
evidence_chain: list[str] # 支撑证据 ID 列表
confidence: float # 置信度
hypothesis_id: str = "" # 关联的假设 ID(Tier 3)python3.4 Tier 3 置信度判定#
- CONFIRMED 阈值:
tier3_confirm_threshold: float = 0.8(配置项) - impact_score 调门槛:
effective_threshold = tier3_confirm_threshold + impact_score.score * 0.15(影响面越大越不敢便宜地停,最高 0.95) - 达标即终止:某假设
CONFIRMED+confidence >= effective_threshold→ 立即停止,不验证剩余假设 - 兜底:所有假设遍历完无
CONFIRMED→ 取confidence最高的假设标记为BEST_EFFORT,Incident 进入ESCALATED
3.5 与现有代码的 diff 清单#
| 文件 | 改动 | 说明 |
|---|---|---|
app/orchestration/diagnosis_runner.py | 大改 | 删 get_diagnosis_graph()/get_deep_diagnosis_graph() 两个图缓存 + resolve_effective_mode() + fast-first 升级逻辑;换成 get_adaptive_investigation_graph() 单一入口 |
app/orchestration/diagnosis_runner.py | 删 normalize_diagnosis_mode() / resolve_effective_mode() | fast/deep 模式概念不再存在 |
app/orchestration/diagnosis_runner.py | 改 run_diagnosis_graph() | 删 diagnosis_mode 参数,签名改为只接收 IncidentContext(而非 raw query),调查深度由系统决定 |
app/agents/graph.py | 改 | 现有 fast 图(Router→Planner→Executor→Replanner→Report)改造为 Tier 调度图:Tier1Node→Tier2Node→Tier3Node,各节点内部保留原逻辑的精华 |
app/diagnosis_graphs/deep_diagnosis_graph.py | 合并进主图 | deep 图的多 subagent 取证保留为 Tier 3 内部的 orchestrator-workers,不再是独立图 |
app/runtime/escalation.py | 删 should_escalate_to_deep() / FastRunSignals | fast→deep 升级概念不再存在,Tier 递进是链路内部自动行为 |
app/incidents/models.py | 加 Hypothesis / Investigation / RootCause / Runbook 等 model | §3.1-3.3 的所有新模型 |
新增 app/investigation/__init__.py | 新建 | 调查引擎模块 |
新增 app/investigation/tier1.py | 新建 | Runbook 匹配 + 前置条件校验 + 执行 |
新增 app/investigation/tier2.py | 新建 | 历史 RCA 检索 + 限定 3 轮验证 |
新增 app/investigation/tier3.py | 新建 | 假设生成 + 定向取证 ReAct Loop |
新增 app/investigation/engine.py | 新建 | Tier 调度入口:T1→T2→T3 递进 + 证据继承 |
新增 app/runbooks/__init__.py | 新建 | Runbook 管理模块 |
新增 app/runbooks/models.py | 新建 | Runbook / RunbookStep / RunbookMatchCondition |
新增 app/runbooks/registry.py | 新建 | Runbook 注册表(YAML 加载 + DB 查询) |
新增 app/runbooks/matcher.py | 新建 | 告警指纹 → Runbook 匹配逻辑 |
app/config.py | 加 | PreprocessingConfig + Tier 相关阈值配置 |
4. 消解三级 vs 五级门禁矛盾#
总纲 §11.1 提到 itops-agent-platform 的 5 级门禁,当前实现为三级。
裁决:
- 核心三级(Day 1):命令成功 → 服务健康 → 指标恢复
- 扩展两级(P2 backlog):baseline_comparison(与历史基线对比)+ impact_assessment(确认修复没让别处恶化)
代码映射:
| 门禁级别 | 现有代码 | 改动 |
|---|---|---|
| 1. 命令成功 | 无(事务引擎隐含检查返回码) | 新建 显式 gate:command_success_gate,检查执行返回码 + 状态变更确认 |
| 2. 服务健康 | remediation_gate_service_health_enabled + 相关配置 | 保留,重命名为 gate_service_health |
| 3. 指标恢复 | remediation_gate_metric_recovery_enabled + 相关配置 | 保留,重命名为 gate_metric_recovery |
5. Runbook 自增长流水线(L6 联动,§3.6 落地)#
5.1 生成触发条件#
- Tier 3 调查完成 +
root_cause.confidence >= 0.8 - 人工确认 RCA 采纳(
human_feedback.rca_accepted = true) - 三级验证门禁全部通过(
RESOLVED状态) - 三个条件同时满足才触发
5.2 生成流水线#
Investigation (Tier 3 产出)
│
▼
LLM 提炼 Prompt:
输入: Investigation.hypotheses[confirmed] + evidence_chain + remediation_result
输出 schema: Runbook (§3.1 的 Pydantic model)
│
▼
Runbook 草稿 (source="auto", status="pending_review")
│
▼
审核队列:
- 人工审核 (飞书通知 + 管理后台)
- OR 自动化回归: 用同类历史告警回放,检查 Runbook 命中率 + 处置成功率
│
▼
审核通过 → status="active", 入 Runbook Registryplaintext5.3 新增代码位置#
| 文件 | 说明 |
|---|---|
app/runbooks/generator.py | LLM Runbook 生成器(Investigation → Runbook 草稿) |
app/runbooks/prompts.py | 提炼 prompt 模板 |
app/runbooks/reviewer.py | 审核队列(人工 + 自动回归) |
6. 实施顺序(P0 内部分步)#
| 步骤 | 做什么 | 产出 | 预估 |
|---|---|---|---|
| P0.1 | Incident 状态迁移 + DDL + transition() 方法 | 状态机可运行,现有链路不断 | 1 天 |
| P0.2 | L2 预处理模块 + IncidentContext 模型 | 告警风暴可测降噪率 | 1 天 |
| P0.3 | Runbook schema + 种子 Runbook 导入 + Tier 1 匹配 | Tier 1 可运行 | 1 天 |
| P0.4 | Tier 2 检索 + 3 轮验证 | Tier 2 可运行 | 0.5 天 |
| P0.5 | Tier 3 假设驱动两阶段(合并现有 deep 图) | Tier 3 可运行 | 1.5 天 |
| P0.6 | 三级 Tier 调度串联 + 删 fast/deep 入口 | 全链路 E2E | 0.5 天 |
| P0.7 | 三级验证门禁统一接口 | 门禁可运行 | 0.5 天 |
总计 ~6 天。P0 完成后即可跑 E2E 测试 + 测降噪率/Tier 命中分布指标。