refactor(cleanup): remove legacy runtime directories and fix API semantics

Task 1: Clean up root-level runtime directories
- Backup live/, backtest/, production/ to runs/_legacy/
- Remove legacy directories from repo root

Task 2: API route semantics cleanup
- Create /api/runs/{run_id}/agents/* routes for runtime agent operations
- Keep /api/workspaces/{id}/agents/* for design-time (deprecated)
- Update frontend runtimeApi.js to use new /runs/ prefix
- Update legacy-inventory.md with completion status

New files:
- backend/api/runs.py - Runtime agent routes with proper run_id semantics

Modified:
- backend/api/__init__.py - Export runs_router
- backend/apps/agent_service.py - Include runs_router, update scope docs
- frontend/src/services/runtimeApi.js - Use /runs/ instead of /workspaces/
- docs/legacy-inventory.md - Mark cleanup as completed

Constraint: Maintain backward compatibility with old /workspaces/ routes
Rejected: Remove old routes entirely | need backward compatibility during transition
Confidence: high
Scope-risk: moderate
Directive: Old /api/workspaces/ routes remain functional but deprecated
Not-tested: Full integration test with active runtime
This commit is contained in:
2026-04-02 01:03:28 +08:00
parent 16b54d5ccc
commit 3334a41e5a
18 changed files with 559 additions and 5096 deletions

View File

@@ -13,6 +13,7 @@ from .workspaces import router as workspaces_router
from .guard import router as guard_router
from .openclaw import router as openclaw_router
from .runtime import router as runtime_router
from .runs import router as runs_router
__all__ = [
"agents_router",
@@ -20,4 +21,5 @@ __all__ = [
"guard_router",
"openclaw_router",
"runtime_router",
"runs_router",
]

547
backend/api/runs.py Normal file
View File

@@ -0,0 +1,547 @@
# -*- coding: utf-8 -*-
"""
Run-scoped Agent API Routes
Provides REST API endpoints for runtime agent asset access under `runs/<run_id>/`.
This module separates runtime concerns from design-time workspace management:
- `/api/runs/{run_id}/agents/*` - Runtime agent assets and configuration
- `/api/workspaces/{workspace_id}/agents/*` - Design-time workspace registry (deprecated)
"""
import logging
import os
import tempfile
from pathlib import Path
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, HTTPException, Depends, Body, UploadFile, File, Form
from pydantic import BaseModel, Field
from backend.agents.workspace_manager import RunWorkspaceManager
from backend.agents.agent_workspace import load_agent_workspace_config
from backend.agents.skills_manager import SkillsManager
from backend.agents.toolkit_factory import load_agent_profiles
from backend.config.bootstrap_config import get_bootstrap_config_for_run
from backend.llm.models import get_agent_model_info
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/runs/{run_id}/agents", tags=["runs"])
# Request/Response Models
class InstallExternalSkillRequest(BaseModel):
"""Request to install an external skill for one agent."""
source: str = Field(..., description="Directory path, zip path, or http(s) zip URL")
name: Optional[str] = Field(None, description="Optional override skill name")
activate: bool = Field(True, description="Whether to enable skill immediately")
class LocalSkillRequest(BaseModel):
skill_name: str = Field(..., description="Local skill name")
class LocalSkillContentRequest(BaseModel):
content: str = Field(..., description="Updated SKILL.md content")
class AgentFileResponse(BaseModel):
"""Agent file content response."""
filename: str
content: str
scope_type: str = "runtime_run"
scope_note: Optional[str] = None
class AgentProfileResponse(BaseModel):
agent_id: str
run_id: str
profile: Dict[str, Any]
scope_type: str = "runtime_run"
scope_note: Optional[str] = None
class AgentSkillsResponse(BaseModel):
agent_id: str
run_id: str
skills: List[Dict[str, Any]]
scope_type: str = "runtime_run"
scope_note: Optional[str] = None
class SkillDetailResponse(BaseModel):
agent_id: str
run_id: str
skill: Dict[str, Any]
scope_type: str = "runtime_run"
scope_note: Optional[str] = None
# Dependencies
def get_workspace_manager():
"""Get run-scoped asset manager for one runtime workspace/run id."""
return RunWorkspaceManager()
def get_skills_manager():
"""Get SkillsManager instance."""
return SkillsManager()
# Runtime Routes
@router.get("/{agent_id}/profile", response_model=AgentProfileResponse)
async def get_agent_profile(
run_id: str,
agent_id: str,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Get agent profile from runtime assets under `runs/<run_id>/`.
Args:
run_id: Run identifier (e.g., "smoke_fullstack")
agent_id: Agent identifier
Returns:
Agent profile with model config, skills, and tool groups
"""
asset_dir = skills_manager.get_agent_asset_dir(run_id, agent_id)
agent_config = load_agent_workspace_config(asset_dir / "agent.yaml")
profiles = load_agent_profiles()
profile = profiles.get(agent_id, {})
bootstrap = get_bootstrap_config_for_run(skills_manager.project_root, run_id)
override = bootstrap.agent_override(agent_id)
active_tool_groups = override.get("active_tool_groups", agent_config.active_tool_groups or profile.get("active_tool_groups", []))
if not isinstance(active_tool_groups, list):
active_tool_groups = []
disabled_tool_groups = agent_config.disabled_tool_groups
if disabled_tool_groups:
disabled_set = set(disabled_tool_groups)
active_tool_groups = [group_name for group_name in active_tool_groups if group_name not in disabled_set]
default_skills = profile.get("skills", [])
if not isinstance(default_skills, list):
default_skills = []
resolved_skills = skills_manager.resolve_agent_skill_names(
config_name=run_id,
agent_id=agent_id,
default_skills=default_skills,
)
prompt_files = agent_config.prompt_files or ["SOUL.md", "PROFILE.md", "AGENTS.md", "POLICY.md", "MEMORY.md"]
model_name, model_provider = get_agent_model_info(agent_id)
return AgentProfileResponse(
agent_id=agent_id,
run_id=run_id,
profile={
"model_name": model_name,
"model_provider": model_provider,
"prompt_files": prompt_files,
"default_skills": default_skills,
"resolved_skills": resolved_skills,
"active_tool_groups": active_tool_groups,
"disabled_tool_groups": disabled_tool_groups,
"enabled_skills": agent_config.enabled_skills,
"disabled_skills": agent_config.disabled_skills,
},
)
@router.get("/{agent_id}/skills", response_model=AgentSkillsResponse)
async def get_agent_skills(
run_id: str,
agent_id: str,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Get agent skills from runtime assets under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
Returns:
List of skills with their status (active/enabled/disabled/available)
"""
agent_asset_dir = skills_manager.get_agent_asset_dir(run_id, agent_id)
agent_config = load_agent_workspace_config(agent_asset_dir / "agent.yaml")
resolved_skills = set(skills_manager.resolve_agent_skill_names(config_name=run_id, agent_id=agent_id, default_skills=[]))
enabled = set(agent_config.enabled_skills)
disabled = set(agent_config.disabled_skills)
payload = []
for item in skills_manager.list_agent_skill_catalog(run_id, agent_id):
if item.skill_name in disabled:
status = "disabled"
elif item.skill_name in enabled:
status = "enabled"
elif item.skill_name in resolved_skills:
status = "active"
else:
status = "available"
payload.append({
"skill_name": item.skill_name,
"name": item.name,
"description": item.description,
"version": item.version,
"source": item.source,
"tools": item.tools,
"status": status,
})
return AgentSkillsResponse(
agent_id=agent_id,
run_id=run_id,
skills=payload,
)
@router.get("/{agent_id}/skills/{skill_name}", response_model=SkillDetailResponse)
async def get_agent_skill_detail(
run_id: str,
agent_id: str,
skill_name: str,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Get detailed skill information from runtime assets.
Args:
run_id: Run identifier
agent_id: Agent identifier
skill_name: Skill name
Returns:
Skill detail information
"""
try:
detail = skills_manager.load_agent_skill_document(
config_name=run_id,
agent_id=agent_id,
skill_name=skill_name,
)
except FileNotFoundError:
raise HTTPException(status_code=404, detail=f"Unknown skill: {skill_name}")
return SkillDetailResponse(
agent_id=agent_id,
run_id=run_id,
skill=detail,
)
@router.post("/{agent_id}/skills/{skill_name}/enable")
async def enable_skill(
run_id: str,
agent_id: str,
skill_name: str,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Enable a skill for an agent in runtime assets.
Args:
run_id: Run identifier
agent_id: Agent identifier
skill_name: Skill name to enable
Returns:
Success message with updated enabled skills list
"""
result = skills_manager.update_agent_skill_overrides(
config_name=run_id,
agent_id=agent_id,
enable=[skill_name],
)
return {
"message": f"Skill '{skill_name}' enabled for agent '{agent_id}'",
"enabled_skills": result["enabled_skills"],
}
@router.post("/{agent_id}/skills/{skill_name}/disable")
async def disable_skill(
run_id: str,
agent_id: str,
skill_name: str,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Disable a skill for an agent in runtime assets.
Args:
run_id: Run identifier
agent_id: Agent identifier
skill_name: Skill name to disable
Returns:
Success message with updated disabled skills list
"""
result = skills_manager.update_agent_skill_overrides(
config_name=run_id,
agent_id=agent_id,
disable=[skill_name],
)
return {
"message": f"Skill '{skill_name}' disabled for agent '{agent_id}'",
"disabled_skills": result["disabled_skills"],
}
@router.post("/{agent_id}/skills/install")
async def install_external_skill(
run_id: str,
agent_id: str,
request: InstallExternalSkillRequest,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Install an external skill into one agent's local skills under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
request: Installation parameters
Returns:
Success message with installed skill details
"""
try:
result = skills_manager.install_external_skill_for_agent(
config_name=run_id,
agent_id=agent_id,
source=request.source,
skill_name=request.name,
activate=request.activate,
)
except (FileNotFoundError, ValueError) as exc:
raise HTTPException(status_code=400, detail=str(exc))
return {
"message": f"Installed external skill '{result['skill_name']}' for '{agent_id}'",
**result,
}
@router.post("/{agent_id}/skills/local")
async def create_local_skill(
run_id: str,
agent_id: str,
request: LocalSkillRequest,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Create a new local skill for an agent under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
request: Local skill creation parameters
Returns:
Success message
"""
try:
skills_manager.create_agent_local_skill(
config_name=run_id,
agent_id=agent_id,
skill_name=request.skill_name,
)
except (ValueError, FileExistsError) as exc:
raise HTTPException(status_code=400, detail=str(exc))
return {"message": f"Created local skill '{request.skill_name}' for '{agent_id}'"}
@router.put("/{agent_id}/skills/local/{skill_name}")
async def update_local_skill(
run_id: str,
agent_id: str,
skill_name: str,
request: LocalSkillContentRequest,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Update a local skill's SKILL.md content under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
skill_name: Skill name
request: Updated content
Returns:
Success message
"""
try:
skills_manager.update_agent_local_skill(
config_name=run_id,
agent_id=agent_id,
skill_name=skill_name,
content=request.content,
)
except (ValueError, FileNotFoundError) as exc:
raise HTTPException(status_code=400, detail=str(exc))
return {"message": f"Updated local skill '{skill_name}' for '{agent_id}'"}
@router.delete("/{agent_id}/skills/local/{skill_name}")
async def delete_local_skill(
run_id: str,
agent_id: str,
skill_name: str,
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Delete a local skill under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
skill_name: Skill name to delete
Returns:
Success message
"""
try:
skills_manager.delete_agent_local_skill(
config_name=run_id,
agent_id=agent_id,
skill_name=skill_name,
)
skills_manager.forget_agent_skill_overrides(
config_name=run_id,
agent_id=agent_id,
skill_names=[skill_name],
)
except (ValueError, FileNotFoundError) as exc:
raise HTTPException(status_code=400, detail=str(exc))
return {"message": f"Deleted local skill '{skill_name}' for '{agent_id}'"}
@router.post("/{agent_id}/skills/upload")
async def upload_external_skill(
run_id: str,
agent_id: str,
file: UploadFile = File(...),
name: Optional[str] = Form(None),
activate: bool = Form(True),
skills_manager: SkillsManager = Depends(get_skills_manager),
):
"""
Upload a zip skill package and install for one agent under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
file: Zip file to upload
name: Optional skill name override
activate: Whether to enable skill immediately
Returns:
Success message with installed skill details
"""
original_name = (file.filename or "").strip()
if not original_name.lower().endswith(".zip"):
raise HTTPException(status_code=400, detail="Uploaded file must be a .zip archive")
suffix = Path(original_name).suffix or ".zip"
temp_path: Optional[str] = None
try:
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
temp_path = tmp.name
content = await file.read()
tmp.write(content)
result = skills_manager.install_external_skill_for_agent(
config_name=run_id,
agent_id=agent_id,
source=temp_path,
skill_name=name,
activate=activate,
)
except (FileNotFoundError, ValueError) as exc:
raise HTTPException(status_code=400, detail=str(exc))
finally:
try:
await file.close()
except Exception as e:
logger.warning(f"Failed to close uploaded file: {e}")
if temp_path and os.path.exists(temp_path):
os.remove(temp_path)
return {
"message": f"Uploaded and installed external skill '{result['skill_name']}' for '{agent_id}'",
**result,
}
@router.get("/{agent_id}/files/{filename}", response_model=AgentFileResponse)
async def get_agent_file(
run_id: str,
agent_id: str,
filename: str,
workspace_manager: RunWorkspaceManager = Depends(get_workspace_manager),
):
"""
Read an agent file from the run-scoped asset tree under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
filename: File to read (e.g., SOUL.md, PROFILE.md)
Returns:
File content
"""
try:
content = workspace_manager.load_agent_file(
config_name=run_id,
agent_id=agent_id,
filename=filename,
)
return AgentFileResponse(
filename=filename,
content=content,
)
except FileNotFoundError:
raise HTTPException(status_code=404, detail=f"File '{filename}' not found")
@router.put("/{agent_id}/files/{filename}", response_model=AgentFileResponse)
async def update_agent_file(
run_id: str,
agent_id: str,
filename: str,
content: str = Body(..., media_type="text/plain"),
workspace_manager: RunWorkspaceManager = Depends(get_workspace_manager),
):
"""
Update an agent file in the run-scoped asset tree under `runs/<run_id>/`.
Args:
run_id: Run identifier
agent_id: Agent identifier
filename: File to update
content: New file content
Returns:
Updated file information
"""
try:
workspace_manager.update_agent_file(
config_name=run_id,
agent_id=agent_id,
filename=filename,
content=content,
)
return AgentFileResponse(
filename=filename,
content=content,
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -11,7 +11,7 @@ from fastapi import FastAPI
from backend.apps.cors import add_cors_middleware
from backend.api import agents_router, guard_router, workspaces_router
from backend.api import agents_router, guard_router, workspaces_router, runs_router
from backend.agents import AgentFactory, WorkspaceManager, get_registry
# Global instances (initialized on startup)
@@ -30,9 +30,9 @@ def _build_scope_payload(project_root: Path) -> dict[str, object]:
"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>/`."
"Runtime routes use `/api/runs/{run_id}/agents/...`. "
"Legacy `/api/workspaces/{workspace_id}/agents/...` routes are deprecated "
"but remain for backward compatibility."
),
}
@@ -96,6 +96,7 @@ def create_app(project_root: Path | None = None) -> FastAPI:
app.include_router(workspaces_router)
app.include_router(agents_router)
app.include_router(runs_router)
app.include_router(guard_router)
return app

File diff suppressed because one or more lines are too long

View File

@@ -1,474 +0,0 @@
{
"baseline_state": {
"initialized": true,
"initial_allocation": {
"AAPL": 52.82787621372046,
"MSFT": 27.48283353510314,
"GOOGL": 50.62714374311787,
"NVDA": 68.65491294557039,
"TSLA": 31.329007841650665,
"META": 21.77700348432056,
"AMZN": 55.94343000358038
}
},
"baseline_vw_state": {
"initialized": true,
"initial_allocation": {
"AAPL": 68.50435598171448,
"MSFT": 28.26372943269579,
"GOOGL": 64.10562703513074,
"NVDA": 105.43488803941372,
"TSLA": 16.283886873554753,
"META": 12.29869945153529,
"AMZN": 44.10358298129591
}
},
"momentum_state": {
"positions": {
"AAPL": 123.26504449868106,
"MSFT": 64.12661158190733,
"GOOGL": 118.13000206727504
},
"cash": 0.0,
"initialized": true,
"last_rebalance_date": "2025-11-03"
},
"equity_history": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99785.98
},
{
"t": 1762243200000,
"v": 99590.68
},
{
"t": 1762329600000,
"v": 99298.78
},
{
"t": 1762416000000,
"v": 98425.78
},
{
"t": 1762502400000,
"v": 98434.93
}
],
"baseline_history": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99760.66
},
{
"t": 1762243200000,
"v": 97620.18
},
{
"t": 1762329600000,
"v": 98327.37
},
{
"t": 1762416000000,
"v": 96286.86
},
{
"t": 1762502400000,
"v": 95539.06
}
],
"baseline_vw_history": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99716.91
},
{
"t": 1762243200000,
"v": 97721.94
},
{
"t": 1762329600000,
"v": 98028.19
},
{
"t": 1762416000000,
"v": 96206.83
},
{
"t": 1762502400000,
"v": 95565.33
}
],
"momentum_history": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99835.69
},
{
"t": 1762243200000,
"v": 99054.53
},
{
"t": 1762329600000,
"v": 99406.81
},
{
"t": 1762416000000,
"v": 98768.07
},
{
"t": 1762502400000,
"v": 97890.54
}
],
"price_history": {
"AAPL": [
{
"date": "2025-11-03",
"price": 269.05
},
{
"date": "2025-11-04",
"price": 270.04
},
{
"date": "2025-11-05",
"price": 270.14
},
{
"date": "2025-11-06",
"price": 269.77
},
{
"date": "2025-11-07",
"price": 268.47
}
],
"MSFT": [
{
"date": "2025-11-03",
"price": 517.03
},
{
"date": "2025-11-04",
"price": 514.33
},
{
"date": "2025-11-05",
"price": 507.16
},
{
"date": "2025-11-06",
"price": 497.1
},
{
"date": "2025-11-07",
"price": 496.82
}
],
"GOOGL": [
{
"date": "2025-11-03",
"price": 283.72
},
{
"date": "2025-11-04",
"price": 277.54
},
{
"date": "2025-11-05",
"price": 284.31
},
{
"date": "2025-11-06",
"price": 284.75
},
{
"date": "2025-11-07",
"price": 278.83
}
],
"NVDA": [
{
"date": "2025-11-03",
"price": 206.88
},
{
"date": "2025-11-04",
"price": 198.69
},
{
"date": "2025-11-05",
"price": 195.21
},
{
"date": "2025-11-06",
"price": 188.08
},
{
"date": "2025-11-07",
"price": 188.15
}
],
"TSLA": [
{
"date": "2025-11-03",
"price": 468.37
},
{
"date": "2025-11-04",
"price": 444.26
},
{
"date": "2025-11-05",
"price": 462.07
},
{
"date": "2025-11-06",
"price": 445.91
},
{
"date": "2025-11-07",
"price": 429.52
}
],
"META": [
{
"date": "2025-11-03",
"price": 637.71
},
{
"date": "2025-11-04",
"price": 627.32
},
{
"date": "2025-11-05",
"price": 635.95
},
{
"date": "2025-11-06",
"price": 618.94
},
{
"date": "2025-11-07",
"price": 621.71
}
],
"AMZN": [
{
"date": "2025-11-03",
"price": 254.0
},
{
"date": "2025-11-04",
"price": 249.32
},
{
"date": "2025-11-05",
"price": 250.2
},
{
"date": "2025-11-06",
"price": 243.04
},
{
"date": "2025-11-07",
"price": 244.41
}
]
},
"portfolio_state": {
"cash": 25395.10000000001,
"positions": {
"MSFT": {
"long": 60,
"short": 0,
"long_cost_basis": 514.2845833333333,
"short_cost_basis": 0.0
},
"GOOGL": {
"long": 50,
"short": 0,
"long_cost_basis": 279.556,
"short_cost_basis": 0.0
},
"META": {
"long": 20,
"short": 0,
"long_cost_basis": 644.155,
"short_cost_basis": 0.0
},
"AMZN": {
"long": 40,
"short": 0,
"long_cost_basis": 247.5725,
"short_cost_basis": 0.0
},
"NVDA": {
"long": 20,
"short": 0,
"long_cost_basis": 203.0,
"short_cost_basis": 0.0
},
"TSLA": {
"long": 0,
"short": 15,
"long_cost_basis": 0.0,
"short_cost_basis": 454.46
},
"AAPL": {
"long": 30,
"short": 0,
"long_cost_basis": 267.89,
"short_cost_basis": 0.0
}
},
"margin_used": 1704.225
},
"all_trades": [
{
"id": "t_20251103_MSFT_0",
"ts": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "MSFT",
"qty": 15,
"price": 519.8
},
{
"id": "t_20251103_GOOGL_1",
"ts": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "GOOGL",
"qty": 20,
"price": 282.18
},
{
"id": "t_20251103_META_2",
"ts": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "META",
"qty": 10,
"price": 656.0
},
{
"id": "t_20251103_AMZN_3",
"ts": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "AMZN",
"qty": 15,
"price": 255.36
},
{
"id": "t_20251104_MSFT_0",
"ts": 1762243200000,
"trading_date": "2025-11-04",
"side": "LONG",
"ticker": "MSFT",
"qty": 25,
"price": 511.76
},
{
"id": "t_20251104_GOOGL_1",
"ts": 1762243200000,
"trading_date": "2025-11-04",
"side": "LONG",
"ticker": "GOOGL",
"qty": 15,
"price": 276.75
},
{
"id": "t_20251104_NVDA_2",
"ts": 1762243200000,
"trading_date": "2025-11-04",
"side": "LONG",
"ticker": "NVDA",
"qty": 20,
"price": 203.0
},
{
"id": "t_20251104_TSLA_3",
"ts": 1762243200000,
"trading_date": "2025-11-04",
"side": "SHORT",
"ticker": "TSLA",
"qty": 15,
"price": 454.46
},
{
"id": "t_20251105_MSFT_0",
"ts": 1762329600000,
"trading_date": "2025-11-05",
"side": "LONG",
"ticker": "MSFT",
"qty": 20,
"price": 513.3
},
{
"id": "t_20251105_GOOGL_1",
"ts": 1762329600000,
"trading_date": "2025-11-05",
"side": "LONG",
"ticker": "GOOGL",
"qty": 15,
"price": 278.87
},
{
"id": "t_20251105_META_2",
"ts": 1762329600000,
"trading_date": "2025-11-05",
"side": "LONG",
"ticker": "META",
"qty": 10,
"price": 632.31
},
{
"id": "t_20251106_AAPL_0",
"ts": 1762416000000,
"trading_date": "2025-11-06",
"side": "LONG",
"ticker": "AAPL",
"qty": 30,
"price": 267.89
},
{
"id": "t_20251107_AMZN_0",
"ts": 1762502400000,
"trading_date": "2025-11-07",
"side": "LONG",
"ticker": "AMZN",
"qty": 25,
"price": 242.9
},
{
"id": "t_20251107_TSLA_1",
"ts": 1762502400000,
"trading_date": "2025-11-07",
"side": "SHORT",
"ticker": "TSLA",
"qty": -5,
"price": 437.92
}
],
"daily_position_history": {},
"last_update_date": "2025-11-07"
}

View File

@@ -1,58 +0,0 @@
[
{
"ticker": "MSFT",
"quantity": 60,
"currentPrice": 496.82,
"marketValue": 29809.2,
"weight": 0.3028
},
{
"ticker": "CASH",
"quantity": 1,
"currentPrice": 25395.1,
"marketValue": 25395.1,
"weight": 0.258
},
{
"ticker": "GOOGL",
"quantity": 50,
"currentPrice": 278.83,
"marketValue": 13941.5,
"weight": 0.1416
},
{
"ticker": "META",
"quantity": 20,
"currentPrice": 621.71,
"marketValue": 12434.2,
"weight": 0.1263
},
{
"ticker": "AMZN",
"quantity": 40,
"currentPrice": 244.41,
"marketValue": 9776.4,
"weight": 0.0993
},
{
"ticker": "AAPL",
"quantity": 30,
"currentPrice": 268.47,
"marketValue": 8054.1,
"weight": 0.0818
},
{
"ticker": "TSLA",
"quantity": -15,
"currentPrice": 429.52,
"marketValue": -6442.8,
"weight": 0.0655
},
{
"ticker": "NVDA",
"quantity": 20,
"currentPrice": 188.15,
"marketValue": 3763.0,
"weight": 0.0382
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"totalAssetValue": 98434.93,
"totalReturn": -1.57,
"cashPosition": 25395.1,
"tickerWeights": {},
"totalTrades": 14,
"winRate": 0.0,
"bullBear": {
"bull": {
"n": 0,
"win": 0
},
"bear": {
"n": 0,
"win": 0
}
}
}

View File

@@ -1,121 +0,0 @@
{
"totalAssetValue": 98434.93,
"totalReturn": -1.57,
"cashPosition": 25395.1,
"tickerWeights": {
"MSFT": 0.3028,
"GOOGL": 0.1416,
"META": 0.1263,
"AMZN": 0.0993,
"NVDA": 0.0382,
"TSLA": -0.0655,
"AAPL": 0.0818
},
"totalTrades": 14,
"pnlPct": -1.57,
"balance": 98434.93,
"equity": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99785.98
},
{
"t": 1762243200000,
"v": 99590.68
},
{
"t": 1762329600000,
"v": 99298.78
},
{
"t": 1762416000000,
"v": 98425.78
},
{
"t": 1762502400000,
"v": 98434.93
}
],
"baseline": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99760.66
},
{
"t": 1762243200000,
"v": 97620.18
},
{
"t": 1762329600000,
"v": 98327.37
},
{
"t": 1762416000000,
"v": 96286.86
},
{
"t": 1762502400000,
"v": 95539.06
}
],
"baseline_vw": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99716.91
},
{
"t": 1762243200000,
"v": 97721.94
},
{
"t": 1762329600000,
"v": 98028.19
},
{
"t": 1762416000000,
"v": 96206.83
},
{
"t": 1762502400000,
"v": 95565.33
}
],
"momentum": [
{
"t": 1762070400000,
"v": 100000.0
},
{
"t": 1762156800000,
"v": 99835.69
},
{
"t": 1762243200000,
"v": 99054.53
},
{
"t": 1762329600000,
"v": 99406.81
},
{
"t": 1762416000000,
"v": 98768.07
},
{
"t": 1762502400000,
"v": 97890.54
}
]
}

View File

@@ -1,128 +0,0 @@
[
{
"id": "t_20251107_AMZN_0",
"timestamp": 1762502400000,
"trading_date": "2025-11-07",
"side": "LONG",
"ticker": "AMZN",
"qty": 25,
"price": 242.9
},
{
"id": "t_20251107_TSLA_1",
"timestamp": 1762502400000,
"trading_date": "2025-11-07",
"side": "SHORT",
"ticker": "TSLA",
"qty": -5,
"price": 437.92
},
{
"id": "t_20251106_AAPL_0",
"timestamp": 1762416000000,
"trading_date": "2025-11-06",
"side": "LONG",
"ticker": "AAPL",
"qty": 30,
"price": 267.89
},
{
"id": "t_20251105_MSFT_0",
"timestamp": 1762329600000,
"trading_date": "2025-11-05",
"side": "LONG",
"ticker": "MSFT",
"qty": 20,
"price": 513.3
},
{
"id": "t_20251105_GOOGL_1",
"timestamp": 1762329600000,
"trading_date": "2025-11-05",
"side": "LONG",
"ticker": "GOOGL",
"qty": 15,
"price": 278.87
},
{
"id": "t_20251105_META_2",
"timestamp": 1762329600000,
"trading_date": "2025-11-05",
"side": "LONG",
"ticker": "META",
"qty": 10,
"price": 632.31
},
{
"id": "t_20251104_MSFT_0",
"timestamp": 1762243200000,
"trading_date": "2025-11-04",
"side": "LONG",
"ticker": "MSFT",
"qty": 25,
"price": 511.76
},
{
"id": "t_20251104_GOOGL_1",
"timestamp": 1762243200000,
"trading_date": "2025-11-04",
"side": "LONG",
"ticker": "GOOGL",
"qty": 15,
"price": 276.75
},
{
"id": "t_20251104_NVDA_2",
"timestamp": 1762243200000,
"trading_date": "2025-11-04",
"side": "LONG",
"ticker": "NVDA",
"qty": 20,
"price": 203.0
},
{
"id": "t_20251104_TSLA_3",
"timestamp": 1762243200000,
"trading_date": "2025-11-04",
"side": "SHORT",
"ticker": "TSLA",
"qty": 15,
"price": 454.46
},
{
"id": "t_20251103_MSFT_0",
"timestamp": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "MSFT",
"qty": 15,
"price": 519.8
},
{
"id": "t_20251103_GOOGL_1",
"timestamp": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "GOOGL",
"qty": 20,
"price": 282.18
},
{
"id": "t_20251103_META_2",
"timestamp": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "META",
"qty": 10,
"price": 656.0
},
{
"id": "t_20251103_AMZN_3",
"timestamp": 1762156800000,
"trading_date": "2025-11-03",
"side": "LONG",
"ticker": "AMZN",
"qty": 15,
"price": 255.36
}
]

View File

@@ -51,8 +51,8 @@ in use.
| Surface | Location | Replacement | ETA |
|---------|----------|-------------|-----|
| Legacy analyst agents | `backend.agents.analyst.*` | `EvoAgent` | After EvoAgent smoke tests pass |
| Mixed workspace_id semantics | `/api/workspaces/{id}/agents/...` | Explicit `run_id` vs `workspace_id` routes | TBD |
| Root-level runtime directories | `live/`, `backtest/`, `production/` | `runs/<run_id>/` | Already deprecated, safe to ignore |
| Mixed workspace_id semantics | `/api/workspaces/{id}/agents/...` | `/api/runs/{run_id}/agents/...` routes added | Completed |
| Root-level runtime directories | `live/`, `backtest/`, `production/` | `runs/<run_id>/` | ✅ Removed, backed up to runs/_legacy/ |
**Status**: Do not add new code using these surfaces. Migrate existing usage
when touching related code.

View File

@@ -130,12 +130,12 @@ export function fetchRuntimeLogs() {
}
function buildRunScopedAgentPath(runId, agentId, suffix = '') {
return `/workspaces/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}${suffix}`;
return `/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}${suffix}`;
}
/**
* Runtime-read agent routes still use the `/workspaces/...` prefix on the
* backend, but the leading identifier on this surface is the active `run_id`.
* Runtime agent routes use `/runs/{run_id}/agents/...`.
* Legacy `/workspaces/...` routes are deprecated but remain for backward compatibility.
*/
export function fetchAgentProfile(runId, agentId) {
return safeFetch(CONTROL_API_BASE, buildRunScopedAgentPath(runId, agentId, '/profile'));

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,134 +0,0 @@
[
{
"agentId": "portfolio_manager",
"name": "投资经理",
"role": "投资经理",
"avatar": "pm",
"rank": null,
"winRate": null,
"bull": {
"n": 0,
"win": 0,
"unknown": 0
},
"bear": {
"n": 0,
"win": 0,
"unknown": 0
},
"logs": [],
"signals": [],
"modelName": "deepseek-v3.2",
"modelProvider": "DASHSCOPE"
},
{
"agentId": "risk_manager",
"name": "风控经理",
"role": "风控经理",
"avatar": "risk",
"rank": null,
"winRate": null,
"bull": {
"n": 0,
"win": 0,
"unknown": 0
},
"bear": {
"n": 0,
"win": 0,
"unknown": 0
},
"logs": [],
"signals": [],
"modelName": "deepseek-v3.2",
"modelProvider": "DASHSCOPE"
},
{
"agentId": "sentiment_analyst",
"name": "情绪分析师",
"role": "情绪分析师",
"avatar": "sentiment",
"rank": 0,
"winRate": null,
"bull": {
"n": 0,
"win": 0,
"unknown": 0
},
"bear": {
"n": 0,
"win": 0,
"unknown": 0
},
"logs": [],
"signals": [],
"modelName": "deepseek-v3.2",
"modelProvider": "DASHSCOPE"
},
{
"agentId": "technical_analyst",
"name": "技术分析师",
"role": "技术分析师",
"avatar": "technical",
"rank": 0,
"winRate": null,
"bull": {
"n": 0,
"win": 0,
"unknown": 0
},
"bear": {
"n": 0,
"win": 0,
"unknown": 0
},
"logs": [],
"signals": [],
"modelName": "deepseek-v3.2",
"modelProvider": "DASHSCOPE"
},
{
"agentId": "fundamentals_analyst",
"name": "基本面分析师",
"role": "基本面分析师",
"avatar": "fundamentals",
"rank": 0,
"winRate": null,
"bull": {
"n": 0,
"win": 0,
"unknown": 0
},
"bear": {
"n": 0,
"win": 0,
"unknown": 0
},
"logs": [],
"signals": [],
"modelName": "deepseek-v3.2",
"modelProvider": "DASHSCOPE"
},
{
"agentId": "valuation_analyst",
"name": "估值分析师",
"role": "估值分析师",
"avatar": "valuation",
"rank": 0,
"winRate": null,
"bull": {
"n": 0,
"win": 0,
"unknown": 0
},
"bear": {
"n": 0,
"win": 0,
"unknown": 0
},
"logs": [],
"signals": [],
"modelName": "deepseek-v3.2",
"modelProvider": "DASHSCOPE"
}
]

View File

@@ -1,18 +0,0 @@
{
"totalAssetValue": 100000.0,
"totalReturn": 0.0,
"cashPosition": 100000.0,
"tickerWeights": {},
"totalTrades": 0,
"winRate": 0.0,
"bullBear": {
"bull": {
"n": 0,
"win": 0
},
"bear": {
"n": 0,
"win": 0
}
}
}

View File

@@ -1,13 +0,0 @@
{
"totalAssetValue": 100000.0,
"totalReturn": 0.0,
"cashPosition": 100000.0,
"tickerWeights": {},
"totalTrades": 0,
"pnlPct": 0.0,
"balance": 100000.0,
"equity": [],
"baseline": [],
"baseline_vw": [],
"momentum": []
}

View File

@@ -1 +0,0 @@
[]