#!/usr/bin/env python3 """Run one fresh target from a runtime-injected execution catalog.""" from __future__ import annotations import argparse from datetime import datetime, timezone import hashlib import importlib.util import json import os from pathlib import Path import re import shutil import subprocess import sys from typing import Any, Iterable import uuid CATALOG_ENV = "AGENT_TASK_EXECUTION_CATALOG" LABEL_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$") PROBE_EXPECTED = "MILESTONE_AGENT_READY" class AgentRunError(RuntimeError): """One-shot runner contract error.""" def load_policy_module(): path = ( Path(__file__).resolve().parents[2] / "orchestrate-agent-task-loop" / "scripts" / "execution_target_policy.py" ) spec = importlib.util.spec_from_file_location("epic_execution_target_policy", path) if spec is None or spec.loader is None: raise AgentRunError(f"execution catalog policy not found: {path}") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module def now() -> str: return datetime.now(timezone.utc).isoformat() def emit(event: str, **payload: Any) -> None: print(json.dumps({"event": event, **payload}, ensure_ascii=False, sort_keys=True), flush=True) def atomic_json(path: Path, value: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_suffix(path.suffix + f".tmp.{os.getpid()}") temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") os.replace(temporary, path) def process_start_token(pid: int) -> str | None: stat = Path(f"/proc/{pid}/stat") try: remainder = stat.read_text(encoding="utf-8").rsplit(")", 1)[1].split() return f"proc:{remainder[19]}" except (OSError, IndexError): return None def workspace_root(raw: str) -> Path: workspace = Path(raw).expanduser().resolve() if not workspace.is_dir(): raise AgentRunError(f"workspace directory not found: {workspace}") result = subprocess.run( ["git", "rev-parse", "--show-toplevel"], cwd=workspace, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, ) if result.returncode != 0 or Path(result.stdout.strip()).resolve() != workspace: raise AgentRunError(f"workspace must be a git repository root: {workspace}") return workspace def state_root(workspace: Path) -> Path: result = subprocess.run( ["git", "rev-parse", "--git-common-dir"], cwd=workspace, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, ) if result.returncode == 0: raw = Path(result.stdout.strip()) common = (workspace / raw).resolve() if not raw.is_absolute() else raw.resolve() if os.access(common, os.W_OK): return common / "epic-work-preparation" fallback = Path(os.environ.get("XDG_STATE_HOME", str(Path.home() / ".local" / "state"))) identity = hashlib.sha256(str(workspace).encode()).hexdigest()[:16] return fallback / "epic-work-preparation" / identity def result_file(workspace: Path, raw: str | None) -> Path | None: if raw is None: return None path = Path(raw).expanduser().resolve() root = state_root(workspace).resolve() try: path.relative_to(root) except ValueError as exc: raise AgentRunError(f"--result-file must be inside runner state root: {root}") from exc return path def persist(locator: Path, result: Path | None, record: dict[str, Any]) -> None: atomic_json(locator, record) if result is not None: atomic_json(result, record) def prompt_text(args: argparse.Namespace) -> str: if args.probe: return ( "Reply only with the result of joining MILESTONE, _AGENT, and _READY. " "Do not inspect files, call tools, or modify the workspace." ) if args.prompt is not None: return args.prompt if args.prompt_file is None: raise AgentRunError("--prompt or --prompt-file is required") path = Path(args.prompt_file).expanduser().resolve() if not path.is_file(): raise AgentRunError(f"prompt file not found: {path}") return path.read_text(encoding="utf-8") def resolve_target(catalog_path: str, target_id: str): policy = load_policy_module() try: catalog = policy.load_catalog(catalog_path) except (OSError, ValueError) as exc: raise AgentRunError(f"invalid execution catalog: {exc}") from exc target = policy.canonical_target(catalog, target_id) if target is None: raise AgentRunError(f"execution catalog target not found: {target_id}") return catalog, target def template_values(*, target, prompt: str, workspace: Path, session_id: str, attempt_dir: Path) -> dict[str, str]: return { "agent": target.agent, "attempt_dir": str(attempt_dir), "model": target.model, "prompt": prompt, "resume_session": "", "session_id": session_id, "target_id": target.catalog_id, "workspace": str(workspace), } def build_command(*, target, prompt: str, workspace: Path, session_id: str, attempt_dir: Path) -> list[str]: values = template_values( target=target, prompt=prompt, workspace=workspace, session_id=session_id, attempt_dir=attempt_dir, ) return [str(item).format_map(values) for item in target.runtime["command"]] def sanitized_command(command: list[str], prompt: str) -> list[str]: return ["" if value == prompt else value for value in command] def parser() -> argparse.ArgumentParser: value = argparse.ArgumentParser(description=__doc__) value.add_argument("--execution-catalog", default=os.environ.get(CATALOG_ENV)) value.add_argument("--target-id", required=True) value.add_argument("--workspace", required=True) prompt_group = value.add_mutually_exclusive_group() prompt_group.add_argument("--prompt") prompt_group.add_argument("--prompt-file") value.add_argument("--label", default="one-shot") value.add_argument("--probe", action="store_true") value.add_argument("--result-file") return value def execute(args: argparse.Namespace) -> int: workspace = workspace_root(args.workspace) if not args.execution_catalog: raise AgentRunError( f"--execution-catalog or {CATALOG_ENV} is required" ) catalog, target = resolve_target(args.execution_catalog, args.target_id) if not LABEL_PATTERN.fullmatch(args.label): raise AgentRunError("--label may contain only letters, digits, dot, underscore, and hyphen") prompt = prompt_text(args) result = result_file(workspace, args.result_file) executable = target.runtime["command"][0] if shutil.which(executable) is None: raise AgentRunError( f"target command not found: target_id={target.catalog_id} command={executable}" ) execution_id = f"{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}-{uuid.uuid4().hex[:12]}" root = state_root(workspace) attempt_dir = root / "runs" / f"{args.label}-{execution_id}" attempt_dir.mkdir(parents=True, exist_ok=False) stream = attempt_dir / "stream.log" locator = attempt_dir / "locator.json" session_id = str(uuid.uuid4()) values = template_values( target=target, prompt=prompt, workspace=workspace, session_id=session_id, attempt_dir=attempt_dir, ) command = build_command( target=target, prompt=prompt, workspace=workspace, session_id=session_id, attempt_dir=attempt_dir, ) environment = { str(key): str(item).format_map(values) for key, item in target.runtime.get("environment", {}).items() } record: dict[str, Any] = { "execution_id": execution_id, "label": args.label, "workspace": str(workspace), "catalog": { "source": str(catalog.source), "revision": catalog.revision, "schema_version": "1.0", }, "target_id": target.catalog_id, "agent": target.agent, "model": target.model, "command": sanitized_command(command, prompt), "prompt_sha256": hashlib.sha256(prompt.encode()).hexdigest(), "session_id": session_id, "stream_log": str(stream), "locator": str(locator), "started_at": now(), "status": "starting", } persist(locator, result, record) emit( "AGENT_STARTED", agent=target.agent, execution_id=execution_id, label=args.label, locator=str(locator), model=target.model, target_id=target.catalog_id, ) with stream.open("wb") as output: try: process = subprocess.Popen( command, cwd=workspace, env={ **os.environ, "MILESTONE_PREPARATION_EXECUTION_ID": execution_id, **environment, }, stdout=output, stderr=subprocess.STDOUT, start_new_session=True, ) except OSError as exc: record.update(status="failed", finished_at=now(), exit_code=127, error=str(exc)) persist(locator, result, record) emit("AGENT_FINISHED", execution_id=execution_id, label=args.label, result="failed", exit_code=127) return 127 record.update( status="running", agent_pid=process.pid, agent_process_start_token=process_start_token(process.pid), ) persist(locator, result, record) try: exit_code = process.wait() except KeyboardInterrupt: record.update(status="tracking", interrupted_at=now(), agent_pid=process.pid) persist(locator, result, record) emit( "AGENT_TRACKING", execution_id=execution_id, label=args.label, locator=str(locator), pid=process.pid, ) return 3 size = stream.stat().st_size status = "succeeded" if exit_code == 0 and size > 0 else "failed" if args.probe and status == "succeeded": content = stream.read_text(encoding="utf-8", errors="replace") if PROBE_EXPECTED not in content.upper(): status = "failed" exit_code = 2 record.update(status=status, finished_at=now(), exit_code=exit_code, output_bytes=size) persist(locator, result, record) emit( "AGENT_FINISHED", execution_id=execution_id, exit_code=exit_code, label=args.label, locator=str(locator), result=status, ) return exit_code if status == "succeeded" else (exit_code or 2) def main(argv: Iterable[str] | None = None) -> int: args = parser().parse_args(argv) try: return execute(args) except (AgentRunError, OSError, ValueError) as exc: emit("AGENT_FINISHED", label=getattr(args, "label", "one-shot"), result="failed", reason=str(exc)) return 2 if __name__ == "__main__": raise SystemExit(main())