# ADR-007：AI 學習雙寫規範（DB + KM 同步寫入）

- **Status**: Accepted
- **Date**: 2026-04-18
- **Decision Maker**: 統帥
- **Author**: Claude

## Context

在 EwoooC 三 Agent 分工架構中（ADR-001），所有 AI 產出（洞察、競品分析、對話、PPT 內容）最終需要沉澱。
若只寫入一個 sink，會導致：
- 只寫 DB：RAG 無法語意搜尋過去的洞察
- 只寫 KM（vector）：精確 period/sku 查詢無法命中

歷史教訓（2026-04-18）：
- 舊版 `openclaw_learning_service.py` 有 `store_insight()` API，但只寫入 DB，未向量化
- 導致 PPT 每次重新呼叫 AI 生成，歷史簡報失真（內容不一致）

## Decision

**所有 AI 產出必須雙寫 DB + KM（pgvector）**，且為同一事務（atomic）。

```
AI 產出（洞察/分析/對話）
     │
     ▼
NemoTron: tool call store_insight()
     │
     ├─→ PostgreSQL ai_insights（結構化欄位 + metadata）
     │          ↓ async
     └─→ Hermes: embed → pgvector embedding 欄位更新同一行
```

**What 必須雙寫**：

| 產出類型 | DB 表 | embedding 欄位 |
|---|---|---|
| PPT 洞察 | `ai_insights` | `embedding vector(1024)` |
| 競品分析 | `ai_insights` | `embedding vector(1024)` |
| 用戶對話記錄 | `conversations` | `embedding vector(1024)` |
| 週報 meta-analysis | `ai_insights` | `embedding vector(1024)` |

**What 不需雙寫**（不需語意搜尋）：
- Telegram 告警派發記錄（只做審計）
- 原始爬蟲資料（`price_records`, `competitor_prices`）
- 用戶操作 log（`audit_logs`）

## Consequences

### Positive
- RAG 永遠能找到歷史洞察，PPT 內容跨時一致
- cache-aside 精確命中（同 period+type → 0.3s 回）+ 語意 fallback（近似查詢）

### Negative / Trade-offs
- 每次寫入有兩個 I/O（DB + embedding），延遲增加約 100-200ms
- Hermes 主機 OOM 時 embedding 會失敗（需 retry queue）

## Implementation Notes

```python
# 雙寫入口點（atomic）
async def store_insight_with_embedding(insight: InsightModel):
    # Step 1: 寫 DB（同步）
    insight_id = db.execute(INSERT INTO ai_insights ...)

    # Step 2: 排隊 embedding（異步，不阻塞主流）
    embedding_queue.put({"id": insight_id, "text": insight.text})

# Hermes worker（獨立進程）
def embedding_worker():
    while True:
        item = embedding_queue.get()
        vec = embed_text(item["text"])  # 呼叫 Ollama bge-m3
        db.execute(UPDATE ai_insights SET embedding = :vec WHERE id = :id)
```

## Related ADRs

- ADR-001：三 Agent 分工（NemoTron 負責寫入觸發，Hermes 負責 embedding）
- ADR-002：pgvector（KM 儲存層）
- ADR-003：本地 embedding（embedding 引擎）
- ADR-005：時間衰減（品質分數策略）
