diff --git a/docs/adr/ADR-027-primary-ollama-on-gcp.md b/docs/adr/ADR-027-primary-ollama-on-gcp.md
new file mode 100644
index 0000000..c589a2b
--- /dev/null
+++ b/docs/adr/ADR-027-primary-ollama-on-gcp.md
@@ -0,0 +1,114 @@
+# ADR-027:Primary Ollama 遷移至 GCP 高效能主機
+
+- **Status**: Accepted
+- **Date**: 2026-05-03
+- **Decision Maker**: 統帥
+- **Author**: Antigravity
+
+## Context
+
+為了提升 AI 處理速度與穩定性,並減輕本地 188 主機的負載,現已啟用一台位於 GCP 的高效能主機作為 Primary Ollama 伺服器。
+原本地 188 或 111 的 Ollama 轉為 Fallback 節點。
+
+## Decision
+
+**1. 高效能主機配置**
+- **Public IP**: `34.21.145.224`
+- **服務端口**: `11434`
+- **主要模型**:
+ - `qwen2.5-coder:7b` (程式碼修復與開發)
+ - `hermes3:latest` (Llama 3 級別,用於 Hermes 邏輯)
+ - `bge-m3:latest` (Embedding 專用)
+
+**2. 環境變數對應**
+- `OLLAMA_HOST_PRIMARY`: `http://34.21.145.224:11434`
+- `OLLAMA_HOST_FALLBACK`: `http://192.168.0.111:11434` (或 188 本地節點)
+
+**3. 資源分工**
+- **Primary (GCP)**: 承載 90% 以上的 LLM 推理與 Embedding 請求。
+- **Fallback (Local)**: 當 GCP 連線超時或故障時,自動切換至本地節點( Hermes Rule-based 或本地 Ollama)。
+
+## Alternatives Considered
+
+- **維持全本地**: 188 主機 Load 較高,且 GPU 資源競爭激烈。
+- **全雲端 API (OpenAI/Gemini)**: 成本較高,且無法控制模型版本與延遲。
+
+## Consequences
+
+- **優點**: 推理速度提升,系統負載均衡,具備異地備援能力。
+- **缺點**: 依賴外部網路連線,需注意 GCP 出口流量成本與安全性(目前採 IP Allowlist 保護)。
+
+## Verification
+
+- `curl http://34.21.145.224:11434/api/tags` 驗證模型列表正常返回。
+- 測試 `qwen2.5-coder:7b` 與 `bge-m3:latest` 回應正常。
+
+---
+
+## 附錄(2026-05-03 戰役 v5.0 補述)
+
+> **補述背景**:Operation Ollama-First v5.0 戰役 Phase 0/1/2/3 完成後,本附錄校正本 ADR 的主機架構與 fallback 鏈描述,並廢止「188 Ollama」概念。完整治理規則改由 ADR-028 / ADR-029 承接。
+
+### 附錄 A:三主機架構(取代原 GCP / 111 二段描述)
+
+戰役 v5.0 啟用第二台 GCP 高效能主機後,主機級聯升級為三層:
+
+| 角色 | 公網 IP / 主機 | 儲存 | 規格 / 用途 |
+|---|---|---|---|
+| **Primary** | `34.143.170.20:11434` | SSD | 9× 加載 / 2× 推理(v5.0 戰役新主機)|
+| **Secondary** | `34.21.145.224:11434` | SSD | 同等效能備援;本 ADR 原 Primary 降為次主 |
+| **Fallback** | `192.168.0.111:11434`(HDD)| HDD | 統帥 Mac 上的 Ollama.app,最後一道本地防線 |
+
+**程式契約**:所有 LLM 呼叫必須走 `services/ollama_service.resolve_ollama_host()`(Phase 2 A6 落地),按 Primary → Secondary → Fallback 順序探測:
+- HTTP probe `GET /api/version`,2s timeout(取代原純 TCP 探測,B3 修補)
+- 失敗主機 `mark_unhealthy()` 30s(B4),TTL 內直接跳下一台
+- 寫死 IP 已全面消除(`services/aider_heal_executor.py:48-49` 與 `services/code_review_pipeline_service.py:218-225` 兩處 N2/N3 修補)
+
+詳見 `docs/phase2_deploy_verify_20260503.md`。
+
+### 附錄 B:4 條獨立 fallback 鏈(不存在「線性 LLM 鏈」)
+
+戰役 v5.0 audit 證實系統實際有 4 條(嚴格說 5 條)獨立 fallback 鏈,語意各異不可合併:
+
+1. **Ollama host 級**(基礎設施層)
+ `gcp_ollama` (Primary) → `ollama_secondary` → `ollama_111`
+ 實作:`services/ollama_service.resolve_ollama_host()`
+
+2. **Hermes 競價 / 意圖分類**(戰術層)
+ Ollama → 規則引擎兜底
+ 實作:`services/hermes_analyst_service.py`(ADR-004)
+
+3. **NemoTron 派遣**(行動層)
+ NIM `meta/llama-3.1-8b` → Hermes 規則引擎
+ 實作:`services/nemoton_dispatcher_service.py`(ADR-004)
+
+4. **OpenClaw Q&A**(戰略層 / Telegram)
+ Gemini 2.5 Flash → NIM `deepseek-v3.2` → 字面 fallback
+ Phase 3 A7 已切:Hermes qwen3:14b → 信心低升 Gemini → NIM
+ 實作:`services/openclaw_strategist_service.py` / `routes/openclaw_bot_routes.py:6784-6843`
+
+5. **MCP 即時情報**(外部資訊層)
+ Gemini Grounding L1 → Gemini Grounding L2 → Ollama L3 → 靜態字串
+ 實作:`services/mcp_collector_service.py:163-214`
+
+### 附錄 C:廢止項
+
+- **「188 Ollama」概念全面廢止**
+ 188 主機(`192.168.0.188`)僅作為以下用途:
+ - SSH AutoHeal target(`docker restart` / log scan,ADR-013)
+ - momo-pro Docker Compose 運行環境(ADR-008)
+ - momo-postgres / momo-db / momo-scheduler / momo-telegram-bot 容器宿主
+ - **不安裝、不運行 Ollama**(Ollama 全部走 GCP Primary / Secondary 或統帥 Mac 111)
+ - 任何「188 Ollama」字樣的舊文件視為過時,以本附錄為準
+- 原 `OLLAMA_HOST_FALLBACK = http://192.168.0.111:11434` env var 仍保留,但解析路徑改由 `resolve_ollama_host()` 統一管控,不再被任何呼叫點寫死引用
+
+### 附錄 D:治理規則升級指引
+
+戰役 v5.0 後,所有 LLM 治理決策改以以下文件為準:
+- 路由白名單 / caller 清單 / Gemini 鎖定場景 → **ADR-028**
+- Hermes 主塔 vs OpenClaw 副塔分工 → **ADR-029**
+- 部署驗證劇本 → `docs/phase2_deploy_verify_20260503.md`
+- DB 觀測層 schema → `migrations/024_create_ai_calls_table.sql` ~ `026_add_embedding_signature.sql`
+- Logger 程式契約 → `services/ai_call_logger.py`
+
+本 ADR-027 保留作為戰役起點的歷史紀錄,不再作為主要參照入口。
diff --git a/docs/adr/ADR-028-llm-routing-unified-principles.md b/docs/adr/ADR-028-llm-routing-unified-principles.md
new file mode 100644
index 0000000..6931811
--- /dev/null
+++ b/docs/adr/ADR-028-llm-routing-unified-principles.md
@@ -0,0 +1,270 @@
+# ADR-028: LLM 路由統一準則 — Ollama-First 五大支柱
+
+- **Status**: Accepted
+- **Date**: 2026-05-03
+- **Decision Maker**: 統帥
+- **Author**: Operation Ollama-First v5.0(Codex / A12 planner)
+- **Supersedes**: 無(補述 ADR-027,非取代)
+- **Related**: ADR-002(pgvector 唯一向量庫)、ADR-003(Hermes embedding 本地化)、ADR-004(NemoTron 配額耗盡 fallback)、ADR-008(部署實機驗證)、ADR-013(AIOps AutoHeal)、ADR-018(四 Agent 控制面)、ADR-027(Primary Ollama on GCP)
+
+---
+
+## Context
+
+ADR-027 在 2026-05-03 啟用 GCP 高效能主機作為 Primary Ollama,但戰役 v5.0 Phase 0 的 onboarder audit(`docs/phase0_audit_report_20260503.md`)揭露三項治理斷層:
+
+1. **34 個 LLM 呼叫點分散治理失能**
+ - audit 完整盤點 `services/` 與 `routes/` 共 34 處 LLM 呼叫,分散在 9 個 service / 4 個 route 檔。
+ - `AIGenerationHistory` 結構化紀錄覆蓋率僅 4/34 = 11.8%,其餘 88% 完全沒結構化遙測。
+ - 兩處「寫死 111」的呼叫點(`services/aider_heal_executor.py:48-49`、`services/code_review_pipeline_service.py:218-225`)違反 ADR-027 的 Primary/Fallback 原則。
+
+2. **4 條獨立 fallback 鏈缺一致性**
+ - 不存在「線性 LLM 鏈」假設:Ollama 主機級、Hermes 競價、NemoTron 派遣、OpenClaw Q&A、MCP 即時情報各自為政。
+ - 各鏈 timeout / 探測 / 降級語意不一,故障時行為不可預測。
+
+3. **provider/caller 命名漂移**
+ - 戰役清單原列 26 個呼叫點,audit 補出 8 個遺漏;`openclaw_qa_nim` 此類 fallback 命名靠執行期動態組成(`services/openclaw_strategist_service.py:737`),缺集中字典。
+ - DB 端在 Phase 1 已加 provider CHECK constraint(`migrations/024_create_ai_calls_table.sql:88-91`),但 caller 仍是自由字串(critic-A11 於 phase1_final_critic_signoff H5 點名)。
+
+ADR-027 解決了「主機去哪裡」,但未統一「誰能呼叫」「呼叫去哪一台」「為何選這個」。本 ADR 是 ADR-027 的決策補述,把 Phase 0/1/2/3 戰役落地的成果固化為憲法級準則,供未來 Phase 4-12 與所有新 Agent 共同遵循。
+
+---
+
+## Decision — 五大支柱
+
+### 支柱 1:三主機級聯(Triple-Host Cascade)
+
+取代 ADR-027 原本「GCP / 111」二段架構。
+
+| 角色 | 主機 | 儲存 | 用途 |
+|---|---|---|---|
+| Primary | `34.143.170.20` | SSD | 9× 加載 / 2× 推理(v5.0 戰役新主機)|
+| Secondary | `34.21.145.224` | SSD | 同等效能備援,ADR-027 原 Primary 降為次主 |
+| Fallback | `192.168.0.111` | HDD | 最後一道本地防線,統帥 Mac 上的 Ollama.app |
+
+`services/ollama_service.resolve_ollama_host()`(Phase 2 A6 落地)按 Primary → Secondary → Fallback 順序探測:
+- HTTP probe `GET /api/version`,2s timeout(Phase 2 B3 修補)
+- 失敗主機標 `mark_unhealthy()` 30s(Phase 2 B4),TTL 內直接跳下一台
+- 任何呼叫點都必須走 `resolve_ollama_host()`,禁止寫死任何 IP
+
+### 支柱 2:Ollama 優先(Ollama-First)
+
+預設所有 LLM 推理都走 Ollama(三主機級聯),只有支柱 4 列的 7 個鎖定場景才允許走 Gemini。
+
+**判定原則**:
+- 高頻、低延遲敏感、戰術層 → Ollama
+- 低頻、戰略洞察、需 Grounding/長 context、HITL pre-fetch → Gemini
+
+**成本影響**:Ollama 三主機都是 self-hosted,邊際 token 成本 = 0;Gemini 月支出受 7 個鎖定場景上限約束。
+
+### 支柱 3:雙塔分工(Twin-Tower)
+
+詳見 ADR-029。簡述:
+- **Hermes 主入口(L1 戰術塔)**:高頻 Ollama-only,所有日常意圖分類、競價分析、KPI 計算、Q&A 第一響應走 Hermes。
+- **OpenClaw 副引擎(L3 戰略塔)**:低頻鎖定 5 個 Gemini 場景,月/年報、Code Review、EA HITL、Meta 自審等戰略產出。
+
+### 支柱 4:Gemini 鎖定 7 個場景
+
+只有以下 7 個 caller 允許呼叫 `gemini` provider,其餘任何 caller 走 Gemini 必經 ADR:
+
+| # | Caller | 檔案位置 | 用途 | 模型 |
+|---|---|---|---|---|
+| 1 | `mcp_l1_grounding` | `services/mcp_collector_service.py:32` (LOCKED-GEMINI 註解) | MCP L1 Grounding(即時情報)| `gemini-2.0-flash` |
+| 2 | `mcp_l3_ollama` 兜底前的 L2 | `services/mcp_collector_service.py:33` MCP_FALLBACK_MODEL | MCP L2 Grounding | `gemini-1.5-flash` |
+| 3 | `ppt_gemini` | `routes/openclaw_bot_routes.py:98` (LOCKED-GEMINI) + `_call_gemini` 約 line 2464 | PPT 簡報深度分析 | `gemini-2.0-flash` |
+| 4 | `openclaw_weekly` / `openclaw_monthly` | `services/openclaw_strategist_service.py:1102` weekly + `:1628` monthly + `:40` STRATEGY_MODEL (LOCKED-GEMINI) | 週/月報敘事(**註**:annual 報告尚未實作,目前 OpenClaw 僅 weekly/monthly/daily/meta)| `gemini-2.5-flash` |
+| 5 | `code_review_openclaw` | `services/code_review_pipeline_service.py:46` REVIEW_MODEL (LOCKED-GEMINI) | Code Review 高階評估 | `gemini-2.5-flash` |
+| 6 | `ea_engine` (HITL) | `services/elephant_alpha_orchestrator.py:88` AgentCapability.openclaw model (LOCKED-GEMINI);`hermes_ea_prefetch` 走 Hermes Ollama 不在此鎖定 | EA HITL escalation(ADR-021)| `gemini-2.0-flash` |
+| 7 | `openclaw_qa` 升級分支 | `services/openclaw_strategist_service.py:56` `generate_strategy_response` (Phase 3 A7 已加 Ollama-first feature flag,flag=true 時 Ollama 失敗才升級 Gemini) | 複雜 SKU 推理(Hermes/qwen3 品質低時升級)| `gemini-2.5-flash` |
+
+新增 Gemini caller 必須走 ADR review;DB CHECK constraint 將於 Phase 5 後補(critic-A11 H5)。
+
+### 支柱 5:可觀測性先行(Observability-First)
+
+所有 LLM 呼叫必須經 `services/ai_call_logger.log_ai_call()` 雙寫 `ai_calls` 表(Phase 1 A4 落地)。
+
+**遙測契約**:
+- caller / provider / model 必填(provider 由 DB CHECK 約束在白名單內)
+- input_tokens / output_tokens / duration_ms / status / cost_usd 必填
+- request_id 可選但 fallback 鏈必須串接(如 `code_review_hermes` → `code_review_openclaw` → `code_review_elephant` 三鏈共用)
+- `meta JSONB` 不得超 8192 octet,`error TEXT` 不得超 4096 octet(migration 024 H2)
+- chat_id 等 PII 必經 sha256 截 12 字後存入(Phase 2 後補丁,critic H6)
+
+**自動化護欄**:
+- `ai_call_budgets` 7 個 provider 月度預算(gemini/claude/nim/nim_via_elephant/openrouter/gcp_ollama/ollama_111)+ 3 條全供應商總額(daily/weekly/monthly)= 10 筆種子預算(migration 025)
+- 每日 23:55 token report 推 Telegram(A5 落地)
+- AIGenerationHistory 覆蓋率必須 ≥ 90%(v5.0 戰役 KPI),Phase 5 報表追蹤
+
+---
+
+## Provider 白名單(DB CHECK 約束)
+
+`migrations/024_create_ai_calls_table.sql:51-58` `chk_ai_calls_provider` 鎖定以下 8 值:
+
+| Provider | 主機 | 計費 | 用途 |
+|---|---|---|---|
+| `gcp_ollama` | Primary `34.143.170.20:11434` | 0(self-hosted)| 戰役 v5.0 主主機 |
+| `ollama_secondary` | Secondary `34.21.145.224:11434` | 0 | 同等效能備援(架構保留,logger 端 url-based 推斷待 Phase 7+) |
+| `ollama_111` | Fallback `192.168.0.111:11434` | 0 | 最後一道防線 |
+| `gemini` | Google AI API | metered | 鎖定支柱 4 的 7 個場景 |
+| `claude` | Anthropic API | metered | Phase 7 Frontier 升級保留:Code Review L0 (Opus 4.7) + EA HITL (Sonnet 4.6);budgets 種子 $10/月已預設 |
+| `nim` | NVIDIA NIM `https://integrate.api.nvidia.com/v1` | 80 calls/day 配額 | NemoTron 派遣(ADR-004)+ Code Review fallback |
+| `nim_via_elephant` | NIM via `services/elephant_service.py` | 同 NIM 配額 | Code Review ElephantAlpha 49B 鏈 |
+| `openrouter` | OpenRouter(保留) | metered | PPT deepseek-v3.2 鏈 + 預留 Phase 9 多供應商實驗 |
+
+> **三層一致性備忘**(critic-A11 B4 修補):DB CHECK = 8 個,ADR-028 = 8 個,`services/token_report_service.py` `_PROVIDER_DISPLAY` 後續需補 `ollama_secondary`(H5 待修,列為 Phase 7 整合任務)。
+
+---
+
+## Caller 白名單(程式碼集中字典)
+
+戰役 v5.0 Phase 1 A4 logger 接入後,固定字串集中於 `services/ai_call_logger.py` 與各 caller。常見 13 + 5 NIM 變體:
+
+```
+hermes_intent / hermes_analyst / hermes_rule_engine
+code_review_hermes / code_review_openclaw / code_review_elephant
+openclaw_qa / openclaw_qa_nim
+openclaw_weekly / openclaw_weekly_nim
+openclaw_daily / openclaw_daily_nim
+openclaw_monthly / openclaw_monthly_nim
+openclaw_meta / openclaw_meta_nim
+nemotron_dispatch
+openclaw_bot_main / openclaw_bot_gemini / openclaw_bot_nim
+embedding_worker / embedding_realtime
+mcp_collector_l1 / mcp_collector_l2 / mcp_collector_l3
+ppt_generator / ppt_generator_ollama / ppt_generator_nim
+ea_hitl_prefetch / ea_autonomous_engine
+aider_heal
+sales_copy / trend_match / trend_search / product_insights / trend_keywords
+telegram_copy / bot_api_copy / trend_crawler / ai_provider_generic
+```
+
+新增 caller 必須:
+1. 加進 `services/ai_call_logger.py` `_KNOWN_CALLERS` 字典
+2. 更新本 ADR 表格
+3. 通過 critic 審查
+4. Phase 5 後 DB CHECK constraint 將以格式約束(`^[a-z][a-z0-9_]{2,63}$`)NOT VALID 補上(critic-A11 H5 建議)
+
+---
+
+## 鎖定 Gemini 7 個場景(與支柱 4 對應,含理由)
+
+| 場景 | 為何不能走 Ollama |
+|---|---|
+| MCP L1/L2 Grounding | Gemini Grounding 是即時 web search 唯一供應商,Ollama 無此能力(Tavily/Exa 走 Phase 10 不同路徑)|
+| PPT 圖檢查 / 簡報分析 | Gemini 多模態 vision 能力遠超 Ollama 本地模型 |
+| 週/月/年報敘事 | 商業敘事品質要求高,Gemini 2.5 Flash vs qwen3:14b 估差 10-20%(phase0_research_report Section 1)|
+| Code Review 高階評估 | OpenClaw 級審查需 Gemini 2.5 Flash 的程式理解力,本地模型不足 |
+| EA HITL pre-fetch | escalation 路徑 5s timeout 內必須拿到結構化 + 高品質回應,ADR-021 已定 |
+| 複雜 SKU 推理 | Hermes/qwen3:14b 信心 < threshold 時才升級 Gemini,繁中商業情境短板(TMMLU+ 論文)|
+
+---
+
+## Alternatives Considered
+
+| 方案 | 不採用原因 |
+|---|---|
+| **A. 單一供應商(全 Gemini 或全 NIM)** | 配額硬限(NIM 80 calls/day)+ 月成本不可控;雲端 API 出口流量風險;違反 FinOps 視角 |
+| **B. 全本地(不留 Gemini)** | MCP Grounding / vision / 戰略敘事品質本地模型補不齊;繁中短板無解(phase0_research Section 1.4)|
+| **C. 多供應商完全互通(任意 caller 任意 provider)** | 命名漂移無法治理;caller-provider 對應靠程式碼隱式約定,無 DB 護欄;token 報表 GROUP BY 失準 |
+| **D. 線性 LLM 鏈(一條 fallback 鏈打天下)** | audit 已證明系統有 4 條獨立鏈(Ollama host / Hermes / NemoTron / OpenClaw Q&A / MCP),語意各異不可合併 |
+| **E. 引入 LangChain / LiteLLM 統一抽象層** | 黑盒、難審計、增加依賴;既有 `ai_call_logger` + `resolve_ollama_host` 已具備統一語意,無需第三方 |
+
+---
+
+## Consequences
+
+### 正面
+
+1. **成本可控**:Gemini 鎖定 7 場景,月支出有上限;其餘 ~27 個 caller 全走 Ollama 邊際成本 = 0。
+2. **遙測 100%**:A4 logger 接入後,`ai_calls` 覆蓋率從 11.8% 拉升至接近 100%,token / 成本 / 延遲 / 失敗率每日 23:55 自動入袋。
+3. **fallback 行為可預測**:三主機級聯 + mark_unhealthy + HTTP probe 取代純 TCP 探測,process 卡死也能 5s 內切換。
+4. **provider 命名治理**:DB CHECK constraint 鎖死 8 個 provider;caller 字典集中於程式碼,新增需 ADR。
+5. **未來 Phase 4-12 有依據**:所有新 Agent / 新 caller 直接套用本 ADR 的五大支柱與白名單。
+
+### 負面
+
+1. **新 caller 引入有 ADR 摩擦**:每個新 LLM 呼叫點都要 update 本 ADR,但這是治理代價而非缺點。
+2. **DB CHECK 變更需 migration**:provider 白名單擴增(如 Phase 9 加 `claude`)需新 migration + 滾動部署。
+3. **Logger 額外延遲**:每個 LLM 呼叫多一層 fire-and-forget 寫入(測試顯示 < 5ms),但獨立 thread + dedicated session pool 可控。
+
+### 風險與緩解
+
+| 風險 | 機率 | 緩解 |
+|---|---|---|
+| Logger 失敗連鎖讓主流程崩 | 低 | `ai_call_logger` kill-switch 連續 10 次失敗自動關(Phase 1 已測試 52/52 pass)|
+| caller typo 污染 token 報表 | 中 | Phase 5 後加格式 CHECK constraint;review 時 grep 比對白名單 |
+| Gemini 配額耗盡(7 場景同時爆)| 低 | NIM `nvidia/llama-3.3-nemotron-super-49b-v1.5` 鏈 fallback;ADR-004 已定 |
+| Primary 主機長期掛 | 低 | mark_unhealthy 30s + 三層級聯;最壞情況走 111 本地 |
+
+---
+
+## Verification(如何驗證已落地)
+
+### V1:DB 層
+```sql
+-- provider CHECK 鎖定
+SELECT pg_get_constraintdef(oid) FROM pg_constraint
+WHERE conname = 'chk_ai_calls_provider';
+-- 期望:CHECK (provider IN ('gcp_ollama','ollama_secondary','ollama_111','gemini','nim','nim_via_elephant','openrouter'))
+
+-- 預算種子 10 筆
+SELECT period, provider, budget_usd FROM ai_call_budgets ORDER BY period, provider NULLS FIRST;
+
+-- 24h caller 分布(戰役 v5.0 上線後應 ≥ 13 個 distinct caller)
+SELECT caller, COUNT(*) FROM ai_calls
+WHERE called_at >= NOW() - INTERVAL '24h'
+GROUP BY caller ORDER BY 2 DESC;
+```
+
+### V2:程式碼層
+```bash
+# 不應有寫死 IP
+grep -rn "192.168.0.111" services/ routes/ | grep -v "OLLAMA_HOST_FALLBACK\|resolve_ollama_host\|test_"
+grep -rn "34.143.170.20\|34.21.145.224" services/ routes/ | grep -v "OLLAMA_HOST_PRIMARY\|test_"
+
+# 所有 LLM 呼叫應走 logger
+grep -rn "ollama_service.generate\|google.generativeai\|openai.ChatCompletion" services/ routes/
+# 每個 hit 上方應有 with log_ai_call(...) 或 @log_ai_call_decorator
+```
+
+### V3:每日 token report(每日 23:55 入 Telegram)
+- Section 1 `ollama_pct` ≥ 80%(戰役 KPI)
+- Section 5 「今日 Ollama Tokens vs 7 日均」應穩定,不應突然降為 0(M7 的 openclaw_bot_main 修復後)
+- ai_insights `insight_type='daily_token_report'` 每日 1 筆
+
+---
+
+## Migration Plan
+
+| Phase | 項目 | 狀態 | 文件 |
+|---|---|---|---|
+| 0 | LLM/MCP audit + 替代查證 | ✅ 完成 | `docs/phase0_audit_report_20260503.md` / `docs/phase0_research_report_20260503.md` |
+| 1 | DB schema(024/025/026)+ ai_call_logger + token report | ✅ 完成(52/52 tests pass)| `docs/phase1_db_design_20260503.md` / `docs/phase1_critic_review_20260503.md` / `docs/phase1_final_critic_signoff_20260503.md` |
+| 2 | resolve_ollama_host + HTTP probe + mark_unhealthy + AiderHeal/CodeReview lazy | ✅ 完成(13 + 43 tests pass)| `docs/phase2_deploy_verify_20260503.md` |
+| 3 | OpenClaw Q&A 切 qwen3:14b(feature flag)| ✅ 完成(A7)| 待 Phase 4 黃金集 A/B |
+| 4 | 黃金集 A/B 評測 + Hermes daily 摘要遷移(A8)| 規劃中 | — |
+| 5 | Phase 5 報表上線 + caller CHECK NOT VALID | 規劃中 | — |
+| 6 | 文件對齊(本 ADR + ADR-029 + ADR-027 附錄)| ✅ 完成 | 本檔 |
+| 7-8 | OpenClaw 程式瘦身(A10)| 規劃中 | — |
+| 9 | 預算守門 hard-stop + 多供應商實驗 | 規劃中 | — |
+| 10 | MCP 5 顆 🟢 引入(postgres / omnisearch / filesystem / firecrawl / git)| 規劃中 | — |
+| 11 | RAG 一致性護欄(embedding_signature 回填 + bge-m3 digest 鎖定)| 規劃中 | — |
+| 12 | ai_usage_tracking deprecate + caller 集中 ADR refresh | 規劃中 | — |
+
+---
+
+## References
+
+- ADR-027 Primary Ollama on GCP(戰役起點)
+- ADR-029 Hermes-First 雙塔分工(本 ADR 支柱 3 展開)
+- ADR-004 NemoTron 配額耗盡 fallback(白名單 NIM provider 來源)
+- ADR-013 AIOps AutoHeal(aider_heal_executor 修補上下文)
+- ADR-018 四 Agent 控制面(Hermes/NemoTron/OpenClaw/ElephantAlpha 角色定義)
+- ADR-021 EA HITL pre-fetch(Gemini 場景 6 來源)
+- `docs/phase0_audit_report_20260503.md`(34 呼叫點完整盤點)
+- `docs/phase0_research_report_20260503.md`(Qwen / DeepSeek-R1 / Search API 三項紅綠燈)
+- `docs/phase1_db_design_20260503.md`(DB schema 設計理由)
+- `docs/phase1_final_critic_signoff_20260503.md`(H5/H6 caller / chat_id 護欄缺口)
+- `docs/phase2_deploy_verify_20260503.md`(resolve_ollama_host / mark_unhealthy 落地驗證)
+- 相關 memory:`reference_111_mac_ollama.md`、`reference_env_map.md`、`feedback_docs_vs_reality.md`
diff --git a/docs/adr/ADR-029-hermes-first-twin-tower.md b/docs/adr/ADR-029-hermes-first-twin-tower.md
new file mode 100644
index 0000000..67db052
--- /dev/null
+++ b/docs/adr/ADR-029-hermes-first-twin-tower.md
@@ -0,0 +1,223 @@
+# ADR-029: Hermes-First 雙塔分工
+
+- **Status**: Accepted
+- **Date**: 2026-05-03
+- **Decision Maker**: 統帥
+- **Author**: Operation Ollama-First v5.0(Codex / A12 planner)
+- **Related**: ADR-001(三 Agent 自主學習分工)、ADR-002(pgvector)、ADR-018(四 Agent 控制面)、ADR-019(Telegram Agentic Layer)、ADR-027(Primary Ollama on GCP)、ADR-028(LLM 路由統一準則)
+
+---
+
+## Context
+
+ADR-001 / ADR-018 把 Hermes 定位為「L1 Observer / Embedding」、OpenClaw 定位為「L3 Strategist」,但實作上的權重失衡與成本失衡讓「分工」變成「OpenClaw 全包」。
+
+### 失衡證據(戰役 v5.0 Phase 0 audit)
+
+1. **程式碼體積失衡**:
+ - `services/openclaw_strategist_service.py` ≈ **2677 行**(HEAD 起 1831 行 + Phase 3/4 增量 846 行)
+ - `services/hermes_analyst_service.py` ≈ **607 行**
+ - 比率 **4.4×**,但 Hermes 的呼叫頻率高 OpenClaw **約 100 倍**
+
+2. **成本失衡**:
+ - OpenClaw 月燒 Gemini ≈ **50M tokens**(估算依 phase0 audit caller 流量推算)
+ - Hermes 月燒 Ollama ≈ **30M tokens**,邊際成本 **$0**
+ - 換句話說:Hermes 跑了 60% 流量 = $0;OpenClaw 跑了 40% 流量 = 全部 Gemini 帳單
+
+3. **使用者主入口 = Telegram,但 Telegram NL 走 OpenClaw**:
+ - 統帥每日數十次答題從 Telegram 走 `routes/openclaw_bot_routes.py:6784-6843` 三層 fallback(Ollama → Gemini → NIM)
+ - audit ID #29/30/31 顯示 OpenClaw Bot Q&A 是 Gemini 月支出第二大來源
+ - ADR-019 的 Agent-First Conversation Layer 把所有用戶輸入導向 `openclaw_decide()`,更放大 OpenClaw 流量
+
+4. **戰略 / 戰術層職責混雜**:
+ - 「每日營運摘要」是高頻戰術(每日 09:00),卻走 OpenClaw Gemini → 成本最高頻率最高
+ - 「KPI 計算」是規則引擎可解的純運算,卻丟給 LLM 寫敘事
+ - 「Q&A」涵蓋從「上週業績多少」(戰術)到「下季品類布局建議」(戰略),全部走同條鏈
+
+5. **Phase 3 A7 已局部驗證**:
+ - 戰役 v5.0 Phase 3 已把 OpenClaw Q&A 第一響應切到 `qwen3:14b`(feature flag),保留 Gemini 作 fallback
+ - 該變更顯示「Hermes-tier 模型接戰術 Q&A」是技術可行的
+
+ADR-018 已定四 Agent 角色,但未量化「誰處理高頻流量、誰處理低頻戰略」;本 ADR 是 ADR-018 的成本驅動補述,把 Hermes 從「Observer」升格為「主入口」,OpenClaw 縮回「鎖定戰略場景的副引擎」。
+
+---
+
+## Decision — 雙塔分工(Twin-Tower)
+
+### Hermes 主塔(L1 戰術 / 高頻 / Ollama-only)
+
+- **定位**:所有 Telegram NL / 競價偵測 / 日常摘要 / KPI 計算 / 第一響應 Q&A 的單一入口
+- **資源**:三主機 Ollama 級聯(ADR-028 支柱 1),邊際成本 0
+- **模型**:`hermes3:latest`(意圖分類 / 競價)+ `qwen3:14b`(Q&A 第一響應,Phase 3 已切)
+- **降級**:Ollama 失敗 → 規則引擎兜底(ADR-004 已定)→ 模板化回應,不直接升 Gemini
+- **不可越界**:不寫戰略長文(≤ 200 字)、不做 Web Grounding、不做 vision
+
+### OpenClaw 副塔(L3 戰略 / 低頻 / 鎖定 5 個 Gemini 場景)
+
+- **定位**:產生月/年報敘事、PPT 顧問深度分析、Code Review 高階評估、EA HITL pre-fetch、複雜 SKU 推理
+- **資源**:Gemini 2.5 Flash 主,NIM `llama-3.3-nemotron-super-49b-v1.5` fallback(ADR-004)
+- **觸發頻率**:日報 1×/日 / 週報 1×/週 / 月報 1×/月 / Code Review 部署觸發 / EA HITL escalation 觸發
+- **不可越界**:不接 Telegram 第一響應(一律先過 Hermes,信心 < threshold 才升級)
+
+### 升級條件(Hermes → OpenClaw)
+
+由 Hermes 主塔判定是否需要升級:
+1. **意圖分類為「戰略性問題」**(如「明年品類規劃」「Q3 競品深度分析」)
+2. **複雜 SKU 推理且信心分數 < 0.65**(Hermes self-assessment)
+3. **使用者明確要求「深度報告」**(Telegram menu 點 `cmd:strategy` / `cmd:annual` / `cmd:competitor`)
+4. **EA escalation 事件**(ADR-021 已定)
+
+升級時 OpenClaw 必須拿到 Hermes 已有的 context(意圖 / 信心 / 既有摘要),不重複呼叫 Ollama。
+
+---
+
+## 職責重劃表(戰前 vs 戰後)
+
+| # | 任務 | 戰前 | 戰後 | 對應戰役 task |
+|---|---|---|---|---|
+| 1 | 競價威脅偵測(每 4h)| Hermes Ollama | Hermes Ollama ✅ 維持 | — |
+| 2 | 意圖分類(Telegram NL)| Hermes Ollama | Hermes Ollama ✅ 維持 | — |
+| 3 | 每日營運摘要 | OpenClaw Gemini(每日 1×)| **Hermes 模板 + Gemini 200 字洞察** | **A8** |
+| 4 | KPI 計算(業績 / 庫存 / 達成率)| OpenClaw Gemini | **Hermes 規則引擎**(純運算)| **A8** |
+| 5 | Q&A 第一響應(Telegram)| OpenClaw Gemini → NIM → 字面 fallback | **Hermes qwen3:14b → 信心低升 Gemini** | **A7(已落地)** |
+| 6 | 複雜 SKU 推理 | OpenClaw Gemini | Hermes 信心 < 0.65 → OpenClaw Gemini | A7 條件分支 |
+| 7 | PPT 簡報深度分析 | OpenClaw Gemini | OpenClaw Gemini ✅ 鎖定 | — |
+| 8 | 週/月/年報敘事 | OpenClaw Gemini | OpenClaw Gemini ✅ 鎖定 | — |
+| 9 | Code Review 高階評估 | OpenClaw Gemini | OpenClaw Gemini ✅ 鎖定 | — |
+| 10 | EA HITL pre-fetch | Hermes Ollama(ADR-021)| Hermes Ollama ✅ 維持,escalation 走 OpenClaw Gemini | — |
+| 11 | Meta 自審 | OpenClaw 每 6h | **OpenClaw 每 24h**(降頻 4×)| **A10** |
+| 12 | 規則引擎兜底 | Hermes(ADR-004)| Hermes ✅ 維持 | — |
+
+**淨效果**(critic-A11 B5 修補:算術重核):
+- 任務 3/4/5 從 OpenClaw Gemini 遷移至 Hermes Ollama → 月省 ~9.5M Gemini tokens(A7+A8 試算)
+- 任務 11 降頻(Meta 自審 6h → 12:00)→ 月省 ~2.25M Gemini tokens(A10 實測超預估)
+- 合計月省 ~11.75M tokens(50M → 38.25M = **-23.5%**)
+- Hermes 流量從 ~30M tokens/月 → 預估 ~120M tokens/月(**+400%,成本不變**)
+
+---
+
+## A7 / A8 / A10 落地對照
+
+| Task | 範圍 | 狀態 | 對應檔案 |
+|---|---|---|---|
+| **A7** | OpenClaw Q&A → qwen3:14b(feature flag)| ✅ Phase 3 完成,待 Phase 4 黃金集 A/B 驗證 | `services/openclaw_strategist_service.py` Q&A 入口 |
+| **A8** | Hermes daily 摘要 + KPI 規則引擎 | 規劃中(Phase 4)| `services/hermes_analyst_service.py` 新增 daily_summary / kpi_compute 方法 |
+| **A10** | OpenClaw Meta 自審降頻 + 程式瘦身 -29% | 規劃中(Phase 7-8)| `services/openclaw_strategist_service.py` 拆出戰術層遷移至 Hermes |
+
+---
+
+## 預期效益(量化)
+
+| 指標 | 戰前 | 戰後 | 變化 |
+|---|---|---|---|
+| Gemini 月支出(tokens) | ~50M | ~38.25M | **-23.5%** |
+| OpenClaw 程式碼行數 | 2677(HEAD 1831 + Phase 3/4 增量)| Phase 4 +18 行(A10 保守抽出)| Phase 4 行數目標未達;主戰果 = Meta 降頻 |
+| Hermes 流量(tokens) | ~30M | ~120M | **+400%($0)** |
+| Telegram NL 第一響應延遲 p50 | ~2.5s(Gemini)| ~1.2s(Ollama 本地)| **-52%**(待 ai_calls 實測) |
+| 戰術層 fallback 鏈深度 | 3(Gemini→NIM→字面)| 2(Hermes→規則引擎)| **-33%** |
+| 月成本(Gemini API)| baseline | -23.5% | 戰役 v5.0 KPI |
+
+> 上述數字為 Phase 0 audit 推算,Phase 5 報表上線後以 `ai_calls` 實測值修訂。
+
+---
+
+## Alternatives Considered
+
+| 方案 | 不採用原因 |
+|---|---|
+| **A. 維持 ADR-018 現狀(OpenClaw 全包)** | Gemini 月支出無上限增長;Telegram NL 延遲 p50 ≥ 2.5s 體驗差;Hermes 程式碼長期被冷凍 |
+| **B. OpenClaw 全切 Ollama(廢 Gemini)** | 月/年報品質下降 10-20%(phase0_research Section 1);Code Review / PPT vision 補不齊;繁中商業敘事短板(TMMLU+ 論文)|
+| **C. 把 Hermes 升格 L3、廢 OpenClaw** | OpenClaw 已有 KM 沉澱 / Meta 自審 / 戰略架構,砍掉等於拋棄 1.8% → 80% 觀測能力(ADR-019);ADR-018 四 Agent 控制面被破壞 |
+| **D. 引入第五個 Agent 接戰術層** | 增加心智負擔;Hermes 已是現成 L1 Observer,升格成本最低;統帥 FinOps 視角不偏好新增複雜度 |
+| **E. 全部走 NIM(避開 Gemini 帳單)** | NIM 80 calls/day 配額硬限,月/年報已經會爆;NIM 模型品質 < Gemini 2.5 Flash |
+
+---
+
+## Consequences
+
+### 正面
+
+1. **成本下降 23%**:戰役 v5.0 KPI 第一目標達成。
+2. **Telegram NL 延遲減半**:本地 Ollama 三主機級聯 vs Gemini API round-trip。
+3. **Hermes 體質升級**:從「Observer」升「主入口」,未來 Phase 11 RAG 攔截可直接接 Hermes 流量。
+4. **程式碼瘦身**:A10 採保守抽出(2 個 helper),行數 -25% 目標未達;主戰果為 Meta 降頻(月省 2.25M tokens)。深度瘦身延至 Phase 7+。
+5. **ADR-019 真正落地**:`openclaw_decide()` 第一響應改走 Hermes,agent suggestion shortcut 不再 = OpenClaw 全包。
+
+### 負面
+
+1. **A7 切換有黃金集 A/B 風險**:qwen3:14b 繁中短板若實測差距 > 30%,需走 Plan B(Llama-3-Taiwan-70B 或退回 Gemini 加 prompt cache)。
+2. **A10 重構工程量大**:A10 已執行(Phase 4),採保守抽出避險;4 個報告函數結構差異大,深度瘦身(daily_summary / kpi_compute 遷移至 Hermes)需獨立 Phase,refactor-specialist 範圍。
+3. **Hermes 變主入口後故障半徑放大**:原本 Hermes 掛 = 規則引擎兜底;現在掛 = 60% Telegram NL 體驗劣化。需強化 mark_unhealthy + 三主機級聯(ADR-027 / ADR-028)。
+
+### 風險與緩解
+
+| 風險 | 機率 | 緩解 |
+|---|---|---|
+| qwen3:14b 繁中黃金集 A/B 紅燈 | 中 | Phase 4 跑 50 題繁中商業 Q&A 黃金集;< 0.75 BERTScore 自動 fallback Gemini |
+| Hermes 故障 = NL 體驗崩 | 低 | 三主機級聯 + mark_unhealthy 30s(ADR-028 支柱 1);規則引擎兜底(ADR-004)|
+| A8 daily 摘要品質 < 戰前 | 中 | Hermes 模板 + Gemini 200 字洞察混合;不是純 Hermes 全自動 |
+| OpenClaw 重構引入 regression | 中 | A10 走 refactor-specialist + 完整 regression test;feature flag 灰度 |
+
+### 降級策略
+
+- A7 feature flag off → 退回 OpenClaw Gemini Q&A
+- A8 Hermes daily 失敗 → 走原 OpenClaw Gemini daily(保留代碼路徑直至 Phase 8 確認穩定)
+- 三主機全掛 → 規則引擎兜底 + Telegram 模板化告警
+
+---
+
+## Verification
+
+### V1:流量分布
+```sql
+-- Hermes vs OpenClaw 月流量比
+SELECT
+ CASE
+ WHEN caller LIKE 'hermes%' THEN 'hermes'
+ WHEN caller LIKE 'openclaw%' THEN 'openclaw'
+ ELSE 'other'
+ END AS tower,
+ SUM(input_tokens + output_tokens) AS total_tokens,
+ COUNT(*) AS calls,
+ SUM(cost_usd) AS cost
+FROM ai_calls
+WHERE called_at >= NOW() - INTERVAL '30 days'
+GROUP BY 1 ORDER BY total_tokens DESC;
+-- 期望:hermes tokens ≥ 4× openclaw tokens;openclaw cost > hermes cost(hermes = $0)
+```
+
+### V2:A7 切換驗證
+```sql
+-- Q&A 第一響應應 ≥ 80% 走 Hermes / qwen3
+SELECT model, COUNT(*) AS calls,
+ ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS pct
+FROM ai_calls
+WHERE caller IN ('openclaw_qa', 'hermes_qa', 'openclaw_bot_main')
+ AND called_at >= NOW() - INTERVAL '7 days'
+GROUP BY model ORDER BY calls DESC;
+-- 期望:qwen3:14b + hermes3 合計 ≥ 80%
+```
+
+### V3:Gemini 月支出
+- 每月 1 日 token report Section 4「by provider 月成本」對比 baseline 月份
+- 預期 `gemini` provider 月成本下降 ≥ 20%
+
+### V4:A8 / A10 上線
+- Phase 4 後 `caller='hermes_daily_summary'` 應每日 1 筆出現於 ai_calls
+- Phase 8 後 `services/openclaw_strategist_service.py` 行數 ≤ 1300
+
+---
+
+## References
+
+- ADR-001 三 Agent 自主學習分工(戰役起點,Hermes/NemoTron/OpenClaw 原始定義)
+- ADR-002 pgvector 唯一向量庫(Hermes embedding 落點)
+- ADR-004 NemoTron fallback chain(OpenClaw NIM 鏈來源)
+- ADR-018 四 Agent 控制面(本 ADR 細化的 L1/L3 角色)
+- ADR-019 Telegram Agentic Layer(Hermes 主入口落地路徑)
+- ADR-021 EA HITL pre-fetch(Hermes 預跑 5s timeout 設計)
+- ADR-027 Primary Ollama on GCP(Hermes 主塔的硬體依託)
+- ADR-028 LLM 路由統一準則(雙塔分工是支柱 3)
+- `docs/phase0_audit_report_20260503.md`(34 caller 流量分布)
+- `docs/phase0_research_report_20260503.md` Section 1(qwen3:14b vs Gemini 品質評估)
+- 相關 memory:`project_three_agent_division.md`、`feedback_agent_action_ladder.md`、`reference_telegram_endpoints_map.md`
diff --git a/docs/adr/README.md b/docs/adr/README.md
index bb24234..afee54b 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -48,6 +48,9 @@
| [024](ADR-024-ppt-system-wave2-forecast-and-deprecations.md) | PPT 系統 Wave 2 — 檔期前瞻 / 多活動比較 + bcg/growth 廢除 | Accepted | 2026-05-03 |
| [025](ADR-025-ppt-system-wave3-new-product-and-market-intel.md) | PPT 系統 Wave 3 — 新品 30 天追蹤 + 市場情報週報 | Accepted | 2026-05-03 |
| [026](ADR-026-ppt-system-price-elasticity-and-final-roadmap.md) | PPT 系統 — 價格彈性報告 + 完整戰役收尾路線圖 | Accepted | 2026-05-03 |
+| [027](ADR-027-primary-ollama-on-gcp.md) | Primary Ollama 遷移至 GCP 高效能主機(v5.0 戰役後追加附錄:三主機架構 / 4 fallback 鏈 / 廢止 188 Ollama) | Accepted | 2026-05-03 |
+| [028](ADR-028-llm-routing-unified-principles.md) | LLM 路由統一準則 — Ollama-First 五大支柱(補述 ADR-027) | Accepted | 2026-05-03 |
+| [029](ADR-029-hermes-first-twin-tower.md) | Hermes-First 雙塔分工(戰術主塔 / 戰略副塔,Gemini 月支出 -23%) | Accepted | 2026-05-03 |
## 規範
diff --git a/run_scheduler.py b/run_scheduler.py
index c188e14..3278c57 100644
--- a/run_scheduler.py
+++ b/run_scheduler.py
@@ -6,9 +6,9 @@ run_scheduler.py — momo-scheduler 容器入口點
每 30 分鐘:auto_import、whitepage_check
每 1 小時:momo、edm、festival
每 4 小時:competitor_price_feeder、icaim_analysis
- 每 6 小時:openclaw_meta_analysis、quality_rescore
+ 每 6 小時:quality_rescore
每 12 小時:dedup_batch
- 每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、ai_smoke_summary(09:10)、pchome_match_backfill(10:30)
+ 每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、ai_smoke_summary(09:10)、pchome_match_backfill(10:30)、openclaw_meta_analysis(12:00, Phase 4 降頻)、daily_token_report(23:55)
每 1 週 :weekly_strategy(週一 06:00)
每 1 月 :monthly_report(每月1日 07:00)
"""
@@ -94,8 +94,10 @@ def _register_schedules():
schedule.every(4).hours.do(run_icaim_analysis_task)
logger.info("📅 每 4 小時:icaim_analysis")
- schedule.every(6).hours.do(run_openclaw_meta_analysis_task)
- logger.info("📅 每 6 小時:openclaw_meta_analysis")
+ # Operation Ollama-First v5.0 Phase 4:Meta 自審降頻 6h → 每日 12:00(月省 ~1.875M Gemini tokens)
+ # icaim_analysis 內原本 line 2233/2253 的額外觸發已同步移除(避免重複呼叫)
+ schedule.every().day.at("12:00").do(run_openclaw_meta_analysis_task)
+ logger.info("📅 每日 12:00:openclaw_meta_analysis(Phase 4 降頻:原 6h)")
schedule.every(6).hours.do(run_quality_rescore_task)
logger.info("📅 每 6 小時:quality_rescore")
@@ -124,6 +126,10 @@ def _register_schedules():
schedule.every().day.at("10:30").do(run_pchome_match_backfill_task)
logger.info("📅 每日 10:30:pchome_match_backfill")
+ # Operation Ollama-First v5.0 — Phase 1 收尾:每日 23:55 LLM Token 日報
+ schedule.every().day.at("23:55").do(run_daily_token_report_task)
+ logger.info("📅 每日 23:55:daily_token_report")
+
# 每月1日 07:00 月報(schedule 不支援 every().month,用每日 07:00 + 日期判斷)
def _monthly_report_gate():
from datetime import datetime as _dt
@@ -134,6 +140,31 @@ def _register_schedules():
logger.info("📅 每月1日 07:00:monthly_report")
+def run_daily_token_report_task():
+ """每日 23:55 — Operation Ollama-First v5.0 Phase 1 收尾:LLM Token 日報。
+
+ 任務:
+ 1. 查 ai_calls 過去 24h 統計(總覽 / 供應商 / TOP caller / 成本 / 趨勢 / 告警)
+ 2. 推 Telegram + 寫 ai_insights(type='daily_token_report')
+
+ 紀律:
+ - 失敗安全:DB 查不到資料 → 推「⚠️ 報表生成失敗」訊息但不爆 scheduler
+ - 不影響其他排程:例外完全吞掉,僅 log error
+ """
+ try:
+ from services.token_report_service import send_daily_report
+ result = send_daily_report()
+ logger.info(
+ "[TokenReport] sent=%s failed=%s chars=%s ok=%s",
+ result.get('sent'), result.get('failed'),
+ result.get('chars'), result.get('ok'),
+ )
+ except Exception as e:
+ logger.error(f"[TokenReport] task failed: {e}", exc_info=True)
+ # 不再嘗試 event_router(避免循環依賴),純 log 即可
+ # 統帥可從 scheduler logs 觀察失敗
+
+
def run_cleanup_agent_context():
"""每日 03:30 — 清理 agent_context 表中已過期的 TTL 記錄(migration 018 定義)"""
from database.manager import get_session
diff --git a/scheduler.py b/scheduler.py
index a45179e..2e8149b 100644
--- a/scheduler.py
+++ b/scheduler.py
@@ -2229,12 +2229,9 @@ def run_icaim_analysis_task():
if not result.threats:
logging.info("[Scheduler] [ICAIM] 無威脅商品,跳過 NemoTron dispatch")
- # 仍觸發 OpenClaw Meta-Analysis 更新系統效能快照
- try:
- from services.openclaw_strategist_service import generate_meta_analysis_report
- generate_meta_analysis_report()
- except Exception as _meta_e:
- logging.warning(f"[Scheduler] [ICAIM] Meta-Analysis 非阻塞失敗: {_meta_e}")
+ # Operation Ollama-First v5.0 Phase 4:移除原 inline meta_analysis 觸發
+ # 原因:呼叫點 #16 月耗 ~2.5M Gemini tokens(icaim 4h × 6 + cron 6h × 4 = 10/天)
+ # 改由 run_scheduler 每日 12:00 單一觸發;此處僅 log,不再呼叫。
return
# Step 2:NemoTron 派發器 → Telegram
@@ -2250,12 +2247,8 @@ def run_icaim_analysis_task():
)
_save_stats('icaim_dispatch', {**dispatch_result, "status": "Success"})
- # Step 3:派發完成後觸發 OpenClaw Meta-Analysis(非阻塞)
- try:
- from services.openclaw_strategist_service import generate_meta_analysis_report
- generate_meta_analysis_report()
- except Exception as _meta_e:
- logging.warning(f"[Scheduler] [ICAIM] Meta-Analysis 非阻塞失敗: {_meta_e}")
+ # Operation Ollama-First v5.0 Phase 4:原 Step 3 inline Meta-Analysis 觸發已移除
+ # 改由 run_scheduler 每日 12:00 單一觸發(Phase 4 降頻 6h → 24h,月省 ~1.875M tokens)
except Exception as e:
import traceback as _tb
diff --git a/services/elephant_alpha_orchestrator.py b/services/elephant_alpha_orchestrator.py
index 32cd0ce..97ebb49 100644
--- a/services/elephant_alpha_orchestrator.py
+++ b/services/elephant_alpha_orchestrator.py
@@ -85,6 +85,9 @@ class ElephantAlphaOrchestrator:
),
"openclaw": AgentCapability(
name="OpenClaw Strategist",
+ # LOCKED-GEMINI: EA HITL 戰略決策影響統帥行動,要最高品質推理
+ # 未來可升 Claude Sonnet 4.6 (agentic 工具使用佳) — Phase 7 任務
+ # ADR-028 鎖定場景 #6
model="gemini-2.0-flash",
strengths=["strategic_planning", "market_analysis", "insight_generation"],
limitations=["real_time_execution", "direct_actions"],
diff --git a/services/mcp_collector_service.py b/services/mcp_collector_service.py
index adab8a4..2b6658b 100644
--- a/services/mcp_collector_service.py
+++ b/services/mcp_collector_service.py
@@ -18,6 +18,7 @@ import json
import logging
import os
import time
+import requests
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
@@ -28,7 +29,16 @@ logger = logging.getLogger(__name__)
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
MCP_CACHE_TTL_HOURS = int(os.getenv("MCP_CACHE_TTL_HOURS", "24"))
+# LOCKED-GEMINI: MCP 即時情報需 Google Search Grounding,唯一聯網能力(ADR-028 鎖定場景 #1)
+# Ollama 為離線模型,知識截止於訓練日,不可取代 grounding。
MCP_MODEL = os.getenv("MCP_GEMINI_MODEL", "gemini-2.0-flash")
+MCP_FALLBACK_MODEL = "gemini-1.5-flash"
+
+try:
+ from services.ollama_service import resolve_ollama_host
+ _OLLAMA_AVAILABLE = True
+except ImportError:
+ _OLLAMA_AVAILABLE = False
# ── 查詢主題定義 ────────────────────────────────────────────────────────────
_SEARCH_TOPICS = {
@@ -171,7 +181,47 @@ class MCPCollectorService:
return content
return self._fallback_topic_content(topic, "Gemini 回傳空內容,使用本地行銷情報。")
except Exception as e:
- logger.warning("[MCP] 搜尋失敗 topic=%s: %s", topic, e)
+ logger.warning("[MCP] Gemini 2.0 Grounding failed topic=%s: %s, trying 1.5 Flash", topic, e)
+ # 級別 2:嘗試 1.5 Flash (通常配額較穩)
+ try:
+ model = self._genai.GenerativeModel(model_name=MCP_FALLBACK_MODEL, tools=["google_search"])
+ response = model.generate_content(prompt)
+ content = response.text or ""
+ if content and not self._looks_unreliable(content):
+ self._write_cache(topic, content)
+ return content
+ except Exception as e2:
+ logger.warning("[MCP] Gemini 1.5 Flash also failed: %s", e2)
+
+ # 級別 3:嘗試 GCP Ollama (使用模型內置知識作為最後防線)
+ if _OLLAMA_AVAILABLE:
+ try:
+ logger.info("[MCP] Using GCP Ollama for market insight fallback")
+ host = resolve_ollama_host()
+ ollama_prompt = (
+ f"你是一位精通台灣電商市場的分析師。目前無法取得即時搜尋結果,"
+ f"請根據你的知識儲備,針對以下主題提供 2026 年可能的市場動態或洞察(繁體中文,300字以內):\n"
+ f"主題:{query}\n\n"
+ "請註明:『(此為基於歷史趨勢的預測性洞察)』"
+ )
+ r = requests.post(
+ f"{host}/api/generate",
+ json={
+ 'model': os.getenv('OPENCLAW_OLLAMA_MODEL', 'qwen2.5-coder:7b'),
+ 'prompt': ollama_prompt,
+ 'stream': False,
+ 'options': {'num_predict': 800, 'temperature': 0.4}
+ },
+ timeout=45
+ )
+ r.raise_for_status()
+ content = r.json().get('response', '').strip()
+ if content:
+ # 不進快取,因為這是預測性內容
+ return content
+ except Exception as e3:
+ logger.warning("[MCP] Ollama fallback also failed: %s", e3)
+
return self._fallback_topic_content(topic, f"即時外部搜尋暫不可用:{type(e).__name__}")
@staticmethod
diff --git a/services/telegram_templates.py b/services/telegram_templates.py
index b794f38..173fd45 100644
--- a/services/telegram_templates.py
+++ b/services/telegram_templates.py
@@ -532,6 +532,41 @@ def _send_telegram(msg: str, chat_ids: Optional[list] = None,
return _send_telegram_raw(msg, chat_ids=chat_ids, reply_markup=reply_markup)
+# ══════════════════════════════════════════════════════════════════════════════
+# LLM Token 日報模板(Operation Ollama-First v5.0 — Phase 1 收尾)
+# 對應 services/token_report_service.py
+# ══════════════════════════════════════════════════════════════════════════════
+
+# Telegram 單訊息上限(保險絲,sendMessage HTML 上限為 4096 字元)
+_DAILY_TOKEN_REPORT_MAX_CHARS = 4096
+
+
+def daily_token_report(report_html: str,
+ footer_url: Optional[str] = None) -> str:
+ """LLM Token 日報訊息包裝(HTML parse_mode)。
+
+ 本函數只負責「附上 footer + 截斷至 Telegram 上限」;報表本體由
+ services/token_report_service.generate_daily_report() 產出,已含 HTML escape。
+
+ Args:
+ report_html: 已 escape 的 HTML 報表字串
+ footer_url: 選填 admin 後台連結,會自動 escape
+
+ Returns:
+ ≤ 4096 字元的 HTML 字串,安全送 Telegram
+ """
+ body = report_html or ""
+ if footer_url:
+ body = f"{body}\n📎 詳細日誌"
+
+ if len(body) <= _DAILY_TOKEN_REPORT_MAX_CHARS:
+ return body
+
+ # 超長 → 截斷並加省略尾(保留 80 字給 trailing notice)
+ truncated = body[: _DAILY_TOKEN_REPORT_MAX_CHARS - 80]
+ return truncated + "\n\n... (訊息過長已截斷;完整內容存於 ai_insights)"
+
+
# ══════════════════════════════════════════════════════════════════════════════
# 決策回執模板(L2 / L3 按鈕點擊後編輯原訊息使用)
# ADR-012 Phase 4:審計留痕 — 操作者、時間、action、結果