Aetherius / services /code_kernel.py
KingOfThoughtFleuren's picture
Upload 29 files
bb76062 verified
Raw
History Blame Contribute Delete
6.41 kB
"""
code_kernel.py
Sandboxed Python execution kernel for Aetherius.
Runs arbitrary Python code in a subprocess with:
- Configurable timeout (default 30s)
- stdout/stderr capture
- Output truncation to prevent memory flooding
- Matplotlib figure auto-saving (if code generates plots)
- On Kaggle T4: full numpy/scipy/torch CUDA access in the subprocess
"""
from __future__ import annotations
import os
import sys
import uuid
import tempfile
import subprocess
import json
TIMEOUT_DEFAULT = 30
MAX_OUTPUT = 8000
_PLOTS_DIR_ENV = "AETHERIUS_PAINTINGS_DIR"
def _get_plots_dir() -> str:
try:
import services.config as cfg
return cfg.PAINTINGS_DIR.rstrip("/")
except Exception:
return os.environ.get(_PLOTS_DIR_ENV, "/tmp/aetherius_plots")
_PREAMBLE = """\
import os, sys, warnings
warnings.filterwarnings("ignore")
# Matplotlib non-interactive backend so plots save without a display
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
_PLOT_SAVE_DIR = {plot_dir!r}
os.makedirs(_PLOT_SAVE_DIR, exist_ok=True)
_PLOT_PATH = None
def _save_current_figure():
global _PLOT_PATH
import uuid
_PLOT_PATH = os.path.join(_PLOT_SAVE_DIR, f"plot_{{uuid.uuid4().hex[:8]}}.png")
plt.savefig(_PLOT_PATH, dpi=150, bbox_inches="tight")
plt.close("all")
print(f"[code_kernel] Plot saved: {{_PLOT_PATH}}")
import atexit
atexit.register(lambda: _save_current_figure() if plt.get_fignums() else None)
# ── User code begins ──────────────────────────────────────────────────────────
"""
def execute(code: str, timeout: int = TIMEOUT_DEFAULT) -> dict:
"""
Execute Python code in an isolated subprocess.
Returns:
{
"success": bool,
"stdout": str,
"stderr": str,
"returncode": int,
"plot_path": str | None, # set if matplotlib figure was saved
}
"""
plots_dir = _get_plots_dir()
os.makedirs(plots_dir, exist_ok=True)
full_code = _PREAMBLE.format(plot_dir=plots_dir) + code
tmp = tempfile.NamedTemporaryFile(
mode="w", suffix=".py", delete=False, encoding="utf-8"
)
try:
tmp.write(full_code)
tmp.close()
proc = subprocess.run(
[sys.executable, tmp.name],
capture_output=True,
text=True,
timeout=timeout,
)
stdout = proc.stdout[:MAX_OUTPUT]
stderr = proc.stderr[:MAX_OUTPUT]
success = proc.returncode == 0
# Check if a plot was saved (preamble prints the path)
plot_path = None
for line in stdout.splitlines():
if line.startswith("[code_kernel] Plot saved:"):
plot_path = line.split(":", 1)[1].strip()
break
return {
"success": success,
"stdout": stdout,
"stderr": stderr,
"returncode": proc.returncode,
"plot_path": plot_path,
}
except subprocess.TimeoutExpired:
return {
"success": False,
"stdout": "",
"stderr": f"[code_kernel] Timed out after {timeout}s.",
"returncode": -1,
"plot_path": None,
}
except Exception as exc:
return {
"success": False,
"stdout": "",
"stderr": f"[code_kernel] Internal error: {exc}",
"returncode": -1,
"plot_path": None,
}
finally:
try:
os.unlink(tmp.name)
except Exception:
pass
def execute_sandboxed_validation(file_path: str, timeout: int = 15) -> dict:
"""
Runs a .py file in a subprocess for Pass-2 validation during
stage_and_verify_code_patch. The file is executed with a minimal
import-only simulation: the code is loaded as a module to catch
runtime import errors, circular dependencies, and top-level
exceptions — without triggering any side effects.
Returns {"success": bool, "error": str | None, "stdout": str}
"""
validation_wrapper = f"""
import sys, traceback
try:
import importlib.util
spec = importlib.util.spec_from_file_location("_patch_validation", {file_path!r})
mod = importlib.util.module_from_spec(spec)
# We do NOT exec the module body for safety — just check it compiles
with open({file_path!r}, 'r', encoding='utf-8') as f:
source = f.read()
compile(source, {file_path!r}, 'exec')
print("VALIDATION_OK")
except SyntaxError as se:
print(f"SYNTAX_ERROR: {{se}}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"RUNTIME_ERROR: {{e}}", file=sys.stderr)
sys.exit(2)
"""
tmp = tempfile.NamedTemporaryFile(
mode="w", suffix=".py", delete=False, encoding="utf-8"
)
try:
tmp.write(validation_wrapper)
tmp.close()
proc = subprocess.run(
[sys.executable, tmp.name],
capture_output=True, text=True, timeout=timeout,
)
if proc.returncode == 0:
return {"success": True, "error": None, "stdout": proc.stdout.strip()}
return {
"success": False,
"error": (proc.stderr or proc.stdout).strip(),
"stdout": proc.stdout.strip(),
}
except subprocess.TimeoutExpired:
return {"success": False, "error": "Validation timed out.", "stdout": ""}
except Exception as exc:
return {"success": False, "error": str(exc), "stdout": ""}
finally:
try:
os.unlink(tmp.name)
except Exception:
pass
def format_result(result: dict) -> str:
"""Format execution result as a readable string for Aetherius."""
parts = []
if result["success"]:
parts.append("✓ Execution succeeded.")
else:
parts.append(f"✗ Execution failed (exit {result['returncode']}).")
if result["stdout"]:
parts.append(f"Output:\n{result['stdout']}")
if result["stderr"]:
label = "Warnings/Errors" if result["success"] else "Error"
parts.append(f"{label}:\n{result['stderr']}")
if result["plot_path"]:
parts.append(f"[AETHERIUS_PAINTING]\nPATH:{result['plot_path']}\nSTATEMENT:code-generated plot")
return "\n\n".join(parts) if parts else "No output."