← Files Astral OrchestratorARCHIVED FILE

scripts/run-agent.py

7.9 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

#!/usr/bin/env python3
"""Launch one exact Astral Orchestrator lane as a separate Codex process."""

from __future__ import annotations

import argparse
import json
import os
import stat
import subprocess
import sys
import tomllib
from pathlib import Path
from typing import NoReturn

SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPT_DIR))

from effort_settings import (  # noqa: E402
    DEFAULT_EFFORTS,
    EffortSettingsError,
    default_settings_path,
    load_efforts,
)
from codex_runtime import (  # noqa: E402
    RuntimeResolutionError,
    resolve_codex_runtime,
)


ROLE_CONTRACTS = {
    "luna": {
        "filename": "astral-orchestrator-luna-implementer.toml",
        "agent_name": "astral_orchestrator_luna_implementer",
        "model": "gpt-6-luna",
        "effort": "max",
        "sandbox": "workspace-write",
    },
    "sol": {
        "filename": "astral-orchestrator-sol-implementer.toml",
        "agent_name": "astral_orchestrator_sol_implementer",
        "model": "gpt-6-sol",
        "effort": "high",
        "sandbox": "workspace-write",
    },
    "terra": {
        "filename": "astral-orchestrator-terra-implementer.toml",
        "agent_name": "astral_orchestrator_terra_implementer",
        "model": "gpt-5.6-terra",
        "effort": "high",
        "sandbox": "workspace-write",
    },
    "reviewer": {
        "filename": "astral-orchestrator-sol-reviewer.toml",
        "agent_name": "astral_orchestrator_sol_reviewer",
        "model": "gpt-6-sol",
        "effort": "high",
        "sandbox": "workspace-write",
    },
    "legacy-luna": {
        "filename": "historical-v3.11.0/astral-orchestrator-luna-implementer.toml",
        "agent_name": "astral_orchestrator_luna_implementer",
        "model": "gpt-5.6-luna",
        "effort": "max",
        "sandbox": "workspace-write",
        "effort_lane": "luna",
    },
    "legacy-reviewer": {
        "filename": "historical-v3.11.0/astral-orchestrator-sol-reviewer.toml",
        "agent_name": "astral_orchestrator_sol_reviewer",
        "model": "gpt-5.6-sol",
        "effort": "high",
        "sandbox": "workspace-write",
        "effort_lane": "reviewer",
    },
}
MAX_PROMPT_BYTES = 1_048_576


def fail(message: str) -> NoReturn:
    print(f"ERROR: {message}", file=sys.stderr)
    raise SystemExit(1)


def regular_file(path: Path, label: str) -> Path:
    if path.is_symlink() or not path.is_file():
        fail(f"{label} must be a real regular file: {path}")
    return path


def read_prompt(path: Path) -> bytes:
    flags = os.O_RDONLY
    if hasattr(os, "O_CLOEXEC"):
        flags |= os.O_CLOEXEC
    if hasattr(os, "O_NOFOLLOW"):
        flags |= os.O_NOFOLLOW

    try:
        descriptor = os.open(path, flags)
    except OSError:
        fail(f"prompt file must be a readable regular file: {path}")

    try:
        try:
            details = os.fstat(descriptor)
            if not stat.S_ISREG(details.st_mode):
                fail(f"prompt file must be a real regular file: {path}")
            if os.name == "posix" and stat.S_IMODE(details.st_mode) & 0o077:
                fail(
                    "prompt file must be private; group or other permission bits are "
                    f"set: {path}"
                )
            if details.st_size == 0 or details.st_size > MAX_PROMPT_BYTES:
                fail(f"prompt file must contain 1 to {MAX_PROMPT_BYTES} bytes")

            chunks = []
            remaining = MAX_PROMPT_BYTES + 1
            while remaining:
                chunk = os.read(descriptor, min(65_536, remaining))
                if not chunk:
                    break
                chunks.append(chunk)
                remaining -= len(chunk)
            prompt = b"".join(chunks)
        except OSError:
            fail(f"prompt file could not be read safely: {path}")
    finally:
        os.close(descriptor)

    if not prompt or len(prompt) > MAX_PROMPT_BYTES:
        fail(f"prompt file must contain 1 to {MAX_PROMPT_BYTES} bytes")
    return prompt


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Run one model-pinned Astral Orchestrator worker or reviewer."
    )
    parser.add_argument("--role", required=True, choices=sorted(ROLE_CONTRACTS))
    parser.add_argument("--workdir", required=True)
    parser.add_argument("--prompt-file", required=True)
    parser.add_argument(
        "--settings-file",
        type=Path,
        default=default_settings_path(),
        help=argparse.SUPPRESS,
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Validate and print allowlisted route settings without starting Codex.",
    )
    return parser.parse_args()


def main() -> int:
    if sys.version_info < (3, 11):
        fail("Python 3.11 or newer is required.")

    args = parse_args()
    profile_dir = SCRIPT_DIR.parent / "agents"
    contract = ROLE_CONTRACTS[args.role]
    profile_path = regular_file(profile_dir / contract["filename"], "agent profile")

    with profile_path.open("rb") as handle:
        profile = tomllib.load(handle)
    for field in ("agent_name", "model", "effort"):
        profile_field = "name" if field == "agent_name" else (
            "model_reasoning_effort" if field == "effort" else field
        )
        if profile.get(profile_field) != contract[field]:
            fail(
                f"{profile_path.name} must set {profile_field} to {contract[field]}"
            )
    if args.role in {"reviewer", "legacy-reviewer"} and profile.get("sandbox_mode") != "workspace-write":
        fail("the reviewer profile must request a workspace-write sandbox")

    instructions = profile.get("developer_instructions")
    if not isinstance(instructions, str) or not instructions.strip():
        fail(f"{profile_path.name} has no developer instructions")

    try:
        efforts, settings_file_present = load_efforts(args.settings_file)
    except EffortSettingsError as error:
        fail(str(error))
    effort_lane = contract.get("effort_lane", args.role)
    configured_effort = efforts[effort_lane]

    prompt_path = Path(args.prompt_file).expanduser()
    prompt = read_prompt(prompt_path)
    prompt_bytes = len(prompt)

    workdir = Path(args.workdir).expanduser().resolve()
    if not workdir.is_dir():
        fail(f"workdir must be an existing directory: {workdir}")

    try:
        runtime = resolve_codex_runtime()
    except RuntimeResolutionError as error:
        fail(str(error))

    evidence = {
        "role": args.role,
        "agent_name": contract["agent_name"],
        "model": contract["model"],
        "effort": configured_effort,
        "effort_source": (
            "default"
            if configured_effort == DEFAULT_EFFORTS[effort_lane]
            else "custom"
        ),
        "native_profile_compatible": configured_effort == contract["effort"],
        "settings_file_present": settings_file_present,
        "sandbox": contract["sandbox"],
        "workdir": str(workdir),
        "prompt_bytes": prompt_bytes,
        "codex_runtime_source": runtime.source,
        "codex_version": runtime.version,
        "codex_config_probe": runtime.config_probe,
    }
    if args.dry_run:
        print(json.dumps(evidence, separators=(",", ":"), sort_keys=True))
        return 0

    command = [
        runtime.path,
        "exec",
        "--model",
        contract["model"],
        "-c",
        f"model_reasoning_effort={json.dumps(configured_effort)}",
        "-c",
        f"developer_instructions={json.dumps(instructions)}",
        "--sandbox",
        contract["sandbox"],
        "--skip-git-repo-check",
        "--cd",
        str(workdir),
        "-",
    ]

    print(
        "ASTRAL_ORCHESTRATOR_ROUTE "
        + json.dumps(evidence, separators=(",", ":"), sort_keys=True),
        flush=True,
    )
    result = subprocess.run(command, input=prompt, check=False)
    return result.returncode


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 2570ee8b87d19cc1f3521aea42534967d6231fb08862f1908e90b6b1a26ab994