""" Agent Control Routes - REST API for agent-to-agent Atom OS control. Allows any agent (OpenClaw, Claude, custom) to programmatically control Atom OS: - Start Atom as background service - Stop Atom service - Check status - Execute commands Usage: import requests # Start Atom response = requests.post("http://localhost:8000/api/agent/start", json={"port": 8000}) # Check status response = requests.get("http://localhost:8000/api/agent/status") # Stop Atom response = requests.post("http://localhost:8000/api/agent/stop") """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any from sqlalchemy.orm import Session # Import daemon manager import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from cli.daemon import DaemonManager # Import authentication and authorization from core.admin_endpoints import get_super_admin from core.models import User, DelegationChain from core.database import get_db router = APIRouter(prefix="/api/agent", tags=["agent-control"]) # Request/Response Models class StartAgentRequest(BaseModel): """Request model for starting Atom OS service.""" port: int = Field(default=8000, ge=1, le=65535, description="Port for web server") host: str = Field(default="0.0.0.0", description="Host to bind to") workers: int = Field(default=1, ge=1, le=16, description="Number of worker processes") host_mount: bool = Field(default=False, description="Enable host filesystem mount") dev: bool = Field(default=False, description="Enable development mode") class StartAgentResponse(BaseModel): """Response model for start endpoint.""" success: bool pid: Optional[int] = None status: str dashboard_url: Optional[str] = None message: str error: Optional[str] = None class StopAgentResponse(BaseModel): """Response model for stop endpoint.""" success: bool status: str message: str error: Optional[str] = None class RestartAgentResponse(BaseModel): """Response model for restart endpoint.""" success: bool pid: Optional[int] = None status: str dashboard_url: Optional[str] = None was_running: bool message: str error: Optional[str] = None class AgentStatusResponse(BaseModel): """Response model for status endpoint.""" success: bool status: dict message: Optional[str] = None class ExecuteCommandRequest(BaseModel): """Request model for execute endpoint.""" command: str = Field(..., description="Atom command to execute") timeout: int = Field(default=30, ge=1, le=300, description="Timeout in seconds") class ExecuteCommandResponse(BaseModel): """Response model for execute endpoint.""" success: bool result: Optional[str] = None error: Optional[str] = None note: Optional[str] = None # API Endpoints @router.post("/start", response_model=StartAgentResponse) async def start_atom( request: StartAgentRequest, current_user: User = Depends(get_super_admin) ): """Start Atom OS as background service (super_admin only). **SECURITY**: Requires super_admin authentication to prevent unauthorized daemon control. Use this endpoint only from trusted sources. Called by external agents (Claude, OpenClaw, custom agents) to programmatically start Atom as a background service. **Example:** ```python import requests response = requests.post( "http://localhost:8000/api/agent/start", json={"port": 8000, "host": "0.0.0.0"} ) print(response.json()) ``` **Returns:** - success: True if started successfully - pid: Process ID of daemon - status: "started" - dashboard_url: URL to web dashboard - message: Success message **Raises:** - 400: If Atom is already running - 500: If daemon fails to start """ try: if DaemonManager.is_running(): current_pid = DaemonManager.get_pid() raise HTTPException( status_code=400, detail=f"Atom OS is already running (PID: {current_pid})" ) pid = DaemonManager.start_daemon( port=request.port, host=request.host, workers=request.workers, host_mount=request.host_mount, dev=request.dev ) return StartAgentResponse( success=True, pid=pid, status="started", dashboard_url=f"http://{request.host}:{request.port}", message="Atom OS started successfully" ) except RuntimeError as e: raise HTTPException(status_code=500, detail=str(e)) except IOError as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/stop", response_model=StopAgentResponse) async def stop_atom(current_user: User = Depends(get_super_admin)): """Stop Atom OS background service (super_admin only). **SECURITY**: Requires super_admin authentication to prevent unauthorized daemon control. Gracefully shuts down Atom daemon service. **Example:** ```python import requests response = requests.post("http://localhost:8000/api/agent/stop") print(response.json()) ``` **Returns:** - success: True if stopped - status: "stopped" - message: Success message **Raises:** - 400: If Atom is not running - 500: If stop fails """ try: if not DaemonManager.is_running(): raise HTTPException( status_code=400, detail="Atom OS is not running" ) DaemonManager.stop_daemon() return StopAgentResponse( success=True, status="stopped", message="Atom OS stopped successfully" ) except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/restart", response_model=RestartAgentResponse) async def restart_atom( request: StartAgentRequest, current_user: User = Depends(get_super_admin) ): """Restart Atom OS background service (super_admin only). **SECURITY**: Requires super_admin authentication to prevent unauthorized daemon control. Stops Atom if running, then starts again with new configuration. **Example:** ```python import requests response = requests.post( "http://localhost:8000/api/agent/restart", json={"port": 8000} ) print(response.json()) ``` **Returns:** - success: True if restarted - pid: New process ID - status: "restarted" - dashboard_url: URL to web dashboard - was_running: Whether Atom was running before restart - message: Success message **Raises:** - 500: If restart fails """ try: was_running = DaemonManager.is_running() if was_running: DaemonManager.stop_daemon() # Wait for clean shutdown import time time.sleep(2) pid = DaemonManager.start_daemon( port=request.port, host=request.host, workers=request.workers, host_mount=request.host_mount, dev=request.dev ) return RestartAgentResponse( success=True, pid=pid, status="restarted", dashboard_url=f"http://{request.host}:{request.port}", was_running=was_running, message="Atom OS restarted successfully" ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/status", response_model=AgentStatusResponse) async def get_status(): """Get Atom OS status and running info. Returns current status, PID, uptime, memory usage, and CPU. **Example:** ```python import requests response = requests.get("http://localhost:8000/api/agent/status") print(response.json()) ``` **Returns:** - success: True - status: Dict with running status, pid, uptime_seconds, memory_mb, cpu_percent **Example Response:** ```json { "success": true, "status": { "running": true, "pid": 12345, "uptime_seconds": 3600, "memory_mb": 256.5, "cpu_percent": 5.2, "status": "running" } } ``` """ try: status_info = DaemonManager.get_status() return AgentStatusResponse( success=True, status=status_info ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/execute", response_model=ExecuteCommandResponse) async def execute_atom_command( request: ExecuteCommandRequest, current_user: User = Depends(get_super_admin) ): """Execute single Atom command and return result (super_admin only). **SECURITY**: Requires super_admin authentication to prevent unauthorized command execution. This endpoint executes arbitrary Atom commands. Useful for one-off tasks from other agents. Starts Atom temporarily, executes command, and shuts down. **Note:** Command routing not yet fully implemented. Use POST /api/agent/start to run Atom as service instead. **Example:** ```python import requests response = requests.post( "http://localhost:8000/api/agent/execute", json={"command": "agent.chat('Hello, create a report')"} ) print(response.json()) ``` **Returns:** - success: True - result: Command execution result (when implemented) - note: Implementation status message **Note:** This endpoint is currently a placeholder. Use daemon mode for full Atom functionality: ```bash # Start as service atom-os daemon # Or via API curl -X POST http://localhost:8000/api/agent/start ``` """ return ExecuteCommandResponse( success=True, result="Command execution not yet implemented", note="Use POST /api/agent/start to run Atom as service instead" ) @router.get("/{chain_id}/bottlenecks") async def analyze_chain_bottlenecks( chain_id: str, db: Session = Depends(get_db), # For Upstream, we restrict this to admins as it reveals internal telemetry current_user: User = Depends(get_super_admin) ): """ Perform diagnostic analysis to identify bottlenecks in the delegation chain. RESTRICTED: Super Admin only. """ # 1. Verify chain existence chain = db.query(DelegationChain).filter(DelegationChain.id == chain_id).first() if not chain: raise HTTPException( status_code=404, detail="Delegation chain not found" ) # 2. Run analysis from analytics.fleet_optimization_service import FleetOptimizationService service = FleetOptimizationService(db) report = service.analyze_bottlenecks(chain_id) return { "chain_id": chain_id, "report": report, "summary": { "total_issues": len(report), "critical_issues": len([r for r in report if r["severity"] == "critical"]), "warnings": len([r for r in report if r["severity"] == "warning"]) } } @router.get("/fleet/health") async def get_fleet_health_summary( db: Session = Depends(get_db), current_user: User = Depends(get_super_admin) ): """ Get fleet-wide health metrics for the supervisor dashboard. RESTRICTED: Super Admin only. """ from analytics.fleet_optimization_service import FleetOptimizationService service = FleetOptimizationService(db) # Scoped to the current admin's tenant if applicable tenant_id = getattr(current_user, "tenant_id", None) return service.get_fleet_health_summary(tenant_id)