feat(agent): complete EvoAgent integration for all 6 agent roles
Migrate all agent roles from Legacy to EvoAgent architecture: - fundamentals_analyst, technical_analyst, sentiment_analyst, valuation_analyst - risk_manager, portfolio_manager Key changes: - EvoAgent now supports Portfolio Manager compatibility methods (_make_decision, get_decisions, get_portfolio_state, load_portfolio_state, update_portfolio) - Add UnifiedAgentFactory for centralized agent creation - ToolGuard with batch approval API and WebSocket broadcast - Legacy agents marked deprecated (AnalystAgent, RiskAgent, PMAgent) - Remove backend/agents/compat.py migration shim - Add run_id alongside workspace_id for semantic clarity - Complete integration test coverage (13 tests) - All smoke tests passing for 6 agent roles Constraint: Must maintain backward compatibility with existing run configs Constraint: Memory support must work with EvoAgent (no fallback to Legacy) Rejected: Separate PM implementation for EvoAgent | unified approach cleaner Confidence: high Scope-risk: broad Directive: EVO_AGENT_IDS env var still respected but defaults to all roles Not-tested: Kubernetes sandbox mode for skill execution
This commit is contained in:
@@ -19,13 +19,31 @@ agent_factory: AgentFactory | None = None
|
||||
workspace_manager: WorkspaceManager | None = None
|
||||
|
||||
|
||||
def _build_scope_payload(project_root: Path) -> dict[str, object]:
|
||||
return {
|
||||
"design_time_registry": {
|
||||
"root": str(project_root / "workspaces"),
|
||||
"meaning": "Persistent control-plane workspace registry",
|
||||
},
|
||||
"runtime_assets": {
|
||||
"root": str(project_root / "runs"),
|
||||
"meaning": "Run-scoped runtime state and agent assets",
|
||||
},
|
||||
"agent_route_note": (
|
||||
"On `/api/workspaces/{workspace_id}/agents/...`, design-time CRUD "
|
||||
"routes still use `workspaces/`, while profile/skills/file routes "
|
||||
"use `workspace_id` as a run id under `runs/<run_id>/`."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def create_app(project_root: Path | None = None) -> FastAPI:
|
||||
"""Create the agent control-plane app."""
|
||||
resolved_project_root = project_root or Path(__file__).resolve().parents[2]
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"""Initialize workspace and registry state for the control plane."""
|
||||
"""Initialize design-time workspace and registry state for the control plane."""
|
||||
global agent_factory, workspace_manager
|
||||
|
||||
workspace_manager = WorkspaceManager(project_root=resolved_project_root)
|
||||
@@ -34,7 +52,7 @@ def create_app(project_root: Path | None = None) -> FastAPI:
|
||||
|
||||
registry = get_registry()
|
||||
print("✓ 大时代 API started")
|
||||
print(f" - Workspaces root: {agent_factory.workspaces_root}")
|
||||
print(f" - Design workspaces root: {agent_factory.workspaces_root}")
|
||||
print(f" - Registered agents: {registry.get_agent_count()}")
|
||||
|
||||
yield
|
||||
@@ -63,6 +81,7 @@ def create_app(project_root: Path | None = None) -> FastAPI:
|
||||
if workspace_manager
|
||||
else 0
|
||||
),
|
||||
"scope_roots": _build_scope_payload(resolved_project_root),
|
||||
}
|
||||
|
||||
@app.get("/api/status")
|
||||
@@ -72,6 +91,7 @@ def create_app(project_root: Path | None = None) -> FastAPI:
|
||||
return {
|
||||
"status": "operational",
|
||||
"registry": registry.get_stats(),
|
||||
"scope": _build_scope_payload(resolved_project_root),
|
||||
}
|
||||
|
||||
app.include_router(workspaces_router)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Read-only OpenClaw CLI FastAPI surface."""
|
||||
"""Read-only OpenClaw CLI FastAPI surface.
|
||||
|
||||
COMPATIBILITY_SURFACE: deferred
|
||||
OWNER: runtime-team
|
||||
SEE: docs/legacy-inventory.md#openclaw-dual-integration
|
||||
|
||||
This is the REST facade (port 8004) for OpenClaw integration.
|
||||
For the WebSocket gateway integration, see:
|
||||
- backend/services/gateway_openclaw_handlers.py
|
||||
- shared/client/openclaw_websocket_client.py
|
||||
|
||||
Key differences:
|
||||
- REST facade: typed Pydantic models, request/response, polling
|
||||
- WebSocket: event-driven, real-time updates, bidirectional
|
||||
|
||||
Decision needed: which surface becomes the long-term contract?
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import annotations
|
||||
from fastapi import FastAPI
|
||||
|
||||
from backend.api import runtime_router
|
||||
from backend.api.runtime import get_runtime_state
|
||||
from backend.api.runtime import get_runtime_state, _check_gateway_health, _get_gateway_process_details
|
||||
from backend.apps.cors import add_cors_middleware
|
||||
|
||||
|
||||
@@ -22,29 +22,57 @@ def create_app() -> FastAPI:
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check() -> dict[str, object]:
|
||||
"""Health check for the runtime service."""
|
||||
"""Health check for the runtime service with Gateway process status."""
|
||||
runtime_state = get_runtime_state()
|
||||
process = runtime_state.gateway_process
|
||||
process_details = _get_gateway_process_details()
|
||||
|
||||
is_running = process is not None and process.poll() is None
|
||||
|
||||
# Determine overall health status
|
||||
if is_running:
|
||||
status = "healthy"
|
||||
elif process is not None:
|
||||
# Process existed but exited
|
||||
status = "degraded"
|
||||
else:
|
||||
status = "healthy" # Service is healthy even without Gateway running
|
||||
|
||||
return {
|
||||
"status": "healthy",
|
||||
"status": status,
|
||||
"service": "runtime-service",
|
||||
"gateway_running": is_running,
|
||||
"gateway_port": runtime_state.gateway_port,
|
||||
"gateway": {
|
||||
"running": is_running,
|
||||
"port": runtime_state.gateway_port,
|
||||
"pid": process_details.get("pid"),
|
||||
"process_status": process_details.get("status"),
|
||||
"returncode": process_details.get("returncode"),
|
||||
},
|
||||
}
|
||||
|
||||
@app.get("/health/gateway")
|
||||
async def gateway_health_check() -> dict[str, object]:
|
||||
"""Detailed health check for the Gateway subprocess."""
|
||||
health = _check_gateway_health()
|
||||
return health
|
||||
|
||||
@app.get("/api/status")
|
||||
async def api_status() -> dict[str, object]:
|
||||
"""Service-level status payload for runtime orchestration."""
|
||||
runtime_state = get_runtime_state()
|
||||
process = runtime_state.gateway_process
|
||||
process_details = _get_gateway_process_details()
|
||||
|
||||
is_running = process is not None and process.poll() is None
|
||||
|
||||
return {
|
||||
"status": "operational",
|
||||
"service": "runtime-service",
|
||||
"runtime": {
|
||||
"gateway_running": is_running,
|
||||
"gateway_port": runtime_state.gateway_port,
|
||||
"gateway_pid": process_details.get("pid"),
|
||||
"gateway_process_status": process_details.get("status"),
|
||||
"has_runtime_manager": runtime_state.runtime_manager is not None,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user