# INV-8: Background Loop Catalog

**版本**：v1.0 初稿
**日期**：2026-05-03（台北）
**來源**：`apps/api/src/main.py` grep asyncio.create_task（實測 31 個）
**用途**：ADR-123 Background Loop Migration Strategy 的執行基礎

---

## 概覽

| 統計 | 數量 |
|------|------|
| 總 background loop | 31 |
| platform_internal | 2（#27、#30）|
| legacy_awoooi_default（過渡）| 29 |
| requires_project_id（Phase 2+ 改造後）| 0（初始）|

---

## 完整清單

> 行號來自 `apps/api/src/main.py`
> 「頻率」為估計值，需 code review 確認

| # | 函數名 | 行號 | 頻率 | 標記 | 改造優先度 | 備註 |
|---|--------|------|------|------|-----------|------|
| 1 | `seed_playbooks_from_rules()` | 331 | 啟動一次 | `legacy_awoooi_default` | P3（低）| 只在啟動時跑一次，風險低 |
| 2 | `backfill_redis_to_pg()` | 341 | 啟動一次 | `legacy_awoooi_default` | P3 | 啟動一次性 backfill |
| 3 | `ensure_playbook_embeddings_indexed()` | 348 | 啟動一次 | `legacy_awoooi_default` | P3 | 啟動一次性 index 確認 |
| 4 | `resend_stale_ready_tokens()` | 362 | 每 30s | `legacy_awoooi_default` | P1 | 涉及 approval token，Phase 8 前必改 |
| 5 | `run_incident_analysis_sweeper()` | 373 | 每 5min | `legacy_awoooi_default` | P2 | 掃描 incident，多 tenant 後需分 project |
| 6 | `run_asset_scanner_loop()` | 383 | 每 10min | `legacy_awoooi_default` | P2 | K8s asset 掃描 |
| 7 | `run_rule_catalog_sync_loop()` | 393 | 每 5min | `legacy_awoooi_default` | P2 | 規則 catalog 同步 |
| 8 | `run_capacity_scanner_loop()` | 403 | 每 15min | `legacy_awoooi_default` | P2 | 容量掃描 |
| 9 | `run_compliance_scanner_loop()` | 413 | 每 30min | `legacy_awoooi_default` | P2 | 合規掃描 |
| 10 | `run_aider_event_processor_loop()` | 423 | 每 30s | `legacy_awoooi_default` | P2 | aider 事件處理 |
| 11 | `run_coverage_evaluator_loop()` | 432 | 每 1h | `legacy_awoooi_default` | P3 | 覆蓋率評估 |
| 12 | `run_rule_stats_updater_loop()` | 442 | 每 15min | `legacy_awoooi_default` | P3 | 規則統計 |
| 13 | `run_asset_change_tracker_loop()` | 452 | 每 5min | `legacy_awoooi_default` | P2 | 資產變更追蹤 |
| 14 | `run_hermes_rule_quality_loop()` | 462 | 每 1h | `legacy_awoooi_default` | P2 | Hermes 規則品質 |
| 15 | `run_capacity_forecaster_loop()` | 472 | 每 6h | `legacy_awoooi_default` | P3 | 容量預測 |
| 16 | `run_daily_report_loop()` | 481 | 每日 | `legacy_awoooi_default` | P3 | 每日報告 |
| 17 | `run_approval_timeout_resolver()` | 490 | 每 30s | `legacy_awoooi_default` | **P0** | 🔴 P1-15：無 trace_id；Phase 8 前必改 |
| 18 | `run_evolver_loop()` | 499 | 每 1h | `legacy_awoooi_default` | P2 | playbook 進化（飛輪關鍵）|
| 19 | `run_playbook_generation_governance_loop()` | 507 | 每 30min | `legacy_awoooi_default` | P2 | playbook 生成治理 |
| 20 | `run_knowledge_decay_loop()` | 519 | 每 6h | `legacy_awoooi_default` | P2 | KM 知識衰退 |
| 21 | `run_km_backfill_reconciler_loop()` | 529 | 每 1h | `legacy_awoooi_default` | P2 | KM backfill 核對 |
| 22 | `run_aol_writeback_loop()` | 540 | 每 30s | `legacy_awoooi_default` | P1 | AI 飛輪 AOL writeback，KM 雙路徑 |
| 23 | `_run_kb_rot_cleaner_loop()` | 585 | 每 6h | `legacy_awoooi_default` | P3 | KB 腐敗清理 |
| 24 | `run_finetune_export_loop()` | 594 | 每日 | `legacy_awoooi_default` | P3 | finetune 資料匯出 |
| 25 | `run_proactive_inspector_loop()` | 605 | 每 5min | `legacy_awoooi_default` | P2 | 主動巡檢 |
| 26 | `run_offline_replay_loop()` | 614 | 每 15min | `legacy_awoooi_default` | P3 | 離線重放 |
| 27 | `run_ai_slo_watchdog_loop()` | 623 | 每 5min | **`platform_internal`** | N/A（已完成）| SLO watchdog，project_id=__platform__ |
| 28 | `run_governance_loop()` | 632 | 每 5min | `legacy_awoooi_default` | P2 | AI 治理主循環 |
| 29 | `run_governance_dispatcher_loop()` | 640 | 每 30s | `legacy_awoooi_default` | P2 | 治理事件派送 |
| 30 | `_run_model_version_tracker_loop()` | 701 | 每 1h | **`platform_internal`** | N/A（已完成）| model 版本追蹤，platform_resource |
| 31 | （需確認）| 701+ | TBD | TBD | TBD | grep 計數 = 31，需確認第 31 個 |

---

## 改造策略（ADR-123）

### platform_internal（#27、#30）
```python
# main.py 改造示意
async def _run_with_context(coro, project_id: str):
    ctx_token = project_id_ctx_var.set(project_id)
    try:
        await coro
    finally:
        project_id_ctx_var.reset(ctx_token)

# platform_internal loop：
asyncio.create_task(_run_with_context(
    run_ai_slo_watchdog_loop(), "__platform__"
))
```

### legacy_awoooi_default（其餘 29 個）
```python
# Phase 2 過渡：fallback 到 awoooi
asyncio.create_task(_run_with_context(
    run_incident_analysis_sweeper(), "awoooi"  # 過渡期
))
# 退場時程：Phase 4 完成後 90 天內逐一改為真正 multi-tenant
```

---

## 改造優先度說明

| 優先度 | 說明 | 對應 loop |
|--------|------|-----------|
| P0（立即）| 直接影響安全性或正確性 | #17（approval_timeout_resolver）|
| P1（Phase 4 前）| 影響 AwoooP run state 正確性 | #4、#22 |
| P2（Phase 6 前）| 影響多 tenant 資料隔離 | #5~16、#18~20、#25、#28~29 |
| P3（Phase 8 前）| 低頻率、低風險 | #1~3、#11~12、#15~16、#23~24、#26 |

---

## 驗收標準

- [ ] 第 31 個 loop 確認並列入
- [ ] 全部 31 個 loop 在 main.py 有 logging context（project_id 可見）
- [ ] `platform_internal` loop 帶 `project_id=__platform__`
- [ ] `legacy_awoooi_default` loop 帶 `project_id=awoooi`（PR-10）
- [ ] loop #17（approval_timeout_resolver）已補入 trace_id（PR-10 合併時）

*最後更新：2026-05-03（台北）*
