""" Process backend for Lazarus Pit. Fastest spawn/teardown. Isolation comes from unique HERMES_HOME and independent Python process. No containers required. """ import os import signal import subprocess from pathlib import Path from typing import Optional from .base import Backend, BackendResult class ProcessBackend(Backend): name = "process" def __init__(self, log_dir: Optional[str] = None): self.log_dir = Path(log_dir or "/tmp/lazarus-logs") self.log_dir.mkdir(parents=True, exist_ok=True) self._pids: dict = {} def _log_path(self, cell_id: str) -> Path: return self.log_dir / f"{cell_id}.log" def spawn(self, cell_id: str, hermes_home: str, command: list, env: Optional[dict] = None) -> BackendResult: log_path = self._log_path(cell_id) run_env = dict(os.environ) run_env["HERMES_HOME"] = hermes_home if env: run_env.update(env) try: with open(log_path, "a") as log_file: proc = subprocess.Popen( command, env=run_env, stdout=log_file, stderr=subprocess.STDOUT, cwd=hermes_home, ) self._pids[cell_id] = proc.pid return BackendResult( success=True, pid=proc.pid, message=f"Spawned {cell_id} with pid {proc.pid}", ) except Exception as e: return BackendResult(success=False, stderr=str(e), message=f"Spawn failed: {e}") def probe(self, cell_id: str) -> BackendResult: pid = self._pids.get(cell_id) if not pid: return BackendResult(success=False, message="No known pid for cell") try: os.kill(pid, 0) return BackendResult(success=True, pid=pid, message="Process is alive") except OSError: return BackendResult(success=False, pid=pid, message="Process is dead") def logs(self, cell_id: str, tail: int = 50) -> BackendResult: log_path = self._log_path(cell_id) if not log_path.exists(): return BackendResult(success=True, stdout="", message="No logs yet") try: lines = log_path.read_text().splitlines() return BackendResult( success=True, stdout="\n".join(lines[-tail:]), message=f"Returned last {tail} lines", ) except Exception as e: return BackendResult(success=False, stderr=str(e), message=f"Log read failed: {e}") def close(self, cell_id: str) -> BackendResult: pid = self._pids.get(cell_id) if not pid: return BackendResult(success=False, message="No known pid for cell") try: os.kill(pid, signal.SIGTERM) return BackendResult(success=True, pid=pid, message="Sent SIGTERM") except OSError as e: return BackendResult(success=False, stderr=str(e), message=f"Close failed: {e}") def destroy(self, cell_id: str) -> BackendResult: res = self.close(cell_id) log_path = self._log_path(cell_id) if log_path.exists(): log_path.unlink() self._pids.pop(cell_id, None) return BackendResult( success=res.success, pid=res.pid, message=f"Destroyed {cell_id}", )