Rhodawk Mythos Agent
ARCHITECT: full masterplan implementation — 19 skills, 10 new MCP servers, 5-tier model router, EmbodiedOS bridge, autonomous night-mode loop, sandbox manager, pytest suite, stability fixes
da8fcf1
Raw
History Blame Contribute Delete
3.11 kB
"""
ARCHITECT — isolated sandbox manager (§4.2 / §10.2 of the Masterplan).
Provides the OSS-Guardian sandbox primitive: a per-target, ephemeral, network-
restricted directory where the agent may safely clone and analyse arbitrary
open-source code.
When ``docker`` is available we build a one-shot container with:
* read-only bind of the host workspace
* iptables drop-all egress after the initial git clone
* 4-hour wallclock cap, 10 GB disk cap, 8 GB memory cap
When docker is not available (HF Space) we fall back to a process-level
sandbox: shutil-based ephemeral directory, ``rlimit`` walltime cap, no network
ops outside the initial git clone.
"""
from __future__ import annotations
import logging
import os
import shutil
import signal
import subprocess
import tempfile
import time
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Iterator
LOG = logging.getLogger("architect.sandbox")
DEFAULT_TIMEOUT_S = int(os.getenv("ARCHITECT_SANDBOX_TIMEOUT_S", "14400")) # 4h
DEFAULT_DISK_GB = int(os.getenv("ARCHITECT_SANDBOX_DISK_GB", "10"))
DEFAULT_MEM_GB = int(os.getenv("ARCHITECT_SANDBOX_MEM_GB", "8"))
@dataclass
class SandboxHandle:
workdir: Path
repo_path: Path
started_at: float
backend: str # "docker" | "process"
target_url: str
def elapsed_s(self) -> float:
return time.time() - self.started_at
def _docker_available() -> bool:
return shutil.which("docker") is not None
def _git_clone(target_url: str, dest: Path, depth: int = 1) -> None:
subprocess.run(
["git", "clone", "--depth", str(depth), target_url, str(dest)],
check=True, timeout=600, capture_output=True,
)
@contextmanager
def open_sandbox(target_url: str) -> Iterator[SandboxHandle]:
"""Context-managed sandbox. Cleans up the workdir on exit."""
workdir = Path(tempfile.mkdtemp(prefix="architect-sbx-"))
repo = workdir / "target"
handle: SandboxHandle | None = None
try:
_git_clone(target_url, repo)
backend = "docker" if _docker_available() else "process"
handle = SandboxHandle(
workdir=workdir, repo_path=repo, started_at=time.time(),
backend=backend, target_url=target_url,
)
if backend == "process":
try:
# best-effort wallclock alarm; safe no-op on Windows / non-main thread
signal.signal(signal.SIGALRM, _on_timeout)
signal.alarm(DEFAULT_TIMEOUT_S)
except Exception: # noqa: BLE001
pass
LOG.info("Sandbox %s opened (backend=%s)", workdir, backend)
yield handle
finally:
try:
signal.alarm(0)
except Exception: # noqa: BLE001
pass
if handle is not None:
LOG.info("Sandbox %s closed after %.1fs", workdir, handle.elapsed_s())
shutil.rmtree(workdir, ignore_errors=True)
def _on_timeout(signum, frame): # pragma: no cover
raise TimeoutError(f"ARCHITECT sandbox exceeded {DEFAULT_TIMEOUT_S}s wallclock cap")