# Skill 03: OpenClaw Cognitive Expert
# AI 認知引擎專家

> **管轄範圍**: Incident Engine, GraphRAG, Multi-Sig, Event Bus
> **觸發條件**: 修改 `services/incident_*.py`, `services/multi_sig*.py`, `services/graph*.py`

---

## 文件資訊

| 欄位 | 值 |
|------|-----|
| **版本** | v1.7 |
| **建立日期** | 2026-03-20 (台北) |
| **建立者** | Claude Code |
| **最後修改** | 2026-03-31 18:00 (台北) |
| **修改者** | Claude Code (首席架構師) |

### 變更紀錄

| 版本 | 日期 | 執行者 | 變更內容 |
|------|------|--------|----------|
| v1.0 | 2026-03-20 | Claude Code | 初始建立 |
| v1.1 | 2026-03-23 | Claude Code | DecisionManager 雙軌引擎 |
| v1.2 | 2026-03-25 | Claude Code | 智能路由引擎 + Tool/Modular 關係 |
| v1.3 | 2026-03-25 | Claude Code | 加入文件資訊區塊 |
| v1.4 | 2026-03-26 | Claude Code | K8s 資源名稱驗證 (ADR-016) |
| v1.5 | 2026-03-27 | Claude Code | Stream Key 統一 + 告警去重機制 |
| v1.6 | 2026-03-27 | Claude Code | **P1 優化: 稍後/靜默按鈕** |
| v1.7 | 2026-03-31 | Claude Code | **Phase 22: OpenClaw + Nemotron 協作 (ADR-044)** |

---

## 核心架構 (三層記憶系統)

### 1. Working Memory (Redis Hash)
```
位置: 192.168.0.188:6380/10
用途: 活動中 Incident 狀態 (TTL 7天)
Key: incident:{incident_id}
```

### 2. Episodic Memory (PostgreSQL/SQLite)
```
位置: 192.168.0.188:5432 (Prod) / awoooi.db (Dev)
用途: 長期歷史記錄
Table: incident_records
```

### 3. Event Bus (Redis Streams)
```
Stream: awoooi:signals  # 2026-03-27 統一格式
Group: awoooi_workers
用途: 告警信號傳遞
```

### 4. Telegram 去重 (2026-03-27 新增)
```
Key: telegram_sent:{incident_id}
TTL: 600 秒 (10 分鐘)
用途: 防止相同 Incident 重複發送 Telegram
```

### 5. Telegram 按鈕佈局 (2026-03-27 v1.6)
```
第一行 (主要操作):
[✅ 簽核] [❌ 拒絕]

第二行 (延遲/靜默):
[⏰ 稍後] [🔕 靜默 1h]

第三行 (可選):
[⚡ 執行自動調優]
```

| 按鈕 | 功能 | Redis Key | TTL |
|------|------|-----------|-----|
| ⏰ 稍後 | 30 分鐘後再提醒 | `telegram_snooze:{approval_id}` | 30 分鐘 |
| 🔕 靜默 1h | 同類資源告警靜默 | `telegram_silence:{resource_name}` | 1 小時 |

---

## 核心約束 (Cognitive Iron Laws)

### 1. 雙層記憶同步

```python
# ✅ 正確 (必須同時寫入)
async def create_incident(incident: Incident):
    # 1. Working Memory (即時狀態)
    await redis_client.hset(f"incident:{incident.id}", mapping=incident.to_redis())

    # 2. Episodic Memory (持久化)
    await db.execute(insert(IncidentRecord).values(**incident.to_db()))

# ❌ 禁止 (單層寫入)
await redis_client.hset(...)  # 只寫 Redis，遺漏 DB
```

### 2. LLM 調用必須走快取

```python
# ✅ 正確
async def analyze_with_ai(context: str) -> str:
    cache_key = f"ai_response:{hash(context)}"
    cached = await redis_client.get(cache_key)
    if cached:
        return cached

    response = await _call_ollama(context)
    await redis_client.setex(cache_key, 3600, response)
    return response

# ❌ 禁止 (無快取直接調用)
response = await _call_ollama(context)
```

### 3. Multi-Sig 動作必須 Dry-Run

```python
# ✅ 正確 (先模擬，後執行)
async def execute_k8s_action(action: K8sAction) -> ExecutionResult:
    # Step 1: Dry-Run 驗證
    dry_result = await k8s_executor.execute(action, dry_run=True)
    if not dry_result.success:
        raise ValidationError(dry_result.error)

    # Step 2: 等待 Multi-Sig 簽核
    approval = await wait_for_approval(action.approval_id)

    # Step 3: 真實執行
    return await k8s_executor.execute(action, dry_run=False)

# ❌ 禁止 (跳過 dry_run)
return await k8s_executor.execute(action, dry_run=False)
```

---

## Event Bus 規範

### Signal Producer (告警輸入)

```python
# Webhook 接收告警 → XADD 至 Stream
await redis_client.xadd(
    "stream:awoooi_signals",
    {"payload": json.dumps(signal.model_dump())},
    maxlen=10000,  # 告警風暴防護
)
```

### Signal Consumer (Worker)

```python
# XREADGROUP 消費 → ACK 確認
messages = await redis_client.xreadgroup(
    groupname="awoooi_workers",
    consumername=consumer_id,
    streams={"stream:awoooi_signals": ">"},
    count=10,
    block=5000,
)
# 處理後必須 ACK
await redis_client.xack("stream:awoooi_signals", "awoooi_workers", message_id)
```

---

## Incident Engine 聚合規則

| 參數 | 值 | 說明 |
|------|-----|------|
| 時間窗口 | 30 分鐘 | 相近告警聚合 |
| 相似度閾值 | 0.7 | 文字相似度門檻 |
| 升級規則 | 3 筆 P2 → P0 | 自動提升 Severity |

---

## 🚨 提案防爆圈 Guardrails (2026-03-23 首席架構師指令)

> **目的**: AI 生成的修復提案可能包含危險指令，必須裝上保險絲

### 鐵律 1: 毀滅性指令黑名單

```python
# ❌ 絕對禁止出現在 AI 生成的提案中
FORBIDDEN_COMMANDS = [
    "rm -rf /",
    "rm -rf /*",
    "DROP DATABASE",
    "DROP TABLE",
    "TRUNCATE",
    "kubectl delete namespace",
    "kubectl delete -A",
    "> /dev/sda",
    "mkfs",
    ":(){:|:&};:",  # Fork bomb
]

def validate_proposal_safety(script: str) -> bool:
    """提案安全檢查 - 必須在生成後立即執行"""
    for forbidden in FORBIDDEN_COMMANDS:
        if forbidden.lower() in script.lower():
            logger.critical("dangerous_command_blocked", command=forbidden)
            return False
    return True
```

### 鐵律 2: K8s Namespace 強制綁定

```python
# ✅ 正確: 所有 K8s 指令必須帶 Namespace
kubectl rollout restart deployment/awoooi-api -n awoooi-prod

# ❌ 禁止: 無 Namespace (可能影響其他系統)
kubectl rollout restart deployment/awoooi-api

# ❌ 絕對禁止: 跨越到系統 Namespace
kubectl delete pod xxx -n kube-system
kubectl delete pod xxx -n default
```

### 鐵律 3: 提案白名單 Namespace

```python
ALLOWED_NAMESPACES = ["awoooi-prod", "awoooi-dev"]

def validate_namespace(command: str) -> bool:
    """檢查 K8s 指令是否在許可 Namespace 內"""
    if "kubectl" in command:
        for ns in ALLOWED_NAMESPACES:
            if f"-n {ns}" in command or f"--namespace={ns}" in command:
                return True
        return False  # 無 Namespace 或不在白名單
    return True
```

### 檢查清單 (每個提案生成後)

| 檢查項目 | 通過條件 |
|---------|---------|
| 毀滅性指令掃描 | 黑名單指令數量 = 0 |
| Namespace 驗證 | 所有 kubectl 指令帶 `-n awoooi-prod` |
| 權限邊界 | 不涉及 kube-system, default |

---

## 🚨 中文 Action 解析 (2026-03-23 教訓)

> **事故**: Y 按鈕執行後顯示「錯誤」，因為 LLM 生成中文建議，`parse_operation_from_action()` 無法解析

### 根因

```json
{"event": "background_execution_skip", "reason": "Could not parse operation type from action"}
{"action": "擴展 awoooi-api-deployment 副本數 + 啟用 HPA"}
{"action": "重新啟動 postgres-primary-0 服務"}
```

### 支援的中文模式

| 中文關鍵字 | 操作類型 | 備註 |
|-----------|---------|------|
| 擴展...副本數 | SCALE_DEPLOYMENT | 自動移除 `-deployment` 後綴 |
| 重新啟動 | RESTART_DEPLOYMENT | 不需要「服務」後綴 |
| StatefulSet Pod (xxx-0) | DELETE_POD | 自動識別 `-N` 格式 |

### 驗證指令

```bash
# 查看目前支援的解析模式
grep -n "chinese" apps/api/src/api/v1/approvals.py

# 檢查 Action 解析函數
grep -A 5 "def parse_operation_from_action" apps/api/src/api/v1/approvals.py
```

### 教訓

1. **LLM 輸出不可預測**: 生成的建議可能是中文、英文或混合
2. **後綴可能錯誤**: LLM 常加 `-deployment` 後綴，需自動清理
3. **資源類型識別**: `xxx-0` 是 StatefulSet Pod，應使用 DELETE_POD 而非 RESTART_DEPLOYMENT

---

## 🤖 本地模型選擇策略 (Phase 12.2)

> **更新**: 2026-03-25 | **硬體**: 192.168.0.188 (62GB RAM, 無 GPU)

### Tier 分層選擇

```
告警進入
    ↓
[判斷複雜度]
    │
    ├─ 簡單 (單一指標) → llama3.2:3b (~2GB, 快速)
    │
    ├─ 中等 (多指標) → qwen2.5:7b-instruct (~5GB, 平衡)
    │
    └─ 複雜 (跨服務) → qwen2.5:14b-instruct-q4 (~10GB, 深度)
```

### 模型配置

| 場景 | 推薦模型 | 參數 |
|------|----------|------|
| 中文告警分析 | qwen2.5:7b-instruct | 中文優化 |
| 快速摘要 | llama3.2:3b | 低延遲 |
| 複雜提案 | qwen2.5:14b-instruct-q4 | 高準確度 |
| 推理鏈分析 | deepseek-r1:7b-qwen-distill | 備選 |

### 調用規範

```python
# ✅ 正確 (根據複雜度選模型)
async def get_model_for_task(complexity: str) -> str:
    model_map = {
        "simple": "llama3.2:3b",
        "medium": "qwen2.5:7b-instruct",
        "complex": "qwen2.5:14b-instruct-q4_K_M",
    }
    return model_map.get(complexity, "qwen2.5:7b-instruct")

# ❌ 禁止 (硬編碼單一模型)
model = "qwen2.5:72b"  # 超過硬體限制
```

### 硬體限制

| 資源 | 約束 |
|------|------|
| 最大模型 | 32B (Q4 量化) |
| 推薦模型 | 7B-14B |
| ☁️ Cloud 模型 | 移至 Phase 12.4 評估 |

---

## 🔀 智能路由引擎 (Phase 13.3)

> **目標**: 用小模型判別意圖 + 評估複雜度，動態路由到不同 LLM

### 架構

```
告警進入
    ↓
┌─────────────────────────────────┐
│ Intent Classifier (Qwen2.5-1B) │ ← 新增
│ 延遲: < 100ms                   │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│ Complexity Scorer (1-5 分)      │ ← 新增
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│ AI Router                       │
│ ├─ 簡單 (1-2) → Ollama Local   │
│ ├─ 中等 (3)   → Ollama Large   │
│ └─ 複雜 (4-5) → Gemini/Claude  │
└─────────────────────────────────┘
```

### 意圖分類

| 意圖 | 範例 | 複雜度 |
|------|------|--------|
| RESTART | Pod 重啟、服務重啟 | 1-2 |
| SCALE | 擴容、縮容 | 2-3 |
| CONFIG | 配置變更 | 3-4 |
| DIAGNOSE | 根因分析、跨服務追蹤 | 4-5 |

### 風險分級自動修復

| 風險等級 | 自動化策略 |
|----------|-----------|
| **LOW** | ✅ 可自動執行 |
| **MEDIUM** | ✅ 可自動執行 (日誌記錄) |
| **HIGH** | 🟡 需 Telegram 快速確認 |
| **CRITICAL** | 🔴 **必須人工審核** |

> **統帥指示**: 真正嚴重緊急的告警，必須透過人工審核！

---

## 🧰 Tool 封裝與模組化關係

> **統帥澄清 (2026-03-25)**: Tool 封裝 ≠ 模組化，兩者是不同層次

| 維度 | leWOOOgo 模組化 | Tool 封裝 |
|------|-----------------|-----------|
| 層次 | 軟體架構層 | 系統整合層 |
| 範圍 | 六大積木 | 外部系統連接 |
| 關係 | 是 Tool 的**實作基礎** | 是模組化的**應用場景** |

### Tool 放置位置

```
Tool 封裝 → 放在 ACTION 積木內 → 遵循模組化原則開發
```

參考: `memory/feedback_tool_vs_modular.md`

---

## 🎯 K8s 資源名稱驗證 (ADR-016)

> **新增**: 2026-03-26 (首席架構師)
> **原因**: E2E 驗證發現 AI 產生無效 kubectl 指令

### 鐵律: kubectl 指令必須驗證資源存在性

```python
# ❌ 禁止: 直接使用 target_resource
kubectl_cmd = f"kubectl rollout restart deployment/{target_resource}"

# ✅ 正確: 先驗證再使用
from src.services.resource_resolver import get_resource_resolver

resolver = get_resource_resolver()
result = await resolver.resolve(target_resource, namespace)

if result.success:
    kubectl_cmd = f"kubectl rollout restart deployment/{result.resource_name} -n {result.namespace}"
elif result.requires_confirmation:
    # 標記需人工確認資源名稱
    raise ResourceValidationError(result.note, candidates=result.candidates)
```

### 常見錯誤模式

| 輸入 | AI 產生 (錯誤) | 正確 |
|------|---------------|------|
| `https://api.awoooi.wooo.work` | `deployment/https://api.awoooi.wooo.work` | `deployment/awoooi-api` |
| `prod-docker-188` | `deployment/prod-docker-188` | 非 K8s 資源，跳過 |

### 相關檔案

| 檔案 | 功能 |
|------|------|
| `src/utils/k8s_naming.py` | 正規化函數 |
| `src/services/resource_resolver.py` | 動態驗證器 |
| `docs/adr/ADR-016-k8s-resource-naming.md` | 契約文檔 |

---

## 🤖 Phase 22: OpenClaw + Nemotron 協作 (ADR-044)

> **新增**: 2026-03-31 (首席架構師批准)
> **目的**: 在同一 Telegram 卡片顯示 OpenClaw 仲裁 + Nemotron 執行方案

### 架構分工

```
OpenClaw = 仲裁者 (Arbitrator) - 決定「為什麼」和「風險等級」
Nemotron = 執行者 (Executor) - 決定「怎麼做」和「具體指令」
```

| 角色 | OpenClaw | Nemotron |
|------|----------|----------|
| **任務** | Root Cause Analysis | Tool Calling |
| **輸出** | 風險等級 + 責任團隊 | kubectl 指令 + 驗證 |
| **模型** | Ollama/Gemini | Nemotron-mini |
| **信心度** | 0-100% | ✅/❌ 驗證狀態 |

### 觸發條件

| 風險等級 | OpenClaw | Nemotron | 原因 |
|----------|----------|----------|------|
| LOW | ✅ | ❌ | 低風險不需 Tool 驗證 |
| MEDIUM | ✅ | ✅ | 需驗證操作可行性 |
| HIGH | ✅ | ✅ | 高風險雙重驗證 |
| CRITICAL | ✅ | ✅ + HITL | 必須人工介入 |

### 核心方法

```python
# apps/api/src/services/openclaw.py
async def generate_incident_proposal_with_tools(
    self,
    incident_id: str,
    severity: str,
    signals: list[dict],
    affected_services: list[str],
) -> tuple[dict | None, str, bool]:
    """
    Phase 22: OpenClaw + Nemotron 協作

    Returns:
        proposal_dict 新增:
        - nemotron_enabled: bool
        - nemotron_tools: list[dict]
        - nemotron_validation: str
    """
```

### Telegram 訊息格式

```
🤖 <b>OpenClaw 仲裁</b>
├ 📊 信心: 🟢 85%
├ 👥 責任: SRE Team
└ 💡 原因: Pod OOM 觸發重啟
━━━━━━━━━━━━━━━━━━━
🔧 <b>Nemotron 執行方案</b>
  ✅ restart_deployment: awoooi-api
  ✅ scale_deployment: replicas=3
└ 驗證: ✅ 驗證通過
```

### Feature Flag

```bash
# 環境變數控制
ENABLE_NEMOTRON_COLLABORATION=true   # 啟用協作
NEMOTRON_TIMEOUT_SECONDS=45          # 超時設定
NEMOTRON_ASYNC_UPDATE=true           # 異步更新模式
```

### 相關文件

- `docs/adr/ADR-044-openclaw-nemotron-collaboration.md`
- `memory/project_phase22_nemotron_collab.md`

---

## 參考文檔

- `apps/api/src/services/incident_engine.py`: 聚合引擎
- `apps/api/src/services/multi_sig_redis.py`: 分散式狀態
- `apps/api/src/workers/signal_worker.py`: Event Bus 消費者
- `apps/api/src/plugins/mcp/mcp_bridge.py`: MCP Bridge
- `memory/project_phase13_enterprise_aiops.md`: Phase 13 規劃
- Phase 6.0-6.3: 認知覺醒計畫
