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
198 lines
5.5 KiB
Python
198 lines
5.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Workspace API Routes.
|
|
|
|
These routes manage the design-time `workspaces/` registry, not the run-scoped
|
|
runtime data under `runs/<run_id>/`.
|
|
"""
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
from pydantic import BaseModel, Field
|
|
|
|
from backend.agents import WorkspaceManager
|
|
|
|
router = APIRouter(prefix="/api/workspaces", tags=["workspaces"])
|
|
|
|
|
|
# Request/Response Models
|
|
class CreateWorkspaceRequest(BaseModel):
|
|
"""Request to create a new workspace."""
|
|
workspace_id: str = Field(..., description="Unique workspace identifier")
|
|
name: Optional[str] = Field(None, description="Display name")
|
|
description: Optional[str] = Field(None, description="Workspace description")
|
|
metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
|
|
|
|
|
|
class UpdateWorkspaceRequest(BaseModel):
|
|
"""Request to update a workspace."""
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
metadata: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class WorkspaceResponse(BaseModel):
|
|
"""Design-time workspace information response."""
|
|
workspace_id: str
|
|
name: str
|
|
description: str
|
|
created_at: Optional[str] = None
|
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class WorkspaceListResponse(BaseModel):
|
|
"""List of workspaces response."""
|
|
workspaces: List[WorkspaceResponse]
|
|
total: int
|
|
|
|
|
|
# Dependencies
|
|
def get_workspace_manager():
|
|
"""Get WorkspaceManager instance."""
|
|
return WorkspaceManager()
|
|
|
|
|
|
# Routes
|
|
@router.post("", response_model=WorkspaceResponse)
|
|
async def create_workspace(
|
|
request: CreateWorkspaceRequest,
|
|
manager: WorkspaceManager = Depends(get_workspace_manager),
|
|
):
|
|
"""
|
|
Create a new workspace.
|
|
|
|
Args:
|
|
request: Workspace creation parameters
|
|
|
|
Returns:
|
|
Created workspace information
|
|
"""
|
|
try:
|
|
config = manager.create_workspace(
|
|
workspace_id=request.workspace_id,
|
|
name=request.name,
|
|
description=request.description,
|
|
metadata=request.metadata or {},
|
|
)
|
|
return WorkspaceResponse(
|
|
workspace_id=config.workspace_id,
|
|
name=config.name,
|
|
description=config.description,
|
|
created_at=config.created_at,
|
|
metadata=config.metadata,
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.get("", response_model=WorkspaceListResponse)
|
|
async def list_workspaces(
|
|
manager: WorkspaceManager = Depends(get_workspace_manager),
|
|
):
|
|
"""
|
|
List all design-time workspaces.
|
|
|
|
Returns:
|
|
List of design-time workspaces
|
|
"""
|
|
workspaces = manager.list_workspaces()
|
|
return WorkspaceListResponse(
|
|
workspaces=[
|
|
WorkspaceResponse(
|
|
workspace_id=ws.workspace_id,
|
|
name=ws.name,
|
|
description=ws.description,
|
|
created_at=ws.created_at,
|
|
metadata=ws.metadata,
|
|
)
|
|
for ws in workspaces
|
|
],
|
|
total=len(workspaces),
|
|
)
|
|
|
|
|
|
@router.get("/{workspace_id}", response_model=WorkspaceResponse)
|
|
async def get_workspace(
|
|
workspace_id: str,
|
|
manager: WorkspaceManager = Depends(get_workspace_manager),
|
|
):
|
|
"""
|
|
Get workspace details.
|
|
|
|
Args:
|
|
workspace_id: Workspace identifier
|
|
|
|
Returns:
|
|
Workspace information
|
|
"""
|
|
workspace = manager.get_workspace(workspace_id)
|
|
if not workspace:
|
|
raise HTTPException(status_code=404, detail=f"Workspace '{workspace_id}' not found")
|
|
|
|
return WorkspaceResponse(
|
|
workspace_id=workspace["workspace_id"],
|
|
name=workspace.get("name", workspace_id),
|
|
description=workspace.get("description", ""),
|
|
created_at=workspace.get("created_at"),
|
|
metadata=workspace.get("metadata", {}),
|
|
)
|
|
|
|
|
|
@router.patch("/{workspace_id}", response_model=WorkspaceResponse)
|
|
async def update_workspace(
|
|
workspace_id: str,
|
|
request: UpdateWorkspaceRequest,
|
|
manager: WorkspaceManager = Depends(get_workspace_manager),
|
|
):
|
|
"""
|
|
Update workspace configuration.
|
|
|
|
Args:
|
|
workspace_id: Workspace identifier
|
|
request: Update parameters
|
|
|
|
Returns:
|
|
Updated workspace information
|
|
"""
|
|
try:
|
|
config = manager.update_workspace_config(
|
|
workspace_id=workspace_id,
|
|
name=request.name,
|
|
description=request.description,
|
|
metadata=request.metadata,
|
|
)
|
|
return WorkspaceResponse(
|
|
workspace_id=config.workspace_id,
|
|
name=config.name,
|
|
description=config.description,
|
|
created_at=config.created_at,
|
|
metadata=config.metadata,
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
|
@router.delete("/{workspace_id}")
|
|
async def delete_workspace(
|
|
workspace_id: str,
|
|
force: bool = False,
|
|
manager: WorkspaceManager = Depends(get_workspace_manager),
|
|
):
|
|
"""
|
|
Delete a workspace.
|
|
|
|
Args:
|
|
workspace_id: Workspace identifier
|
|
force: If True, delete even if workspace has agents
|
|
|
|
Returns:
|
|
Success message
|
|
"""
|
|
try:
|
|
success = manager.delete_workspace(workspace_id, force=force)
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail=f"Workspace '{workspace_id}' not found")
|
|
return {"message": f"Workspace '{workspace_id}' deleted successfully"}
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|