- 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>
136 lines
4.1 KiB
Python
136 lines
4.1 KiB
Python
# -*- 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
|