feat: Add agent workspace system and runtime management
- Add agent core modules (agent_core, factory, registry, skill_loader) - Add runtime system for agent execution management - Add REST API for agents, workspaces, and runtime control - Add process supervisor for agent lifecycle management - Add workspace template system with agent profiles - Add frontend RuntimeView and runtime API integration - Add per-agent skill workspaces for smoke_fullstack run - Refactor skill system with active/installed separation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
135
backend/api/runtime.py
Normal file
135
backend/api/runtime.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Runtime API routes exposing the latest trading run state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.runtime.agent_runtime import AgentRuntimeState
|
||||
from backend.runtime.context import TradingRunContext
|
||||
from backend.runtime.manager import TradingRuntimeManager
|
||||
|
||||
router = APIRouter(prefix="/api/runtime", tags=["runtime"])
|
||||
|
||||
runtime_manager: Optional[TradingRuntimeManager] = None
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
class RunContextResponse(BaseModel):
|
||||
config_name: str
|
||||
run_dir: str
|
||||
bootstrap_values: Dict[str, Any]
|
||||
|
||||
|
||||
class RuntimeAgentState(BaseModel):
|
||||
agent_id: str
|
||||
status: str
|
||||
last_session: Optional[str] = None
|
||||
last_updated: str
|
||||
|
||||
|
||||
class RuntimeAgentsResponse(BaseModel):
|
||||
agents: List[RuntimeAgentState]
|
||||
|
||||
|
||||
class RuntimeEvent(BaseModel):
|
||||
timestamp: str
|
||||
event: str
|
||||
details: Dict[str, Any]
|
||||
session: Optional[str]
|
||||
|
||||
|
||||
class RuntimeEventsResponse(BaseModel):
|
||||
events: List[RuntimeEvent]
|
||||
|
||||
|
||||
def _latest_snapshot_path() -> Optional[Path]:
|
||||
candidates = sorted(
|
||||
PROJECT_ROOT.glob("runs/*/state/runtime_state.json"),
|
||||
key=lambda path: path.stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
return candidates[0] if candidates else None
|
||||
|
||||
|
||||
def _load_snapshot() -> Dict[str, Any]:
|
||||
snapshot_path = _latest_snapshot_path()
|
||||
if snapshot_path is None or not snapshot_path.exists():
|
||||
raise HTTPException(status_code=503, detail="runtime manager is not initialized")
|
||||
return json.loads(snapshot_path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _get_runtime_payload() -> Dict[str, Any]:
|
||||
if runtime_manager is not None:
|
||||
return runtime_manager.build_snapshot()
|
||||
return _load_snapshot()
|
||||
|
||||
|
||||
def _to_state_response(state: AgentRuntimeState) -> RuntimeAgentState:
|
||||
return RuntimeAgentState(
|
||||
agent_id=state.agent_id,
|
||||
status=state.status,
|
||||
last_session=state.last_session,
|
||||
last_updated=state.last_updated.isoformat(),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/context", response_model=RunContextResponse)
|
||||
async def get_run_context() -> RunContextResponse:
|
||||
"""Return the most recent run context."""
|
||||
payload = _get_runtime_payload()
|
||||
context = payload.get("context")
|
||||
if context is None:
|
||||
raise HTTPException(status_code=404, detail="run context is not ready")
|
||||
|
||||
return RunContextResponse(
|
||||
config_name=context["config_name"],
|
||||
run_dir=context["run_dir"],
|
||||
bootstrap_values=context["bootstrap_values"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/agents", response_model=RuntimeAgentsResponse)
|
||||
async def list_agent_states() -> RuntimeAgentsResponse:
|
||||
"""List the current runtime state of every registered agent."""
|
||||
payload = _get_runtime_payload()
|
||||
agents = [RuntimeAgentState(**agent) for agent in payload.get("agents", [])]
|
||||
return RuntimeAgentsResponse(agents=agents)
|
||||
|
||||
|
||||
@router.get("/events", response_model=RuntimeEventsResponse)
|
||||
async def list_runtime_events() -> RuntimeEventsResponse:
|
||||
"""Return the recent runtime events that TradingRuntimeManager emitted."""
|
||||
payload = _get_runtime_payload()
|
||||
events = [RuntimeEvent(**event) for event in payload.get("events", [])]
|
||||
return RuntimeEventsResponse(events=events)
|
||||
|
||||
|
||||
@router.get("/agents/{agent_id}", response_model=RuntimeAgentState)
|
||||
async def get_agent_state(agent_id: str) -> RuntimeAgentState:
|
||||
"""Return the current runtime state for a single agent."""
|
||||
payload = _get_runtime_payload()
|
||||
state = next(
|
||||
(agent for agent in payload.get("agents", []) if agent["agent_id"] == agent_id),
|
||||
None,
|
||||
)
|
||||
if state is None:
|
||||
raise HTTPException(status_code=404, detail=f"agent '{agent_id}' not registered")
|
||||
return RuntimeAgentState(**state)
|
||||
|
||||
|
||||
def register_runtime_manager(manager: TradingRuntimeManager) -> None:
|
||||
"""Allow other modules to expose the runtime manager to the API."""
|
||||
global runtime_manager
|
||||
runtime_manager = manager
|
||||
|
||||
|
||||
def unregister_runtime_manager() -> None:
|
||||
"""Drop the runtime manager reference (used for shutdown/testing)."""
|
||||
global runtime_manager
|
||||
runtime_manager = None
|
||||
Reference in New Issue
Block a user