F1 — 告警觸發 → 人工簽核完整流程
主線:P0 告警 → @alice 批准 → 執行完成
正常路徑
H
SRE 指揮中心
42 成員 · Hermes 在線
editMessage: 按鈕狀態
sendMessage: 結果新訊息
reply_to: 關聯原卡
異常 A — @bob 無授權誤按
拒絕路徑
H
SRE 指揮中心
私訊 @bob(不發群組)
異常 B — 超過 48h 按鈕失效:訊息老化降級策略
降級路徑
H
SRE 指揮中心
48h 老化觸發
APPROVAL 訊息老化狀態機
─────────────────────────────
[PENDING] message_id: M001
│
├─ T < 47h
│ └─ 按鈕可用 · 正常 editMessage
│
├─ T = 47h (掃描觸發)
│ └─ 告警升級通知 (sendMessage)
│
└─ T ≥ 48h (editMessage 硬上限)
│
├─ 步驟① editMessage M001
│ - 移除 InlineKeyboard
│ - 文字改為「⚠ 已失效,見新卡」
│ (若 editMessage 失敗 → 靜默跳過)
│
└─ 步驟② sendMessage (新 message_id: M002)
- 複製原告警內容
- 加「卡片更新」標記
- 重新綁定新 approval_id
- 重置 48h 計時器
DB 更新:approval_records
old_message_id = M001 → status = "expired_relocated"
new_message_id = M002 → status = "pending"
relocated_at = now()
generation = generation + 1 ← 防無限更新 (max=3)
Redis 更新:
DEL approval:M001
SET approval:M002 {approval_id, relocate_gen:1} TTL=48h
editMessage 失敗處理:若原始訊息已被刪除或 bot 被踢出群組,editMessage 會回 400。
直接跳步驟②,在 DB 記錄 old_message_relocate_failed=true,繼續發新卡。
generation 上限 = 3:最多遷移 3 次(約 6 天)。
第 4 次觸發時,改為通知群組管理員並關閉告警,
記錄 approval 為 auto_expired_escalated。
callback_data 格式規格
| 欄位 | 說明 | 範例 |
|---|---|---|
| 格式 | apr:{short_id}:{action}:{user_id} |
|
| short_id | approval short ID(6 碼) | 0091a3 |
| action | approve / reject / detail | approve |
| user_id | 指派人 Telegram user_id(整數) | 88234512 |
| 完整範例 | apr:0091a3:approve:88234512 |
|
| 驗證邏輯 | handler 比對 callback.from.id == user_id,不符 → answerCallbackQuery toast + 私訊 | |
| 長度上限 | Telegram 限制 64 bytes,此格式約 28 bytes,安全 | |
Redis Session 結構 — F1
KEY: approval:{message_id}
TTL: 172800s (48h)
────────────────────────────────
{
"approval_id": "apr-0091-a3f",
"short_id": "0091a3",
"incident_id": "inc-2404-0091",
"assignee_uid": 88234512,
"group_chat_id": -1001234567890,
"created_at": 1745460863,
"status": "pending",
"relocate_gen": 0
}
KEY: approval:sent:{incident_id}
TTL: 172800s (48h)
值:message_id (去重,防止重複發卡)
KEY: rate_limit:apr:{chat_id}
TTL: 60s (token bucket 60s 視窗)
值:剩餘令牌數 (max=10)
F1 完整狀態機
APPROVAL 狀態機
───────────────────────────────────────────────────────────────────
[INIT] Alertmanager webhook → awoooi API
│
├─ 去重檢查 approval:sent:{incident_id} 存在?
│ └─ YES → skip (不重複發卡)
│
└─ NO → sendMessage (告警卡) → 儲存 approval:{message_id}
[PENDING] 等待簽核
│
├─ callbackQuery 收到
│ ├─ user_id 驗證
│ │ ├─ FAIL → answerCallbackQuery toast ⛔ + 私訊 → 回 [PENDING]
│ │ └─ PASS →
│ │ ├─ editMessage (執行中狀態,按鈕 muted)
│ │ ├─ 執行動作 (kubectl / ansible)
│ │ └─ 判斷結果 →
│ │ ├─ SUCCESS → sendMessage (結果卡 reply) → [DONE]
│ │ └─ FAILED → sendMessage (失敗卡 + retry) → [ERROR]
│ │
│ └─ timeout 掃描 (每 30min cron)
│ ├─ 47h → sendMessage 升級提醒 → [PENDING]
│ └─ ≥48h → 老化降級 → [RELOCATED]
[DONE] approval_records.status = completed
[ERROR] approval_records.status = failed, retry_count++
[EXPIRED] approval_records.status = auto_expired (generation ≥ 3)
[RELOCATED] approval_records.status = pending (新 message_id)
F2 — Hermes NL 簡單查詢 + 多輪 Context
主線:查詢 → 追問(多輪 session)
session: ses-0924-alice
H
SRE 指揮中心
42 成員 · session 計時中
ctx-lock 顯示 = 群組其他人知道 Hermes 在和 @alice 對話
agent-tag = 哪個 agent 在工作
Redis Session 結構 — F2 多輪對話
KEY: hermes:session:{chat_id}:{user_id}
TTL: 300s (5分鐘無活動 → 失效)
────────────────────────────────
{
"session_id": "ses-0924-alice",
"user_id": 88234512,
"chat_id": -1001234567890,
"agent": "debugger",
"turn": 2,
"context": [
{
"role": "user",
"content": "今天有幾個 P0 告警?",
"ts": 1745496121
},
{
"role": "assistant",
"content": "今日 P0 告警:3 件...",
"ts": 1745496124,
"agent": "debugger",
"cost_usd": 0.02
},
{
"role": "user",
"content": "最嚴重那個是什麼?",
"ts": 1745496151
}
],
"compaction_after": 5, ← turn ≥ 5 觸發 compaction
"cost_total_usd": 0.03
}
KEY: hermes:session:active:{chat_id}
TTL: 300s
值:"{user_id}:{session_id}" ← 群組可見:誰在和 Hermes 對話中
Context Indicator — 群組其他人如何知道不要亂插嘴
設計方案:
1. 每則 Hermes 回覆都帶
2. 其他人 @Hermes 時,若 active session 存在,Hermes 優先回應:
「@bob 我正在和 @alice 進行多輪對話(ses-0924-alice · turn 2),若需要我先處理你的問題請輸入 /interrupt」
3. /interrupt 命令會在 @alice 的下一則回覆中附注「⚠ @bob 已插隊」
4. session TTL 到期(5 min 無活動)自動釋放,@bob 再問就能直接對話
1. 每則 Hermes 回覆都帶
ctx-lock badge(ses-xxxx-alice · turn N)2. 其他人 @Hermes 時,若 active session 存在,Hermes 優先回應:
「@bob 我正在和 @alice 進行多輪對話(ses-0924-alice · turn 2),若需要我先處理你的問題請輸入 /interrupt」
3. /interrupt 命令會在 @alice 的下一則回覆中附注「⚠ @bob 已插隊」
4. session TTL 到期(5 min 無活動)自動釋放,@bob 再問就能直接對話
Compaction 觸發策略
────────────────────────────────
turn ≤ 4: 原始 context 傳 LLM
turn = 5: 自動壓縮成 "summary" node
- 保留最近 2 輪完整訊息
- 其餘壓縮成 1 段摘要
- 節省 token ~60%
turn > 10: 強制壓縮,清除最早 5 輪
turn > 20: 強制結束 session,提示 @alice
「對話過長,請重新開始」
F3 — Hermes NL 危險動作拒絕 → 引導 Approval
主線:kubectl mutate → 拒絕 → 建立 Approval
H
SRE 指揮中心
意圖分類規則 — 拒絕 vs 允許
| Pattern | 分類 | 處理 |
|---|---|---|
kubectl get/describe/logs | read:k8s | 直接執行 |
kubectl rollout/delete/scale | mutate:k8s | 拒絕 → Approval |
docker rm/stop | mutate:docker | 拒絕 → Approval |
psql SELECT | read:db | 直接執行 |
psql DROP/DELETE/UPDATE | mutate:db | 拒絕 → Approval |
git push --force | danger:git | 硬拒絕,不提供 Approval |
rm -rf | danger:shell | 硬拒絕,不提供 Approval |
F3 狀態機
NL 指令分類狀態機
────────────────────────────────────────
[INPUT] @Hermes {自然語言}
│
├─ 意圖分類 (Layer 2 LLM)
│ ├─ read:* → 執行 → 回覆結果
│ ├─ mutate:* → [APPROVAL_GATE] (見 F1 流程)
│ └─ danger:* → 硬拒絕
│
[APPROVAL_GATE]
├─ 組裝 approval_payload {指令, 目標, 請求人}
├─ 建立 ApprovalRecord (status=pending)
├─ sendMessage 告警卡到群組
└─ 等待簽核 → 執行 → 回報 @bob
[HARD_REJECT] danger:*
├─ 不建立 ApprovalRecord
├─ 回覆:「此操作不被支援,理由:...」
└─ 記錄 audit_log (event=hard_reject)
異常:LLM 分類失敗
├─ fallback → 視為 mutate:unknown → Approval
└─ 不直接執行
F4 — 直接 @ 特定 Agent(跳過 Layer 2 分類)
主線:@hermes-critic 直接派送
H
SRE 指揮中心
路由規則:訊息 text 以
@hermes-{agent} 開頭 → 解析 agent name → 直接派送,
不經過 Layer 2 LLM 意圖分類。降低延遲約 1-2s。
12 Agent 規格(TG handle / emoji / hashtag)
| Agent | Handle | Emoji | Hashtag |
|---|---|---|---|
| critic | @hermes-critic | 🔍 | #審查 |
| vuln-verifier | @hermes-verifier | 🎯 | #漏洞驗證 |
| debugger | @hermes-debugger | 🐛 | #除錯 |
| db-expert | @hermes-db | 💾 | #資料庫 |
| planner | @hermes-planner | 📋 | #拆解 |
| fullstack-engineer | @hermes-engineer | 🛠️ | #工程 |
| frontend-designer | @hermes-designer | 🎨 | #設計 |
| refactor-specialist | @hermes-refactor | ♻️ | #重構 |
| migration-engineer | @hermes-migration | 🚚 | #升級 |
| onboarder | @hermes-onboarder | 🗺️ | #導覽 |
| tool-expert | @hermes-tools | 🧰 | #工具 |
| web-researcher | @hermes-web | 📚 | #文檔 |
回覆格式:第一行必須是
{emoji} #{hashtag} — {任務摘要},
讓群組成員一眼判斷是哪個 agent 在工作。
F5 — Agent 鏈式對話(debugger → db-expert 交棒)
主線:鏈式派遣 + 合併回覆
H
SRE 指揮中心
並派 UI 規範 — 多 Agent 同時答
視覺組織原則:
當 2-3 個 agent 同時有結論時,合併為單一訊息(
禁止:連發 3 條訊息(噪音太高), 也禁止把不同 agent 輸出混成一段(無法溯源)。
例外:若兩個 agent 回答時間差 > 30s, 則各發各的,不強制合併。
當 2-3 個 agent 同時有結論時,合併為單一訊息(
multi-agent-bubble),
各 agent 的輸出用分隔線隔開,每節開頭寫 {emoji} #{hashtag} — {小標題}。
禁止:連發 3 條訊息(噪音太高), 也禁止把不同 agent 輸出混成一段(無法溯源)。
例外:若兩個 agent 回答時間差 > 30s, 則各發各的,不強制合併。
Agent 鏈式狀態機
────────────────────────────────
[START] 使用者 @hermes-debugger 查 bug
│
[AGENT_A_WORKING] debugger 分析
│
[AGENT_A_RESULT] 發現需要 DB 專家
├─ 詢問使用者:要派 @hermes-db?
│
[USER_CONFIRM] 點 [好]
│
[PARALLEL_DISPATCH]
├─ debugger (已有結論)
└─ db-expert (新派)
│
[MERGE_WAIT] 等待兩個結果
├─ 全部到齊 (≤30s) → 合併回覆
└─ 超時 30s → 先發先到的,
其他補發
[DONE] 合併訊息發送
cost = sum(agent_costs)
Redis Session 結構 — F5 鏈式
KEY: hermes:chain:{session_id}
TTL: 120s
────────────────────────────────
{
"session_id": "ses-1033-alice",
"primary_agent": "debugger",
"chain": [
{
"agent": "debugger",
"status": "done",
"result": "approval_records 全表掃描...",
"cost_usd": 0.03
},
{
"agent": "db-expert",
"status": "working",
"dispatched_at": 1745499209
}
],
"merge_timeout": 30, ← 秒
"pending_merge": true
}
F6 — 錯誤處理 UX(4 種異常 × 完整序列)
E1 — LLM Timeout
H
SRE 指揮中心
重試按鈕:相同 prompt,最多 2 次。第 3 次失敗後改顯示「請稍後再試」並通知 admin。
E2 — Prompt Injection 偵測
H
SRE 指揮中心
重要:不顯示「偵測到什麼」,只說「已記錄,不執行」。
詳細資訊寫入 audit_log,不洩漏偵測邏輯給攻擊者。
E3 — Rate Limit 觸發
H
SRE 指揮中心
KEY: rate_limit:{chat_id}
TTL: 60s (滑動視窗)
值:{count, queue: [{msg, ts}, ...]}
當 count > 10:
- 新訊息加入 queue
- 回傳 rate_limit 提示
- 60s 後 token 重置,按序發送
E4 — Agent 分派失敗 / 找不到合適專家
H
SRE 指揮中心
「預設 debugger 兜底」規則:僅適用於 SRE 域內但分類不確定 的問題。
完全超域(如採購、HR、財務)不能硬塞給 debugger,要清楚說「超出範圍」。
F6 全局錯誤處理狀態機
全局錯誤處理決策樹
───────────────────────────────────────────
[任何輸入]
│
├─ 注入偵測 (Layer 0, 規則引擎)
│ └─ score ≥ 0.9 → 硬拒絕 → 記錄 sec event
│
├─ Rate limit (Redis token bucket)
│ └─ 超出 → 佇列延遲 → 回傳佇列通知
│
├─ 意圖分類 (Layer 2 LLM)
│ ├─ LLM timeout (30s)
│ │ └─ retry × 2 → fallback 錯誤訊息
│ ├─ confidence < 0.6
│ │ └─ 詢問澄清:「你是想查詢還是執行?」
│ └─ domain mismatch
│ └─ 超域拒絕(不用 debugger 兜底)
│
├─ Agent 執行
│ ├─ agent timeout (60s)
│ │ └─ 回「思考超時」+ retry
│ └─ agent error
│ └─ 回「執行失敗」+ 詳情 + retry
│
└─ Telegram API
├─ editMessage 400 (訊息過期)
│ └─ 靜默跳過,繼續 sendMessage
└─ sendMessage 429 (flood)
└─ 等待 retry_after 秒後重試
F7 — 新成員加入群組(普通成員 vs 簽核人員)
主線 A — 普通成員加入
H
SRE 指揮中心
主線 B — Approver 白名單成員加入
H
SRE 指揮中心
私訊補充(僅 Approver):
Hermes 額外私訊 @carol: 「你有 1 件待簽核的 P0 告警(#incident-2404-0091), 已等待 2h14m。請前往群組或點此查看:[查看告警]」
Hermes 額外私訊 @carol: 「你有 1 件待簽核的 P0 告警(#incident-2404-0091), 已等待 2h14m。請前往群組或點此查看:[查看告警]」
F7 狀態機 + Redis 結構
新成員事件處理狀態機
────────────────────────────────────────
[EVENT] chat_member_updated
│
├─ new_member.status == "member"? (not kicked/left)
│ └─ NO → 忽略
│
└─ YES → 查詢去重鎖
KEY: hermes:welcomed:{chat_id}:{user_id}
TTL: 86400s (24h)
│
├─ 存在 → 去重,不重複歡迎
└─ 不存在 →
├─ 查 approvers 白名單
│ └─ Redis KEY: hermes:approvers:{chat_id}
│ set of user_ids, TTL: 3600s
│
├─ 組裝歡迎訊息(普通 or Approver 版)
├─ sendMessage 到群組
├─ [Approver only] 私訊待簽核告警
└─ SET hermes:welcomed:{chat_id}:{user_id} 1 TTL=86400s
去重設計說明:
Telegram 可能重複發 chat_member 事件(網路重試)
用 Redis 去重鎖確保 24h 內只歡迎一次
Commands 指令列表(群組可用)
| 指令 | 說明 |
|---|---|
/agents | 列出全部 12 agent + 一句話說明 |
/status | 今日告警統計(P0/P1/P2 數量) |
/pending | 列出待簽核 Approval(僅 Approver) |
/interrupt | 插隊進入他人的多輪 session |
/session | 查看目前活躍的 session(我自己的) |
/end | 主動結束自己的 session |
/cost | 本月 AI 成本統計(僅 admin) |