- 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>
164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Parse run-scoped BOOTSTRAP.md into structured and runtime config."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
import re
|
|
|
|
import yaml
|
|
|
|
from backend.config.env_config import get_env_float, get_env_int, get_env_list
|
|
|
|
|
|
BOOTSTRAP_FRONT_MATTER_RE = re.compile(
|
|
r"^---\s*\n(.*?)\n---\s*\n?(.*)$",
|
|
re.DOTALL,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BootstrapConfig:
|
|
"""Structured configuration extracted from BOOTSTRAP.md."""
|
|
|
|
values: Dict[str, Any] = field(default_factory=dict)
|
|
prompt_body: str = ""
|
|
|
|
def get(self, key: str, default: Any = None) -> Any:
|
|
return self.values.get(key, default)
|
|
|
|
def agent_override(self, agent_id: str) -> Dict[str, Any]:
|
|
overrides = self.values.get("agent_overrides", {})
|
|
if not isinstance(overrides, dict):
|
|
return {}
|
|
override = overrides.get(agent_id, {})
|
|
return override if isinstance(override, dict) else {}
|
|
|
|
|
|
def load_bootstrap_config(bootstrap_path: Path) -> BootstrapConfig:
|
|
"""Load structured bootstrap config and free-form prompt body."""
|
|
if not bootstrap_path.exists():
|
|
return BootstrapConfig()
|
|
|
|
raw = bootstrap_path.read_text(encoding="utf-8").strip()
|
|
if not raw:
|
|
return BootstrapConfig()
|
|
|
|
match = BOOTSTRAP_FRONT_MATTER_RE.match(raw)
|
|
if not match:
|
|
return BootstrapConfig(prompt_body=raw)
|
|
|
|
front_matter = match.group(1).strip()
|
|
body = match.group(2).strip()
|
|
parsed = yaml.safe_load(front_matter) or {}
|
|
if not isinstance(parsed, dict):
|
|
parsed = {}
|
|
|
|
return BootstrapConfig(values=parsed, prompt_body=body)
|
|
|
|
|
|
def get_bootstrap_config_for_run(
|
|
project_root: Path,
|
|
config_name: str,
|
|
) -> BootstrapConfig:
|
|
"""Load BOOTSTRAP.md from the run workspace."""
|
|
return load_bootstrap_config(
|
|
project_root / "runs" / config_name / "BOOTSTRAP.md",
|
|
)
|
|
|
|
|
|
def save_bootstrap_config(bootstrap_path: Path, config: BootstrapConfig) -> None:
|
|
"""Persist structured bootstrap config back to BOOTSTRAP.md."""
|
|
bootstrap_path.parent.mkdir(parents=True, exist_ok=True)
|
|
values = config.values if isinstance(config.values, dict) else {}
|
|
front_matter = yaml.safe_dump(
|
|
values,
|
|
allow_unicode=True,
|
|
sort_keys=False,
|
|
).strip()
|
|
body = (config.prompt_body or "").strip()
|
|
|
|
content = f"---\n{front_matter}\n---"
|
|
if body:
|
|
content += f"\n\n{body}\n"
|
|
else:
|
|
content += "\n"
|
|
|
|
bootstrap_path.write_text(content, encoding="utf-8")
|
|
|
|
|
|
def update_bootstrap_values_for_run(
|
|
project_root: Path,
|
|
config_name: str,
|
|
updates: Dict[str, Any],
|
|
) -> BootstrapConfig:
|
|
"""Patch selected front matter keys for a run and persist them."""
|
|
bootstrap_path = project_root / "runs" / config_name / "BOOTSTRAP.md"
|
|
existing = load_bootstrap_config(bootstrap_path)
|
|
values = dict(existing.values)
|
|
values.update(updates)
|
|
updated = BootstrapConfig(values=values, prompt_body=existing.prompt_body)
|
|
save_bootstrap_config(bootstrap_path, updated)
|
|
return updated
|
|
|
|
|
|
def _coerce_bool(value: Any) -> bool:
|
|
"""Parse booleans from bootstrap-friendly string values."""
|
|
if isinstance(value, bool):
|
|
return value
|
|
if isinstance(value, str):
|
|
normalized = value.strip().lower()
|
|
if normalized in {"1", "true", "yes", "on"}:
|
|
return True
|
|
if normalized in {"0", "false", "no", "off"}:
|
|
return False
|
|
return bool(value)
|
|
|
|
|
|
def resolve_runtime_config(
|
|
project_root: Path,
|
|
config_name: str,
|
|
enable_memory: bool = False,
|
|
schedule_mode: str = "daily",
|
|
interval_minutes: int = 60,
|
|
trigger_time: str = "09:30",
|
|
) -> Dict[str, Any]:
|
|
"""Merge env defaults with run-scoped bootstrap front matter."""
|
|
bootstrap = get_bootstrap_config_for_run(project_root, config_name)
|
|
return {
|
|
"tickers": bootstrap.get("tickers")
|
|
or get_env_list("TICKERS", ["AAPL", "MSFT"]),
|
|
"initial_cash": float(
|
|
bootstrap.get(
|
|
"initial_cash",
|
|
get_env_float("INITIAL_CASH", 100000.0),
|
|
),
|
|
),
|
|
"margin_requirement": float(
|
|
bootstrap.get(
|
|
"margin_requirement",
|
|
get_env_float("MARGIN_REQUIREMENT", 0.0),
|
|
),
|
|
),
|
|
"max_comm_cycles": int(
|
|
bootstrap.get(
|
|
"max_comm_cycles",
|
|
get_env_int("MAX_COMM_CYCLES", 2),
|
|
),
|
|
),
|
|
"schedule_mode": str(
|
|
bootstrap.get("schedule_mode", schedule_mode),
|
|
).strip().lower() or schedule_mode,
|
|
"interval_minutes": int(
|
|
bootstrap.get(
|
|
"interval_minutes",
|
|
interval_minutes or get_env_int("INTERVAL_MINUTES", 60),
|
|
),
|
|
),
|
|
"trigger_time": str(
|
|
bootstrap.get("trigger_time", trigger_time),
|
|
).strip() or trigger_time,
|
|
"enable_memory": bool(enable_memory)
|
|
or _coerce_bool(bootstrap.get("enable_memory", False)),
|
|
}
|