# aider-watch v2 設計稿 — 完全整合 awoooi 飛輪版

> **建立**：2026-04-20 @ Asia/Taipei
> **取代**：[v1 2026-04-19](2026-04-19-aider-watch-design.md)（已 DEPRECATED）
> **觸發**：統帥 2026-04-20 指示「C 路線 + 甲 bot」— 把 aider-watch 完全整合進 awoooi AI 自主化飛輪，而非獨立個人工具

---

## 0. 為何重寫

v1 把 aider-watch 做成 **獨立個人工具**：
- 獨立 repo `~/aider-watch`
- 獨立 PG DB `aider_watch`（放 188 但與 awoooi 平行）
- 獨立 Telegram bot `@NemoTronAwoooI_Bot`
- 被動記錄，無 AI 學習

**根本問題**：純記錄 ≠ AI 自主化。違反 `feedback_ai_autonomous_direction.md`（🔴🔴🔴 最高鐵律）的「AI 自主化北極星」— 設計裡沒有 learning、沒有 auto-repair、沒有 decision feedback loop。

**v2 方向**（統帥批准）：
- ✅ 進 awoooi 主 PG（新 migration 一張 `aider_events` 表）
- ✅ 走 awoooi 既有 Telegram 告警鏈（`@tsenyangbot` + Redis dedup）
- ✅ 接進 AI Router feedback loop（Phase 24 ADR-052）
- ✅ aider error → 自動建 `incident`（走既有事件管線）
- ✅ 從 aider events 抽 `symptom_pattern` → 餵 `playbooks` 學習
- ✅ Mac client 端保留為薄殼，純 HTTP 推送 + 本機 fallback buffer

---

## §1 架構總覽（全景）

```
┌──────────── 統帥 Mac （外部 agent, K3s 外）────────────┐
│                                                          │
│  aiderw (Python wrapper)                                 │
│    ├ subprocess aider + stdout tee                       │
│    ├ 退出時 git diff / chat_history.md canonical rebuild │
│    ├ redact secrets（本地先做一遍）                      │
│    └ HTTP POST events → AWOOOI API (HMAC-signed)         │
│                                                          │
│  本機 fallback: ~/aider-watch/buffer/*.jsonl             │
│    （API 不可達時寫這裡，launchd flush job 5min 補送）   │
│                                                          │
│  本機只留: live.log (append-only, tail -F 用)            │
│  無本機 PG，無本機 report                                │
└──────────────┬───────────────────────────────────────────┘
               │ HTTPS + HMAC-SHA256
               ▼
┌────── AWOOOI API (K3s svc 192.168.0.120:32334) ──────────┐
│                                                          │
│  api/v1/aider_events.py                                  │
│    POST /api/v1/aider/events   (HMAC verified)           │
│    → push Redis stream signals:aider:events              │
│                                                          │
│  jobs/aider_event_processor_job.py  (loop, 5s poll)      │
│    ├ consume Redis stream                                │
│    ├ write aider_events table                            │
│    ├ classify:                                            │
│    │   error / non-zero exit / silent_timeout            │
│    │   → 建 incident（走 incident_service）              │
│    │     severity: silent=P3, error=P2, multi-error=P1   │
│    │     alert_category: aider_activity                  │
│    │   → incident_service 自己觸發 TG (既有 dedup 生效)  │
│    │                                                      │
│    └ 每 day 聚合: extract symptom_pattern                │
│       → 寫 playbooks.symptom_pattern JSONB               │
│         例: {repo: "awoooi", model: "elephant", error_rate: 0.3} │
│                                                          │
│  services/ai_router.py  (既有, Phase 24)                 │
│    新增 hook: feedback_from_aider_events()               │
│    → 讀過去 7 日 aider_events：                          │
│      哪 model × 哪 repo/task 成功率 vs 失敗率            │
│    → 調整 route() 的 provider priority                   │
│                                                          │
│  services/telegram_gateway.py  (既有, 不動)              │
│    incident → TG @tsenyangbot + Redis dedup 10min TTL    │
└──────────────────────────────────────────────────────────┘
```

**關鍵設計決定**：

1. **aider error → incident → 既有鏈路**：不做另一套告警機制，aider 的錯誤等同 awoooi 其他來源的錯誤，統一 incident table 統一 TG 通道。
2. **Event 進 Redis stream 再處理**：沿用 Phase 6.1 Event Bus pattern。API 層快 response（<50ms），處理在背景。
3. **AI Router feedback 是學習閉環的核心**：這就是「AI 自主化」的體現 — aider-watch 不只是記錄，是餵給 AI Router 讓 model 選擇越來越準。
4. **symptom_pattern 抽取**：日 batch job，把 aider 失敗模式抽成 playbook candidate（跟 Phase 7 blindspot governance 同一機制）。
5. **Mac 端盡量薄**：只做 event capture + HTTP POST + local buffer。所有商業邏輯在 server。

---

## §2 元件清單

### 2.1 AWOOOI 內新增（Server 端）

| # | 檔案 | 職責 | 既有模式參考 |
|---|------|------|--------------|
| S1 | `apps/api/migrations/adr091_aider_events_schema.sql` | DB migration | 既有 `adr090_asset_inventory_foundation.sql` |
| S2 | `apps/api/src/models/aider.py` | Pydantic: `AiderEvent`, `AiderSessionStart`, `AiderFileEdit`, `AiderError`, `AiderSessionEnd`（6 種 + 通用外殼） | `models/incident.py` |
| S3 | `apps/api/src/repositories/aider_event_repository.py` | CRUD aider_events + 聚合查詢 | `repositories/incident_repository.py` |
| S4 | `apps/api/src/services/aider_event_service.py` | 分類、建 incident、抽 symptom_pattern | `services/incident_service.py` |
| S5 | `apps/api/src/api/v1/aider_events.py` | POST webhook + HMAC 驗證 | `api/v1/webhooks.py` (Alertmanager) |
| S6 | `apps/api/src/jobs/aider_event_processor_job.py` | Redis stream consumer + 分類 dispatcher + 日 batch pattern extractor | `jobs/asset_scanner_job.py` |
| S7 | `apps/api/src/services/ai_router.py` 新增 `feedback_from_aider_events()` | 從 aider_events 聚合統計 → 調整 model priority | 既有檔改一個 method |
| S8 | `apps/api/src/utils/secret_redactor.py` | 泛用 secret redactor（**從 v1 搬過來**） | 目前 awoooi 無，新增 |
| S9 | `apps/api/src/core/config.py` 新增 3 個 settings | `AIDER_WEBHOOK_SECRET`, `AIDER_EVENTS_STREAM_KEY`, `AIDER_PATTERN_EXTRACT_INTERVAL_HOURS` | 既有 core/config.py |
| S10 | `apps/api/src/main.py` lifespan 註冊 | `asyncio.create_task(run_aider_event_processor_loop())` | 既有 378-382 pattern |

### 2.2 Mac 端（Client）

**位置**：`awoooi/scripts/aider_watch_client/`（進 awoooi repo，不是獨立 repo）

| # | 檔案 | 職責 |
|---|------|------|
| M1 | `aider_watch_client/aiderw.py` | subprocess aider + event capture + HTTP POST |
| M2 | `aider_watch_client/parsers.py` | aider stdout banner / chat_history.md / git numstat（從 v1 搬） |
| M3 | `aider_watch_client/api_client.py` | HTTP POST with HMAC + retry + buffer fallback |
| M4 | `aider_watch_client/buffer.py` | JSONL buffer（本機 fallback） |
| M5 | `aider_watch_client/install.sh` | 在 Mac 安裝（pipx or venv）+ 註冊 launchd flush job |
| M6 | `aider_watch_client/launchd/com.awoooi.aider-flush.plist` | 每 5min flush buffer |

### 2.3 舊 v1 產物去向

| v1 產物 | v2 處理 |
|---------|---------|
| `~/aider-watch/aider_watch/events.py` | ✅ 搬 `apps/api/src/models/aider.py`（改為 awoooi 命名） |
| `~/aider-watch/aider_watch/redactor.py` | ✅ 搬 `apps/api/src/utils/secret_redactor.py` + 同一份用於 Mac client |
| `~/aider-watch/aider_watch/telegram.py` | ❌ 廢棄（走 awoooi telegram_gateway） |
| `~/aider-watch/aider_watch/config.py` | ⚠️ 拆：Mac 端用的部分搬進 `aider_watch_client/`；server 的改讀 awoooi `core/config.py` |
| `~/aider-watch/aider_watch/wrapper.py`（未完成） | 簡化搬進 `aider_watch_client/aiderw.py` |
| `~/aider-watch/bin/aiderw` | 搬 `aider_watch_client/bin/aiderw` |
| `~/aider-watch/pyproject.toml` | 改為 `aider_watch_client/pyproject.toml`（client 端獨立 pipx 裝） |
| `~/aider-watch/tests/*` | 搬：client 測試留 client，server 測試改寫走 awoooi pytest 約定 |
| `~/aider-watch/schema.sql` + `install_pg.sh` | ❌ 廢棄（v2 用 awoooi migration） |
| `~/aider-watch/` repo 本身 | 保留 1 週作備份，2026-04-27 後刪（或歸 archive 分支） |

---

## §3 Event Schema（與 v1 一致，微調）

7 種 event type，Mac 客戶端 POST 的 payload shape：

```json
{
  "ts": "2026-04-20T10:15:33+08:00",
  "session_id": "01J7XZ...",            // ULID
  "host": "ogt-mac",                      // 將來多機器區分
  "type": "session_start|file_edit|error|commit|silent_timeout|session_end|raw",
  "payload": { ... type-specific (同 v1 §3) }
}
```

**批次推送**：`POST /api/v1/aider/events` body 接受 `{"events": [...]}` 陣列，最多 50 筆/次，降低 HTTP 往返成本。

---

## §4 DB Schema — awoooi 主 PG 新 migration

**Migration**：`adr091_aider_events_schema.sql`

```sql
-- aider events raw table（單一表，event + 一次性 session 元資料合一用 JSONB）
-- 設計參考: asset_discovery_run pattern
-- 2026-04-20 @ Asia/Taipei

CREATE TABLE IF NOT EXISTS aider_events (
  id              BIGSERIAL PRIMARY KEY,
  session_id      TEXT NOT NULL,
  ts              TIMESTAMPTZ NOT NULL,
  type            TEXT NOT NULL,                -- session_start / file_edit / error / ...
  host            TEXT DEFAULT 'ogt-mac',
  payload         JSONB NOT NULL,
  incident_id     TEXT,                          -- 若觸發建 incident，記 FK
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX aider_events_session_idx ON aider_events(session_id);
CREATE INDEX aider_events_type_ts_idx ON aider_events(type, ts DESC);
CREATE INDEX aider_events_ts_idx ON aider_events(ts DESC);
-- payload GIN for 查 repo / model 統計
CREATE INDEX aider_events_payload_gin ON aider_events USING GIN (payload);

-- 不另外建 sessions 表：從 events 聚合（session_start + session_end 配對），
-- 查詢性能可接受（session 量 <1000/day），避免雙寫漂移。
```

**symptom_pattern 抽取**：不新增表，直接寫 `playbooks` 表的 `symptom_pattern` JSONB（既有）：

```json
{
  "source": "aider_watch",
  "repo": "awoooi",
  "model": "elephant-alpha",
  "task_kind": "refactor",
  "error_rate": 0.35,
  "evidence_session_ids": ["01J...", "01J..."]
}
```

---

## §5 API 合約

### POST /api/v1/aider/events

**Request**:
```http
POST /api/v1/aider/events
Content-Type: application/json
X-Aider-Signature: sha256=<HMAC-SHA256 of body using AIDER_WEBHOOK_SECRET>
X-Aider-Client: aiderw/0.2.0

{
  "events": [
    { "ts": "...", "session_id": "...", "type": "session_start", "payload": {...} },
    ...
  ]
}
```

**Response**:
- `202 Accepted` → `{"accepted": N, "stream_id": "redis-id"}`（快速 ack，不等處理完成）
- `401 Unauthorized` → HMAC 驗證失敗
- `400 Bad Request` → schema 驗證失敗，回具體錯誤欄位
- `429 Too Many Requests` → 超過 rate limit（預設 120 req/min per client）
- `503 Service Unavailable` → Redis 不可達（client 必須 fallback 到 buffer）

**HMAC 驗證**：
```python
import hmac, hashlib
expected = "sha256=" + hmac.new(
    AIDER_WEBHOOK_SECRET.encode(),
    request.body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
    abort(401)
```

---

## §6 AI 學習回路

### 6.1 incident 自動建立（即時）
`aider_event_service.handle_error_event(ev)`：
- 若 `type in {"error", "session_end with exit≠0", "silent_timeout"}`：
  - 建 incident: 
    - `alert_category = "aider_activity"`
    - `severity = P1 if error_count>=3 else P2 if error else P3 if silent_timeout`
    - `affected_services = [repo_name]`
    - `signals = {source: "aider_watch", session_id, model, last_error_message}`
  - incident_service 自動觸發既有 Telegram gateway（dedup 10min TTL 自動生效）

### 6.2 symptom_pattern 日 batch 抽取
`aider_event_processor_job.extract_patterns_daily()`（每日 02:30 台北）：
- 聚合近 7 日 aider_events
- group by (repo, model, task_kind)
- 計算 error_rate / avg_tokens / avg_duration
- 若 `error_rate >= 0.25` 且 `sample_n >= 3` → 寫 playbook candidate

### 6.3 AI Router feedback hook（每次 route() 呼叫）
`ai_router.py` 加：
```python
def feedback_from_aider_events(repo: str, task_kind: str) -> dict[str, float]:
    """回傳 {model_name: success_rate} based on past 7 days aider usage."""
    # 從 aider_event_repository 聚合
    return {"elephant-alpha": 0.85, "gemini-pro": 0.92, ...}
```
在 `AIRouter.route()` 內：如果有 feedback data，把 provider 權重乘 `success_rate`。

---

## §7 Telegram 整合

**100% 複用既有 `services/telegram_gateway.py`，零新增 bot**：

- aider error → incident created → `telegram_gateway.send_alert(incident)`
- 訊息格式走 awoooi SOUL.md 標準（不再自訂 🚀/🏁 emoji 模板）
- Redis dedup `telegram_sent:{incident_id}` TTL 600s 自動生效
- bot `@tsenyangbot`，chat_id 從 K8s secret `TELEGRAM_CHAT_ID` 讀

**aider 非 error 事件（session_start / file_edit / commit）**：**不推 Telegram**，只寫 aider_events 表。理由：
- awoooi Telegram 是 **告警通道**，不該塞正常活動洗版
- 統帥要看活動 → 下次加 `/api/v1/aider/activity_feed` REST 查詢或前端 dashboard
- 若你堅持要推，單獨開 `AIDER_WATCH_PUSH_ACTIVITY` flag 預設 false

### ⚠️ v1 @NemoTronAwoooI_Bot 怎麼處理
- 這支 bot 2026-04-19 才建，無生產依賴
- v2 不用它 → 建議 **deactivate** 或 **改用途**（例如 aider 測試 sandbox 用）
- 不刪 bot（BotFather 刪除不可逆）

---

## §8 錯誤處理 / 降級策略

| 失敗情境 | 處理 | 結果 |
|---------|------|------|
| **AWOOOI API 不可達** | Mac client 寫 `~/aider-watch/buffer/*.jsonl`；launchd flush job 每 5min 重試 | 不丟資料 |
| **Mac 離開內網**（🆕 盲點 1） | `AIDER_API_URL` 走 awoooi nginx 公網 endpoint（`https://awoooi.example/api/v1/aider/events`）或 VPN/Tailscale；buffer 作備援 | 離家/出差仍可用 |
| **HMAC 驗證失敗** | API 回 401，記 `audit_log`；Mac client 留 buffer 等人工介入 | 防偽造 |
| **Redis stream 滿** | 自動 trim 10k 筆上限；溢位寫 `audit_log` warning | 不阻塞 API |
| **aider_event_processor 掛** | 既有 main.py lifespan supervisor 會重啟；消費位置從 Redis stream consumer group 恢復 | 自癒 |
| **incident_service 建 incident 失敗** | 降級：只寫 aider_events 表，不建 incident；TG 不推（可接受，event 還在） | 不丟資料 |
| **incident 洗版**（🆕 盲點 2）：一 session 連續多 error 建多個 incident | service 層加 **coalesce 機制**：同 `session_id` + `aider_activity` 在 60s 窗口內只建 1 個 incident，後續 error 合進同一 incident 的 signals 陣列 | TG 不被 aider 噪音淹沒 |
| **AI Router feedback 首次 rollout 風險**（🆕 盲點 3） | 加 `USE_AIDER_FEEDBACK` flag 預設 `false`；灰度驗證（跑 7 天真實 aider 資料、比對 `ai_router.route()` 決策差異）再開 `true` | 防止 aider 資料牽連其他 route 決策 |
| **secret 外洩** | Mac client 進 buffer 前先 redact；server 再 redact 一次（depth-in） | 雙層防線 |
| **wrapper 崩潰** | aiderw 外層 try/except 全包；atexit 補 session_end | aider 照跑 |
| **aider 本身崩潰** | wrapper 收 SIGCHLD → 送 error event + session_end exit≠0 | 死得清楚 |

**🆕 §8b Mac client 網路配置選項**（盲點 1 展開）

| 方式 | `AIDER_API_URL` 設值範例 | 優缺 |
|------|------------------------|------|
| **Tailscale**（如果統帥已裝） | `http://<tailscale-ip>:32334/...` | 零設定，encrypted；需 Tailscale 持續登入 |
| **awoooi nginx 公網** | `https://<domain>/api/v1/aider/events` | 公網可達；需在 nginx 加 reverse proxy route 並 enable TLS |
| **VPN（WireGuard）** | `http://192.168.0.120:32334/...` | 強隱私；需 VPN 持續連線 |

**預設採 awoooi nginx 公網** — awoooi 已有 nginx:443 on 188 host，增加一條 location block 即可；Mac 不需額外軟體。

**🆕 §8c `AWOOOI_PG_PW` 取得流程**（盲點 4）

A0 前置步驟：
```bash
# 從 K8s secret 取 PG 密碼（部署端一次性）
kubectl get secret awoooi-secrets -n awoooi -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d > /tmp/pg_pw.txt
export AWOOOI_PG_PW="$(cat /tmp/pg_pw.txt)"
shred -u /tmp/pg_pw.txt   # 不留痕
```
此步符合 `feedback_secrets_leak_incidents_2026-04-18.md` 零信任三層防線 L4（本機 shell env，不進檔）。

---

## §9 測試策略

遵循 `feedback_no_mock_testing.md`：

| 層級 | 範圍 | 工具 | 通過標準 |
|------|------|------|----------|
| **Unit (server)** | models schema, secret_redactor, HMAC 驗證, pattern extractor | pytest | 100% pass |
| **Unit (client)** | aiderw parsers, api_client retry, buffer write | pytest | 100% pass |
| **Integration (server)** | 真寫 PG `aider_events`, Redis stream round-trip | pytest + real PG (168 test schema) | round-trip OK |
| **Integration (client→server)** | Mac client POST events → server 消費 → 寫 DB → 查詢 | 本機 pytest + 打 awoooi API | 全流程通 |
| **E2E happy** | 真 aiderw 跑一次 aider →events 進 awoooi DB → 若 error 建 incident → TG 收到 | 手動 | TG 收到（走 @tsenyangbot 不是新 bot） |
| **E2E degradation** | 拔網路跑 aiderw → buffer 有檔 → 網路恢復 + flush job 跑 → buffer 清空 + DB 補齊 | 手動 | buffer → DB 無漏 |
| **AI Router feedback** | 灌 10 場 aider session 資料 → 查 ai_router.feedback_from_aider_events 回傳值 → 驗 success_rate 合理 | pytest | 數值正確 |

---

## §10 相關 Memory 依據

- `feedback_ai_autonomous_direction.md` 🔴🔴🔴 — AI 自主化北極星（這是 v2 存在的根本原因）
- `feedback_alertmanager_awoooi_flow.md` — 告警鏈路 Alertmanager→AWOOOI API→TG
- `feedback_telegram_dedup.md` — Redis dedup 10min TTL
- `feedback_secrets_leak_incidents_2026-04-18.md` — secret 雙層 redact
- `feedback_architecture_review_gates.md` 🔴🔴 — 重大方向變更需架構 review（本 v2 就是）
- `feedback_solution_adoption_checklist.md` — 解決方案導入 7 問（已對照）
- `project_master_aiops_blueprint.md` — AI 自主化 MASTER blueprint 全景
- `project_blindspot_governance.md` 🔴🔴🔴 — Phase 7 盲區治理（symptom_pattern 抽取沿用）
- `project_phase24_ai_router.md` — AI Router D1-D14 決策

---

## §11 後續

本設計批准後：
1. 寫 v2 plan（`2026-04-20-aider-watch-v2.md`）
2. 暫停 `~/aider-watch` repo 開發（Task 6 不跑 install_pg.sh）
3. 按 v2 plan 執行 server + client 雙半實作
4. Mac client 部署在 awoooi repo 而非獨立
