"""ENTRYPOINT #1 — the nightly research run. Brings up oMLX, then runs the 3 personas strictly sequentially (one model, time-sliced), each under a rollover wall-clock budget bounded by the hard stop. Every agent's output is checkpointed to state/output//. Run with --compressed for a fast end-to-end test (short budgets). uv run python -m orchestration.run_nightly [--compressed] """ from __future__ import annotations import sys import time from datetime import datetime, timezone from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from loguru import logger # noqa: E402 from agents.graph import run_agent # noqa: E402 from agents.schemas import RunManifest # noqa: E402 from config.personas import PERSONAS # noqa: E402 from config.settings import get_settings # noqa: E402 from llm import server # noqa: E402 from llm.client import resolve_model_id # noqa: E402 from orchestration.budget import ( # noqa: E402 local_hard_stop_ts, next_deadline, next_market_trade_date, ) from orchestration.checkpoint import Checkpointer # noqa: E402 from reporting import report_writer # noqa: E402 def main(compressed: bool = False) -> int: s = get_settings() s.ensure_dirs() trade_date = next_market_trade_date(s.market_tz) logger.add(s.log_dir / f"nightly_{trade_date}.log", rotation="10 MB", retention="14 days") logger.info("=== nightly run trade_date={} profile={} compressed={} ===", trade_date, s.model_profile, compressed) if not server.ensure_ready(): logger.error("oMLX not ready; aborting.") return 1 # preflight: confirm internet before committing to a long run (warn but continue) try: from tools.web import web_search probe = web_search.invoke({"query": "stock market news today"}) logger.info("internet preflight: {}", "OK" if "http" in probe else "no results (degraded)") except Exception as e: logger.warning("internet preflight failed (continuing): {}", e) model_name = resolve_model_id() run_id = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") cp = Checkpointer(s.output_dir, trade_date) manifest = RunManifest( run_id=run_id, trade_date=trade_date, model=model_name, started_at=datetime.now(timezone.utc), status="running", ) cp.write_manifest(manifest) hard_stop = local_hard_stop_ts(s.hard_stop, s.local_tz) per_agent_cap_s = 5 * 60 if compressed else s.per_agent_wallclock_min * 60 step_budget = 12 if compressed else s.per_agent_step_budget min_steps = 4 if compressed else s.per_agent_min_steps min_seconds = 60 if compressed else s.per_agent_min_minutes * 60 outputs = [] for i, persona in enumerate(PERSONAS): now = time.time() if now >= hard_stop: logger.warning("hard stop reached; skipping {} remaining agents", len(PERSONAS) - i) break deadline = next_deadline(now, hard_stop, len(PERSONAS) - i, per_agent_cap_s) logger.info("--- {} until {} ---", persona.id, datetime.fromtimestamp(deadline).strftime("%H:%M:%S")) if not server.server_up(): # recover if a prior agent left oMLX unresponsive logger.warning("oMLX unreachable before {}; recovering...", persona.id) server.ensure_ready(warm=False) out = run_agent(persona, deadline_ts=deadline, step_budget=step_budget, min_steps=min_steps, min_seconds=min_seconds, checkpointer=cp, model_name=model_name) cp.write(out) outputs.append(out) manifest.agents.append(persona.id) try: report_writer.write_agent_report(out, trade_date) # analyst research note except Exception as e: logger.warning("agent report failed for {}: {}", persona.id, e) # combined daily report (execution results appended in the morning by run_market_open) try: daily = report_writer.write_daily_report(trade_date, outputs) report_writer.to_pdf(daily) except Exception as e: logger.warning("daily report failed: {}", e) manifest.status = "complete" manifest.ended_at = datetime.now(timezone.utc) cp.write_manifest(manifest) logger.info("=== nightly run complete; output in {} ===", cp.dir) return 0 if __name__ == "__main__": raise SystemExit(main(compressed="--compressed" in sys.argv))