# ADR-105: 推翻 A2 鐵律 — DIAGNOSE primary 改 Ollama 本地優先

**狀態**: 已廢止（Superseded by ADR-110, 2026-05-03）
**Status**: Accepted (2026-04-29)
**Deciders**: ogt（首席架構師）+ Claude Code
**Date**: 2026-04-29
**Supersedes**: A2 (2026-04-27 INC-20260425) DIAGNOSE 永久排除 Ollama

---

## Context

### A2 鐵律的歷史背景（2026-04-27 INC-20260425）

INC-20260425 事件：NIM timeout 後 fallback 到 Ollama deepseek-r1:14b，造成二次 timeout 238s（CPU-only）。

統帥批准 A2 補丁：
- `_intent_provider_overrides[DIAGNOSE] = OPENCLAW_NEMO`（強制走 188 NIM）
- `_diagnose_fallback_chain` = `[OPENCLAW_NEMO, GEMINI, CLAUDE]`（**永久排除 Ollama**）
- 6 個 regression test 守門「Ollama 不在 DIAGNOSE chain」

### 2 個月後的事實基礎變化

#### 新事實 1：Ollama 111 已升級為 GPU

**舊（A2 假設）**：CPU-only deepseek-r1:14b @ 238s 不可用
**新（2026-04-29 實測）**：M1 Pro Apple Silicon GPU + qwen2.5:7b-instruct
- VRAM 8.2GB 全載入，ctx 32k
- `curl /api/generate` hi prompt → **0.54s 完成**
- prod 已實際 load 此 model（驗證自 `curl http://192.168.0.111:11434/api/ps`）

#### 新事實 2：A2 的雲端 fallback 全死

**2026-04-29 prod log 證據**：
```
openclaw_nemo  → 500 Internal Server Error（http://192.168.0.188:8088/api/v1/analyze/incident）
gemini         → 429 Too Many Requests（每日配額 1000 用爆）
claude         → 404 Not Found（/v1/messages，model claude-3-haiku-20240307 過期）
```

A2 chain `[NEMO, GEMINI, CLAUDE]` 三條全部不可用 → 100% incident `llm_failed` → AI 自動修復永遠不啟動。

#### 新事實 3：統帥鐵律明確衝突

- `feedback_ai_autonomous_direction.md`: 以本地免費 LLM 為主
- `feedback_ollama_111_only.md`: Ollama 唯一主機 = 111
- 統帥 2026-04-29 親口：「主要優先用 111 主機的 Ollama」+「就算 Ollama 慢還是可以等」

A2 鐵律與「本地優先」鐵律衝突。後者優先（範圍更廣、來源更高）。

---

## Decision

**推翻 A2 鐵律**：DIAGNOSE primary 從 OPENCLAW_NEMO 改為 OLLAMA。

### 配套修改（4 處）

#### 1. `ai_router.py:_intent_provider_overrides`
```python
IntentType.DIAGNOSE: AIProviderEnum.OLLAMA,  # 推翻 A2
```

#### 2. `ai_router.py:_diagnose_fallback_chain`
```python
self._diagnose_fallback_chain = [
    (AIProviderEnum.OLLAMA, self._ollama_default),       # 主：本地免費（推翻 A2）
    (AIProviderEnum.OPENCLAW_NEMO, self._openclaw_nemo_default),  # fallback 1
    (AIProviderEnum.GEMINI, self._gemini_default),       # fallback 2
    (AIProviderEnum.CLAUDE, self._claude_default),       # fallback 3
]
```

#### 3. `openclaw.py` 注入 `task_type="diagnose"`
critic 揭示的真根因：webhooks alert_context 沒注入 `task_type` →
`ai_providers/ollama.py:77` 讀不到 → 走 `OPENCLAW_TIMEOUT=30s`（不夠 qwen2.5:7b 推理）。

修法：
```python
exec_context = dict(alert_context) if alert_context else {}
if decision.intent == IntentType.DIAGNOSE:
    exec_context["task_type"] = "diagnose"
```
讓 Ollama 用 `OLLAMA_DIAGNOSE_TIMEOUT_SECONDS=200s`。

#### 4. 6 個 regression test 同步更新

| Test 檔案 | 舊斷言 | 新斷言 |
|----------|-------|-------|
| `test_p0_diagnose_routing.py::test_diagnose_override_is_openclaw_nemo` | DIAGNOSE = OPENCLAW_NEMO | `test_diagnose_override_is_ollama` → DIAGNOSE = OLLAMA |
| `test_ai_router_diagnose_fallback.py::test_diagnose_fallback_chain_no_ollama` | OLLAMA 不在 chain | `test_diagnose_fallback_chain_ollama_primary` → OLLAMA chain[0] |
| 其他 4 個守門 test | (排除 Ollama 系列) | (Ollama primary 系列) |

每個 test docstring 記載歷史脈絡 + 推翻原因，便於後續維護者理解。

### 不動的部分（避免 blast radius）

- `_full_fallback_chain`: 不動（避免影響 RESTART/SCALE/CONFIG/DELETE）
- `_tool_calling_fallback_chain`: 不動（NEMOTRON 仍主推理 tool_call）
- `_intent_provider_overrides[DELETE]`: 仍是 CLAUDE（CRITICAL 風險強制最強模型）
- `complexity_map`: 4/5 寫死 gemini/claude（critic M2 留待後續 ADR）

---

## Consequences

### 正面

1. **AI 自動修復鏈路恢復** — DIAGNOSE 主推理走本地 Ollama，雲端只當 fallback；100% `llm_failed` 應降至 < 30%
2. **遵守統帥本地優先鐵律** — 對齊 `feedback_ai_autonomous_direction.md` + `feedback_ollama_111_only.md`
3. **節省雲端成本** — Gemini/Claude 配額不再被頻繁觸發燒光
4. **隱私加強** — incident evidence 不再頻繁送雲端

### 負面

1. **DIAGNOSE 延遲變長** — 真實 incident prompt 含 evidence + RAG context，估 5-30s（vs OPENCLAW_NEMO 2-27s）
   - 緩解：`OLLAMA_DIAGNOSE_TIMEOUT_SECONDS=200s` 足夠等
   - 統帥指令「即使慢也可以等」
2. **Ollama 健康度成單點** — 主推理掛掉 → fallback 雲端死亡 chain
   - 緩解：`ollama_failover_manager` 偵測 111 不健康自動切 188 CPU 備援
   - 監控：`OllamaInstanceDown` PrometheusRule 已部署（鐵證 3）
3. **INC-20260425 教訓再現風險** — 若 prod model 不知為何切回 deepseek-r1:14b（CPU 跑） → 238s timeout 重演
   - 緩解：`models.json:21` 暫保留 deepseek-r1:14b（critic C1 警示，留待 ADR-106 處理）
   - 防線：CB（circuit breaker）對 Ollama 失敗 5 次後 OPEN 60s

### 已知債（後續 ADR）

- **ADR-106 (待寫)**：`models.json` 對齊 prod 實際 load model（qwen2.5:7b-instruct）
- **ADR-107 (待寫)**：`complexity_map` 4/5 寫死 gemini/claude 改為動態路由
- **ADR-108 (待寫)**：OpenClaw 188 服務 500 根因 + 修復策略
- **ADR-109 (待寫)**：Claude API endpoint 過期（model `claude-3-haiku-20240307` 升 `claude-haiku-4-5`）

---

## Validation

### 部署前

- ✅ 1635 unit tests 全綠
- ✅ 6 個 regression test 反映新鐵律
- ✅ critic + onboarder 兩位 agent 全景審查通過

### 部署後（等 CD #1119 4115ddde 通過）

- [ ] prod pod image tag 更新到 fb0c72db
- [ ] 新 incident 路由 log `provider=ollama` 而非 `provider=openclaw_nemo`
- [ ] `llm_failed` 比率從 100% 降至 < 30%
- [ ] Ollama 真實 latency p95（含 evidence + RAG）< 60s
- [ ] `ollama_failover_manager` 對 OLLAMA primary 行為正確（不誤切 188 CPU）

---

## Rollback

env 切換（不需 redeploy）：
```bash
# 暫時關閉本地優先，回到 A2 鐵律
kubectl -n awoooi-prod set env deployment/awoooi-api \
    AI_FALLBACK_ORDER='["openclaw_nemo","gemini","claude"]'
```

或修改 commit `fb0c72db` 推 revert：
```bash
git revert fb0c72db
git push gitea main
```

---

## References

- A2 原 commit: 2026-04-27 ai_router v4.4
- INC-20260425 教訓記錄: `feedback_auto_execute_pattern_bug.md`
- 推翻 commit: `fb0c72db` (2026-04-29)
- CD 阻塞修復: `4115ddde` (setup_test_schema.sql 補欄位)
- 統帥鐵律: `feedback_ai_autonomous_direction.md` / `feedback_ollama_111_only.md`
- critic PR review: 已驗證實測 0.54s + complexity_map 4/5 留債
- onboarder LLM 路徑全景審查: 確認 7 處違反本地優先鐵律
