feat(phase6-9): Complete modular architecture and Agent Teams

Phase 6.4 - Modular Architecture:
- Add lewooogo-brain adapters for LLM providers
- Add lewooogo-data dual memory (Redis + PostgreSQL)
- Implement consensus engine for multi-agent decisions
- Add incident memory service for historical context

Phase 9 - Agent Teams (Claude Agent SDK):
- Add base agent class with Claude Sonnet 4 integration
- Implement action planner, blast radius, and security agents
- Add agent API endpoints and proposal workflow
- Integrate ADR-009 OpenClaw Agent Teams architecture

DevOps & CI/CD:
- Add GitHub Actions CI/CD workflows (ci.yaml, cd.yaml)
- Add pre-commit hooks and secrets baseline
- Add docker-compose for local development
- Update Kubernetes network policies

Frontend Improvements:
- Add auto-healing error boundary component
- Update i18n messages for agent features
- Enhance dual-state incident card with execution feedback

Documentation:
- Add 7 ADRs covering MCP, design system, architecture decisions
- Update ARCHITECTURE_MEMORY.md with modular design
- Add GLOBAL_RULES.md and SOUL.md for project identity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-23 18:40:36 +08:00
parent 6eccb45757
commit 7478dc0254
169 changed files with 24613 additions and 247 deletions

View File

@@ -7,11 +7,19 @@ Endpoints:
- GET /health - Full health check with components
- GET /health/ready - K8s readinessProbe
- GET /health/live - K8s livenessProbe
統帥鐵律 2026-03-23:
- 禁止假數據 (必須真實連接資源)
- 每個檢查 2 秒超時
- 失敗不導致 API 崩潰
"""
import asyncio
import time
from datetime import datetime, timezone
from typing import Literal
import httpx
from fastapi import APIRouter
from pydantic import BaseModel
@@ -21,6 +29,9 @@ from src.core.logging import get_logger
router = APIRouter()
logger = get_logger("awoooi.health")
# Health check timeout (seconds)
HEALTH_CHECK_TIMEOUT = 2.0
class ComponentStatus(BaseModel):
"""Individual component status"""
@@ -39,6 +50,140 @@ class HealthResponse(BaseModel):
components: dict[str, Literal["up", "down", "degraded"]]
# =============================================================================
# Real Health Check Functions (統帥鐵律: 禁止假數據)
# =============================================================================
async def check_database() -> Literal["up", "down"]:
"""
Check PostgreSQL connection using asyncpg
統帥鐵律: 真實執行 SELECT 1禁止假數據
"""
try:
import asyncpg
# Parse DATABASE_URL for asyncpg (remove +asyncpg suffix)
db_url = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
conn = await asyncio.wait_for(
asyncpg.connect(db_url),
timeout=HEALTH_CHECK_TIMEOUT,
)
try:
result = await asyncio.wait_for(
conn.fetchval("SELECT 1"),
timeout=HEALTH_CHECK_TIMEOUT,
)
if result == 1:
logger.debug("health_check_database", status="up")
return "up"
else:
logger.warning("health_check_database", status="down", reason="unexpected_result")
return "down"
finally:
await conn.close()
except asyncio.TimeoutError:
logger.warning("health_check_database", status="down", reason="timeout")
return "down"
except Exception as e:
logger.warning("health_check_database", status="down", error=str(e))
return "down"
async def check_redis() -> Literal["up", "down"]:
"""
Check Redis connection using redis.ping()
統帥鐵律: 真實執行 PING禁止假數據
"""
try:
import redis.asyncio as redis_lib
# Create temporary connection for health check (avoid pool dependency)
client = redis_lib.from_url(
settings.REDIS_URL,
encoding="utf-8",
decode_responses=True,
socket_timeout=HEALTH_CHECK_TIMEOUT,
socket_connect_timeout=HEALTH_CHECK_TIMEOUT,
)
try:
result = await asyncio.wait_for(
client.ping(),
timeout=HEALTH_CHECK_TIMEOUT,
)
if result:
logger.debug("health_check_redis", status="up")
return "up"
else:
logger.warning("health_check_redis", status="down", reason="ping_failed")
return "down"
finally:
await client.close()
except asyncio.TimeoutError:
logger.warning("health_check_redis", status="down", reason="timeout")
return "down"
except Exception as e:
logger.warning("health_check_redis", status="down", error=str(e))
return "down"
async def check_ollama() -> Literal["up", "down"]:
"""
Check Ollama service via /api/tags endpoint
統帥鐵律: 真實 HTTP 請求,禁止假數據
"""
try:
async with httpx.AsyncClient(timeout=HEALTH_CHECK_TIMEOUT) as client:
response = await client.get(f"{settings.OLLAMA_URL}/api/tags")
if response.status_code == 200:
logger.debug("health_check_ollama", status="up")
return "up"
else:
logger.warning(
"health_check_ollama",
status="down",
status_code=response.status_code,
)
return "down"
except httpx.TimeoutException:
logger.warning("health_check_ollama", status="down", reason="timeout")
return "down"
except Exception as e:
logger.warning("health_check_ollama", status="down", error=str(e))
return "down"
async def check_openclaw() -> Literal["up", "down"]:
"""
Check OpenClaw service via /health endpoint
統帥鐵律: 真實 HTTP 請求,禁止假數據
"""
try:
async with httpx.AsyncClient(timeout=HEALTH_CHECK_TIMEOUT) as client:
response = await client.get(f"{settings.OPENCLAW_URL}/health")
if response.status_code == 200:
logger.debug("health_check_openclaw", status="up")
return "up"
else:
logger.warning(
"health_check_openclaw",
status="down",
status_code=response.status_code,
)
return "down"
except httpx.TimeoutException:
logger.warning("health_check_openclaw", status="down", reason="timeout")
return "down"
except Exception as e:
logger.warning("health_check_openclaw", status="down", error=str(e))
return "down"
@router.get("/health", response_model=HealthResponse)
async def get_health() -> HealthResponse:
"""
@@ -46,14 +191,34 @@ async def get_health() -> HealthResponse:
Returns overall system health and individual component statuses.
Used for monitoring dashboards and alerting.
統帥鐵律 2026-03-23: 禁止假數據,所有檢查必須真實連接
"""
# TODO: Implement actual async health checks
components = {
"api": "up",
"database": "up", # TODO: asyncpg ping
"redis": "up", # TODO: redis ping
"ollama": "up", # TODO: httpx check
"clawbot": "up", # TODO: httpx check
# API is always up if this endpoint responds
api_status: Literal["up", "down", "degraded"] = "up"
# Run all health checks concurrently with timeout protection
start_time = time.monotonic()
db_task = asyncio.create_task(check_database())
redis_task = asyncio.create_task(check_redis())
ollama_task = asyncio.create_task(check_ollama())
openclaw_task = asyncio.create_task(check_openclaw())
# Wait for all tasks (each has internal timeout)
db_status, redis_status, ollama_status, openclaw_status = await asyncio.gather(
db_task, redis_task, ollama_task, openclaw_task,
return_exceptions=False,
)
elapsed_ms = (time.monotonic() - start_time) * 1000
components: dict[str, Literal["up", "down", "degraded"]] = {
"api": api_status,
"database": db_status,
"redis": redis_status,
"ollama": ollama_status,
"openclaw": openclaw_status,
}
# Determine overall status
@@ -67,10 +232,11 @@ async def get_health() -> HealthResponse:
else:
overall_status = "healthy"
logger.debug(
logger.info(
"health_check",
status=overall_status,
components=components,
elapsed_ms=round(elapsed_ms, 2),
)
return HealthResponse(