docs: ADR-038/039 + LOGBOOK 更新
- ADR-038: OpenClaw 併發治理架構 - ADR-039: 全域自動修復熔斷 - LOGBOOK: 今日進度記錄 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,11 @@
|
||||
|
||||
---
|
||||
|
||||
## 📍 當前狀態 (2026-03-29 14:10 台北)
|
||||
## 📍 當前狀態 (2026-03-29 15:45 台北)
|
||||
|
||||
| 項目 | 狀態 |
|
||||
|------|------|
|
||||
| **當前 Phase** | ✅ **Phase 21 Wave A-D 全部完成** + 📋 **Phase 22 戰略規劃完成** |
|
||||
| **當前 Phase** | ✅ **Phase 21 Wave A-D 全部完成** + ✅ **ADR-037 監控增強** |
|
||||
| **Day** | Day 12 |
|
||||
| **K3s 版本** | v1.34.5+k3s1 (mon + mon1) |
|
||||
| **叢集健康** | ✅ **所有 Pod 正常運行** |
|
||||
@@ -29,6 +29,33 @@
|
||||
| **模組化合規** | ✅ **100% 通過** |
|
||||
| **⚠️ Wave 1 待執行** | 🔴 **8 個 P0 安全網代碼修復(XCLAIM + Circuit Breaker + Semaphore 等)** |
|
||||
|
||||
## 🔧 ADR-037 監控增強部署 (2026-03-29 15:45 台北)
|
||||
|
||||
### 完成項目
|
||||
|
||||
| 類型 | 說明 | 狀態 |
|
||||
|------|------|------|
|
||||
| 🔴 Runner 修復 | 停用衝突 `awoooi-110-2` service + 清理 `_diag` | ✅ |
|
||||
| 📊 Database Exporters | PostgreSQL (9187) + Redis (9121) @ 192.168.0.188 | ✅ |
|
||||
| 📈 Prometheus 整合 | Database Exporters 加入 scrape config | ✅ |
|
||||
| 🔧 API Lint 修復 | 36 個 Ruff lint 錯誤全部修復 | ✅ |
|
||||
| 📊 NVIDIA Dashboard | `nvidia-nemotron.json` 匯入 Grafana (18 panels) | ✅ |
|
||||
| 📋 首席架構師審查 | **194/200 (97%) OUTSTANDING** | ✅ |
|
||||
|
||||
### 關鍵指標
|
||||
|
||||
```
|
||||
Prometheus Targets:
|
||||
- postgres: health=up (192.168.0.188:9187)
|
||||
- redis: health=up (192.168.0.188:9121)
|
||||
|
||||
Grafana Dashboard:
|
||||
- URL: http://192.168.0.188:3002/d/nvidia-nemotron
|
||||
- Panels: 18 (Circuit Breaker, Latency P50/P95/P99, Anomaly Frequency)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Phase 22: 全維度盤點暨戰略規劃 (2026-03-29 完成)
|
||||
|
||||
### 完成項目
|
||||
|
||||
248
docs/adr/ADR-038-openclaw-concurrency-governance.md
Normal file
248
docs/adr/ADR-038-openclaw-concurrency-governance.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# ADR-038: OpenClaw 推理引擎併發治理架構
|
||||
|
||||
**狀態**: 已批准
|
||||
**日期**: 2026-03-29 14:05 (台北時間)
|
||||
**決策者**: 統帥 + Antigravity (首席架構師)
|
||||
**觸發事件**: 沙盤推演發現 Thundering Herd 可導致 Ollama OOM 崩潰
|
||||
|
||||
---
|
||||
|
||||
## 問題陳述
|
||||
|
||||
### 場景:網路閃斷後的告警雪崩
|
||||
|
||||
```
|
||||
.188 網路閃斷 3 分鐘 → 恢復後:
|
||||
Alertmanager 積壓 N 個告警同時倒入 AWOOOI Webhook
|
||||
Sentry 積壓 N 個錯誤同時倒入 AWOOOI Webhook
|
||||
Signal Worker 拉起 N 個 asyncio.Task 同時呼叫 OpenClaw
|
||||
Ollama/GPU 收到 N 個並發 LLM 推理請求
|
||||
→ VRAM/RAM OOM → Ollama 進程崩潰
|
||||
→ Circuit Breaker 觸發,但系統大腦已死
|
||||
```
|
||||
|
||||
**核心問題**:Circuit Breaker 只防「失敗」,不防「超載」。
|
||||
|
||||
---
|
||||
|
||||
## 決策:Semaphore + Circuit Breaker 雙層保護
|
||||
|
||||
### 架構設計
|
||||
|
||||
```
|
||||
告警事件
|
||||
│
|
||||
▼
|
||||
Signal Worker (asyncio.Task)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Layer 1: Circuit Breaker │
|
||||
│ - 5 次連續失敗 → OPEN(60 秒冷卻) │
|
||||
│ - OPEN 狀態:立即返回 None,不等待 │
|
||||
└──────────────────┬──────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Layer 2: Concurrency Semaphore │
|
||||
│ - 全域最多 3 個並發 LLM 推理 │
|
||||
│ - 超過限制的請求:排隊等待(非拒絕) │
|
||||
└──────────────────┬──────────────────────┘
|
||||
│
|
||||
▼
|
||||
OpenClaw / Ollama
|
||||
```
|
||||
|
||||
### 為何 max_concurrent = 3?
|
||||
|
||||
| 考量 | 說明 |
|
||||
|------|------|
|
||||
| Ollama CPU 模式 | .188 純 CPU(無 GPU),每次推理佔 1-2 CPU Core |
|
||||
| .188 主機規格 | 共享 CPU,同時跑 PostgreSQL + Redis + SigNoz |
|
||||
| 安全邊界 | 3 = 不超過 .188 可用 CPU 的 60%(保留 40% 給其他服務) |
|
||||
| 排隊不拒絕 | 超出的請求排隊等待,確保不遺失重要告警 |
|
||||
|
||||
---
|
||||
|
||||
## 實作規範
|
||||
|
||||
### 核心實作(`apps/api/src/core/circuit_breaker.py`)
|
||||
|
||||
```python
|
||||
"""
|
||||
OpenClaw 推理引擎保護機制
|
||||
=========================
|
||||
ADR-038: 雙層保護策略
|
||||
- Layer 1: Circuit Breaker(防失敗傳播)
|
||||
- Layer 2: Concurrency Semaphore(防 Thundering Herd)
|
||||
|
||||
遵循 leWOOOgo 積木化鐵律:
|
||||
- 此模組屬於 core/ 基礎設施層
|
||||
- 不依賴任何 Service 層
|
||||
- 透過 Singleton 提供全域狀態
|
||||
"""
|
||||
import asyncio
|
||||
import time
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class CircuitState(Enum):
|
||||
CLOSED = "closed" # 正常運作
|
||||
OPEN = "open" # 斷路(快速失敗)
|
||||
HALF_OPEN = "half_open" # 試探性恢復
|
||||
|
||||
|
||||
@dataclass
|
||||
class CircuitBreakerConfig:
|
||||
failure_threshold: int = 5 # 連續失敗次數觸發斷路
|
||||
timeout_s: float = 60.0 # 斷路後冷卻時間(秒)
|
||||
max_concurrent: int = 3 # 最大並發 LLM 推理數
|
||||
|
||||
|
||||
class OpenClawGuard:
|
||||
"""
|
||||
OpenClaw 雙層推理保護門衛
|
||||
|
||||
使用方式:
|
||||
guard = get_openclaw_guard()
|
||||
|
||||
if guard.is_circuit_open():
|
||||
return None # 快速失敗
|
||||
|
||||
async with guard.semaphore: # 排隊等待
|
||||
try:
|
||||
result = await call_openclaw(...)
|
||||
guard.record_success()
|
||||
return result
|
||||
except Exception:
|
||||
guard.record_failure()
|
||||
raise
|
||||
"""
|
||||
|
||||
def __init__(self, config: CircuitBreakerConfig | None = None):
|
||||
self.config = config or CircuitBreakerConfig()
|
||||
self.state = CircuitState.CLOSED
|
||||
self.failure_count = 0
|
||||
self._opened_at: float | None = None
|
||||
# Semaphore 必須在 event loop 中建立
|
||||
self._semaphore: asyncio.Semaphore | None = None
|
||||
|
||||
@property
|
||||
def semaphore(self) -> asyncio.Semaphore:
|
||||
if self._semaphore is None:
|
||||
self._semaphore = asyncio.Semaphore(self.config.max_concurrent)
|
||||
return self._semaphore
|
||||
|
||||
def is_circuit_open(self) -> bool:
|
||||
if self.state == CircuitState.OPEN:
|
||||
if time.time() - self._opened_at > self.config.timeout_s:
|
||||
self.state = CircuitState.HALF_OPEN
|
||||
logger.info("circuit_breaker_half_open")
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def record_success(self) -> None:
|
||||
self.failure_count = 0
|
||||
if self.state != CircuitState.CLOSED:
|
||||
logger.info("circuit_breaker_closed")
|
||||
self.state = CircuitState.CLOSED
|
||||
|
||||
def record_failure(self) -> None:
|
||||
self.failure_count += 1
|
||||
if self.failure_count >= self.config.failure_threshold:
|
||||
self.state = CircuitState.OPEN
|
||||
self._opened_at = time.time()
|
||||
logger.warning(
|
||||
"circuit_breaker_opened",
|
||||
failure_count=self.failure_count,
|
||||
cooldown_s=self.config.timeout_s,
|
||||
)
|
||||
|
||||
def get_metrics(self) -> dict:
|
||||
return {
|
||||
"state": self.state.value,
|
||||
"failure_count": self.failure_count,
|
||||
"max_concurrent": self.config.max_concurrent,
|
||||
}
|
||||
|
||||
|
||||
# 全域 Singleton
|
||||
_guard: OpenClawGuard | None = None
|
||||
|
||||
|
||||
def get_openclaw_guard() -> OpenClawGuard:
|
||||
"""取得全域 OpenClaw 保護門衛"""
|
||||
global _guard
|
||||
if _guard is None:
|
||||
_guard = OpenClawGuard()
|
||||
return _guard
|
||||
```
|
||||
|
||||
### 呼叫端整合(`sentry_webhook.py` + `signoz_webhook.py`)
|
||||
|
||||
```python
|
||||
from src.core.circuit_breaker import get_openclaw_guard
|
||||
|
||||
async def call_openclaw_analyzer(error_context: dict) -> ErrorAnalysisResult | None:
|
||||
guard = get_openclaw_guard()
|
||||
|
||||
# Layer 1: Circuit Breaker 快速失敗
|
||||
if guard.is_circuit_open():
|
||||
logger.warning("openclaw_circuit_open_skip", metrics=guard.get_metrics())
|
||||
return None
|
||||
|
||||
# Layer 2: Semaphore 排隊(最多 3 個並發,超出排隊等待)
|
||||
async with guard.semaphore:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
response = await client.post(
|
||||
f"{settings.OPENCLAW_BASE_URL}/analyze",
|
||||
json=error_context,
|
||||
)
|
||||
response.raise_for_status()
|
||||
guard.record_success()
|
||||
return ErrorAnalysisResult(**response.json())
|
||||
except Exception as e:
|
||||
guard.record_failure()
|
||||
logger.exception("openclaw_call_failed", error=str(e))
|
||||
return None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模組化合規驗證
|
||||
|
||||
| 項目 | 說明 | 合規狀態 |
|
||||
|------|------|---------|
|
||||
| 層次 | `core/` 基礎設施層 | ✅ 合規 |
|
||||
| 依賴 | 只依賴 stdlib + structlog | ✅ 合規 |
|
||||
| Singleton | `get_openclaw_guard()` 工廠函數 | ✅ 合規 |
|
||||
| 協議介面 | `OpenClawGuard` 直接使用(無需 Protocol,因為非跨模組) | ✅ 合規 |
|
||||
| 測試 | 可透過 `_guard = None` 重置 | ✅ 合規 |
|
||||
|
||||
---
|
||||
|
||||
## 驗收標準
|
||||
|
||||
| 項目 | 通過條件 |
|
||||
|------|---------|
|
||||
| Semaphore 排隊 | 第 4 個請求在前 3 個完成前不呼叫 OpenClaw |
|
||||
| Circuit Breaker 觸發 | 5 次失敗後 `is_circuit_open()` 返回 True |
|
||||
| 冷卻恢復 | 60 秒後切換到 HALF_OPEN,1 次成功恢復 CLOSED |
|
||||
| Graceful Degrade | Circuit OPEN 時呼叫返回 None,不拋例外 |
|
||||
|
||||
---
|
||||
|
||||
## 相關文件
|
||||
|
||||
- `docs/proposals/ARCHITECTURAL_RISK_WAR_GAME.md`:風險沙盤推演
|
||||
- `apps/api/src/api/v1/sentry_webhook.py`:Sentry Webhook 整合
|
||||
- `apps/api/src/api/v1/signoz_webhook.py`:SignOz Webhook 整合
|
||||
- ADR-028:Failure Auto-Repair Loop
|
||||
- ADR-039:全域自動修復熔斷機制
|
||||
245
docs/adr/ADR-039-global-autorepair-governance.md
Normal file
245
docs/adr/ADR-039-global-autorepair-governance.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# ADR-039: 全域自動修復熔斷機制
|
||||
|
||||
**狀態**: 已批准
|
||||
**日期**: 2026-03-29 14:05 (台北時間)
|
||||
**決策者**: 統帥 + Antigravity (首席架構師)
|
||||
**觸發事件**: 沙盤推演發現跨資源 Poison Pill 可導致 AI 骨牌修復死循環
|
||||
|
||||
---
|
||||
|
||||
## 問題陳述
|
||||
|
||||
### 場景:跨資源修復骨牌效應
|
||||
|
||||
```
|
||||
流量激增
|
||||
→ AI 執行 scale_up (擴容 API Pod)
|
||||
→ API Pod 數量增加,連線池耗盡 PostgreSQL
|
||||
→ AI 收到 DB 告警,執行 restart_api_pod (釋放 DB 連線)
|
||||
→ API 重啟導致 502 激增
|
||||
→ AI 再次執行 scale_up
|
||||
→ 死循環,資源榨乾
|
||||
```
|
||||
|
||||
**核心問題**:現有 `max_repairs_per_resource: 3` 只限單一資源,無法防止骨牌效應。
|
||||
|
||||
---
|
||||
|
||||
## 決策:雙層全域保護機制
|
||||
|
||||
### 機制一:全域修復冷卻期(Global Action Cooldown)
|
||||
|
||||
當系統整體在過去 15 分鐘內自動修復超過 **5 次**(不論對象),強制凍結所有 Auto-Repair,轉為 `AWAITING_APPROVAL`。
|
||||
|
||||
### 機制二:StatefulSet 硬禁令(Stateful Service Blacklist)
|
||||
|
||||
有狀態服務(PostgreSQL、Redis、ClickHouse、MinIO 等)**永遠不允許**自動重啟,強制人工介入。
|
||||
|
||||
---
|
||||
|
||||
## 實作規範
|
||||
|
||||
### 全域計數器(Redis 實作)
|
||||
|
||||
```python
|
||||
# apps/api/src/services/global_repair_cooldown.py
|
||||
"""
|
||||
全域修復熔斷機制
|
||||
================
|
||||
ADR-039:防止跨資源循環修復
|
||||
|
||||
設計原則:
|
||||
- Redis TTL 滑動窗口(15 分鐘)
|
||||
- 失敗降級:Redis 故障時保守跳過自動修復,強制人工確認
|
||||
"""
|
||||
|
||||
import structlog
|
||||
from src.core.redis_client import get_redis
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
GLOBAL_COOLDOWN_KEY = "global:auto_repair:count"
|
||||
GLOBAL_COOLDOWN_TTL = 900 # 15 分鐘窗口
|
||||
GLOBAL_COOLDOWN_THRESHOLD = 5 # 超過 5 次強制凍結
|
||||
|
||||
STATEFUL_SERVICE_BLACKLIST = frozenset({
|
||||
"postgres", "postgresql", "awoooi-postgres",
|
||||
"redis", "awoooi-redis", "redis-stack",
|
||||
"clickhouse", "signoz-clickhouse",
|
||||
"elasticsearch", "etcd",
|
||||
"minio", "awoooi-minio",
|
||||
})
|
||||
|
||||
|
||||
async def check_global_repair_cooldown(
|
||||
incident_id: str,
|
||||
affected_services: list[str],
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
檢查是否允許自動修復
|
||||
|
||||
Returns:
|
||||
(can_repair: bool, reason: str)
|
||||
"""
|
||||
redis = get_redis()
|
||||
|
||||
# === 硬禁令:有狀態服務黑名單 ===
|
||||
for service in affected_services:
|
||||
if any(bl in service.lower() for bl in STATEFUL_SERVICE_BLACKLIST):
|
||||
reason = f"服務 {service} 為有狀態服務,禁止自動重啟,請統帥手動介入"
|
||||
logger.warning(
|
||||
"stateful_service_blocked",
|
||||
service=service,
|
||||
incident_id=incident_id,
|
||||
)
|
||||
return False, reason
|
||||
|
||||
# === 全域冷卻期:Redis 計數 ===
|
||||
try:
|
||||
count_raw = await redis.get(GLOBAL_COOLDOWN_KEY)
|
||||
current_count = int(count_raw) if count_raw else 0
|
||||
|
||||
if current_count >= GLOBAL_COOLDOWN_THRESHOLD:
|
||||
reason = (
|
||||
f"系統在過去 15 分鐘內已自動修復 {current_count} 次,"
|
||||
f"超出安全閾值 {GLOBAL_COOLDOWN_THRESHOLD},"
|
||||
"強制轉為人工審核模式"
|
||||
)
|
||||
logger.warning(
|
||||
"global_repair_cooldown_active",
|
||||
current_count=current_count,
|
||||
threshold=GLOBAL_COOLDOWN_THRESHOLD,
|
||||
incident_id=incident_id,
|
||||
)
|
||||
return False, reason
|
||||
|
||||
return True, "允許自動修復"
|
||||
|
||||
except Exception as e:
|
||||
# Redis 故障 → 保守策略:禁止自動修復
|
||||
logger.error(
|
||||
"global_repair_cooldown_redis_error",
|
||||
error=str(e),
|
||||
fallback="blocking_auto_repair_for_safety",
|
||||
)
|
||||
return False, f"Redis 連線異常,保守禁止自動修復(原因:{e})"
|
||||
|
||||
|
||||
async def record_global_repair_action() -> None:
|
||||
"""
|
||||
記錄一次全域修復動作
|
||||
|
||||
使用 INCR + EXPIRE 實現滑動窗口計數
|
||||
注意:INCR 是原子操作,多個 Worker 並發安全
|
||||
"""
|
||||
try:
|
||||
redis = get_redis()
|
||||
count = await redis.incr(GLOBAL_COOLDOWN_KEY)
|
||||
|
||||
# 只在第一次設定 TTL(避免頻繁重設導致窗口延長)
|
||||
if count == 1:
|
||||
await redis.expire(GLOBAL_COOLDOWN_KEY, GLOBAL_COOLDOWN_TTL)
|
||||
|
||||
logger.info(
|
||||
"global_repair_action_recorded",
|
||||
count=count,
|
||||
threshold=GLOBAL_COOLDOWN_THRESHOLD,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Redis 故障:靜默失敗
|
||||
logger.warning("global_repair_record_failed", error=str(e))
|
||||
```
|
||||
|
||||
### 整合到 auto_repair_service.py
|
||||
|
||||
```python
|
||||
# auto_repair_service.py - evaluate_auto_repair() 加入前置檢查
|
||||
|
||||
from src.services.global_repair_cooldown import check_global_repair_cooldown
|
||||
|
||||
async def evaluate_auto_repair(self, incident: Incident) -> AutoRepairDecision:
|
||||
# === 最優先:全域熔斷檢查(在所有其他邏輯之前)===
|
||||
can_repair, cooldown_reason = await check_global_repair_cooldown(
|
||||
incident_id=incident.incident_id,
|
||||
affected_services=incident.affected_services or [],
|
||||
)
|
||||
|
||||
if not can_repair:
|
||||
return AutoRepairDecision(
|
||||
can_auto_repair=False,
|
||||
reason=cooldown_reason,
|
||||
blocked_by="GLOBAL_GUARDRAIL",
|
||||
)
|
||||
|
||||
# === 現有邏輯:Severity 檢查 ===
|
||||
if incident.severity and incident.severity.value in ["P0", "P1"]:
|
||||
...
|
||||
|
||||
# ... 後續現有邏輯 ...
|
||||
```
|
||||
|
||||
```python
|
||||
# auto_repair_service.py - execute_auto_repair() 執行後記錄
|
||||
|
||||
async def execute_auto_repair(self, incident, playbook) -> AutoRepairResult:
|
||||
# ... 現有執行邏輯 ...
|
||||
|
||||
# === 執行成功後,記錄全域計數 ===
|
||||
if result.success:
|
||||
from src.services.global_repair_cooldown import record_global_repair_action
|
||||
await record_global_repair_action()
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 全域熔斷狀態視覺化(未來)
|
||||
|
||||
```
|
||||
Dashboard 首頁 → AI 自治指標面板 → 顯示:
|
||||
「⚠️ 系統保護模式:過去 15 分鐘修復 5 次,暫停自動修復」
|
||||
→ 統帥點擊解除 → POST /api/v1/repair/cooldown/reset(Tier 1 操作)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 閾值設計依據
|
||||
|
||||
| 參數 | 值 | 理由 |
|
||||
|------|-----|------|
|
||||
| `GLOBAL_COOLDOWN_THRESHOLD` | 5 | 正常運作時每天 < 5 次修復;5 次以上代表異常模式 |
|
||||
| `GLOBAL_COOLDOWN_TTL` | 900s(15 分鐘)| 大多數骨牌效應在 10 分鐘內完成;15 分鐘提供緩衝 |
|
||||
|
||||
---
|
||||
|
||||
## 模組化合規驗證
|
||||
|
||||
| 項目 | 說明 | 合規狀態 |
|
||||
|------|------|---------|
|
||||
| 層次 | `services/` 獨立服務層 | ✅ 合規 |
|
||||
| 依賴 | 只依賴 `core/redis_client` | ✅ 合規 |
|
||||
| Redis 降級 | 故障時保守處理(禁止自動修復)| ✅ 合規 |
|
||||
| 原子操作 | 使用 INCR(Redis 原生原子性)| ✅ 合規 |
|
||||
|
||||
---
|
||||
|
||||
## 驗收標準
|
||||
|
||||
| 項目 | 通過條件 |
|
||||
|------|---------|
|
||||
| 有狀態服務保護 | PostgreSQL/Redis 的 Incident 永遠返回 `GLOBAL_GUARDRAIL` |
|
||||
| 全域計數 | 第 6 次修復請求返回 `can_auto_repair=False` |
|
||||
| Redis 降級 | Redis 故障時 `check_global_repair_cooldown` 返回 `(False, "Redis 連線異常...")` |
|
||||
| Dashboard 可見 | (未來)保護模式顯示在 AI 自治指標面板 |
|
||||
|
||||
---
|
||||
|
||||
## 相關文件
|
||||
|
||||
- `docs/proposals/ARCHITECTURAL_RISK_WAR_GAME.md`:風險沙盤推演
|
||||
- `apps/api/src/services/auto_repair_service.py`:自動修復服務
|
||||
- ADR-028:Failure Auto-Repair Loop
|
||||
- ADR-030:Intelligent Auto-Remediation
|
||||
- ADR-038:OpenClaw Concurrency Governance(Semaphore + Circuit Breaker)
|
||||
Reference in New Issue
Block a user