CoolFace
Apppublic

PixelCraftLab/sysadmin_troubleshooter

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
kernel_env_environment.py393 linesDownload Raw Back to server
1# Copyright (c) Meta Platforms, Inc. and affiliates.2# All rights reserved.3#4# This source code is licensed under the BSD-style license found in the5# LICENSE file in the root directory of this source tree.6 7"""SysAdmin Environment implementation."""8 9from __future__ import annotations10 11import re12from typing import Any, Dict, List, Optional13from uuid import uuid414 15from openenv.core.env_server.interfaces import Environment16from openenv.core.env_server.types import State17from openenv.core.rubrics.base import Rubric18 19try:20    from ..models import KernelAction, KernelObservation21except ImportError:22    from models import KernelAction, KernelObservation23 24 25# ---------------------------------------------------------------------------26# Per-task Rubrics — scores STRICTLY in (0, 1): 0.1 = not done, 0.9 = done27# ---------------------------------------------------------------------------28 29class Task1KillRogueRubric(Rubric):30    """31    Grader for Task 1: Kill the rogue_app process.32 33    Score is strictly between 0 and 1:34      0.1 — rogue_app is still running (task not done)35      0.9 — rogue_app has been killed (task done)36    """37 38    def forward(self, action: Any, observation: Any) -> float:39        tasks: Dict = getattr(observation, "tasks_status", {}) or {}40        done: bool = bool(tasks.get("task_1_kill_rogue", False))41        # Never return exactly 0.0 or 1.0 — strictly in (0, 1)42        return 0.9 if done else 0.143 44 45class Task2NginxActiveRubric(Rubric):46    """47    Grader for Task 2: Start the nginx service.48 49    Score is strictly between 0 and 1:50      0.1 — nginx is still inactive (task not done)51      0.9 — nginx is active (task done)52    """53 54    def forward(self, action: Any, observation: Any) -> float:55        tasks: Dict = getattr(observation, "tasks_status", {}) or {}56        done: bool = bool(tasks.get("task_2_nginx_active", False))57        return 0.9 if done else 0.158 59 60class Task3NginxConfigRubric(Rubric):61    """62    Grader for Task 3: Fix the nginx config typo ('liten' -> 'listen').63 64    Score is strictly between 0 and 1:65      0.1 — typo still present (task not done)66      0.5 — typo fixed but nginx not yet restarted (partial credit)67      0.9 — typo fixed and nginx is active (task fully done)68    """69 70    def forward(self, action: Any, observation: Any) -> float:71        tasks: Dict = getattr(observation, "tasks_status", {}) or {}72        state: Dict = getattr(observation, "system_state", {}) or {}73 74        config_fixed: bool = bool(tasks.get("task_3_nginx_config_fixed", False))75        nginx_active: bool = "nginx" in (state.get("active_services") or [])76 77        if config_fixed and nginx_active:78            return 0.9   # fully done79        elif config_fixed:80            return 0.5   # typo fixed, nginx not yet started81        else:82            return 0.1   # nothing done yet83 84 85class SysAdminRubric(Rubric):86    """87    Master rubric for the SysAdmin environment.88 89    Aggregates 3 per-task child rubrics with weighted scoring.90    Weights: task_1=0.20, task_2=0.30, task_3=0.5091    """92 93    def __init__(self) -> None:94        super().__init__()95        # Registering as attributes auto-registers them as named children96        self.task_1_kill_rogue = Task1KillRogueRubric()97        self.task_2_nginx_active = Task2NginxActiveRubric()98        self.task_3_nginx_config_fixed = Task3NginxConfigRubric()99 100    def forward(self, action: Any, observation: Any) -> float:101        s1 = self.task_1_kill_rogue(action, observation)102        s2 = self.task_2_nginx_active(action, observation)103        s3 = self.task_3_nginx_config_fixed(action, observation)104        # Weighted aggregate — also stays strictly in (0, 1)105        score = s1 * 0.20 + s2 * 0.30 + s3 * 0.50106        return round(score, 4)107 108 109# ---------------------------------------------------------------------------110# Mock Linux system simulator111# ---------------------------------------------------------------------------112 113class MockSystem:114    """Simulates a Linux-like system state for safely running SysAdmin tasks."""115 116    def __init__(self):117        self.processes = {118            1: {"name": "systemd", "cpu": 0.1},119            1024: {"name": "rogue_app", "cpu": 15.5},120            2048: {"name": "sshd", "cpu": 0.2},121        }122        self.services = {123            "nginx": {"status": "inactive", "enabled": True},124            "ssh": {"status": "active", "enabled": True},125            "cron": {"status": "active", "enabled": True},126        }127        self.files = {128            "/etc/nginx/nginx.conf": "server {\n    liten 80;\n    server_name localhost;\n}",129            "/var/log/syslog": "Apr  6 09:00:00 localhost systemd[1]: Started SSH service.\n",130        }131        self.last_command_output = ""132 133    def run_command(self, command: str) -> tuple[str, str, int]:134        """Simple shell command parser."""135        parts = command.strip().split()136        if not parts:137            return "", "", 0138 139        cmd = parts[0]140        args = parts[1:]141 142        if cmd == "ps":143            output = "PID   COMMAND      %CPU\n"144            for pid, info in self.processes.items():145                output += f"{pid:<5} {info['name']:<12} {info['cpu']}\n"146            return output, "", 0147 148        elif cmd == "kill":149            if not args:150                return "", "kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]", 1151            try:152                pid = int(args[0])153                if pid in self.processes:154                    del self.processes[pid]155                    return "", "", 0156                else:157                    return "", f"kill: ({pid}) - No such process", 1158            except ValueError:159                return "", f"kill: {args[0]}: arguments must be process IDs", 1160 161        elif cmd == "killall":162            if not args:163                return "", "killall: usage: killall [-Z context] [-e] [-g] [-i] [-m] [-o] [-q] [-r] [-s signal] [-u user] [-v] [-w] [-I] [-V] name ...", 1164            name = args[0]165            to_kill = [pid for pid, info in self.processes.items() if info["name"] == name]166            if not to_kill:167                return "", f"{name}: no process found", 1168            for pid in to_kill:169                del self.processes[pid]170            return "", "", 0171 172        elif cmd == "systemctl":173            if len(args) < 2:174                return "", "systemctl: too few arguments", 1175            action = args[0]176            service = args[1]177            if service not in self.services:178                return "", f"Failed to {action} {service}.service: Unit {service}.service not found.", 5179 180            if action == "status":181                status = self.services[service]["status"]182                output = f"● {service}.service\n   Loaded: loaded\n   Active: {status}\n"183                return output, "", 0184            elif action == "start":185                # nginx fails if config has typo186                if service == "nginx" and "liten" in self.files.get("/etc/nginx/nginx.conf", ""):187                    return "", "Job for nginx.service failed because the control process exited with error-code.", 1188                self.services[service]["status"] = "active"189                return "", "", 0190            elif action == "stop":191                self.services[service]["status"] = "inactive"192                return "", "", 0193            elif action == "restart":194                if service == "nginx" and "liten" in self.files.get("/etc/nginx/nginx.conf", ""):195                    return "", "Job for nginx.service failed. See 'systemctl status nginx.service' and 'journalctl -xe' for details.", 1196                self.services[service]["status"] = "active"197                return "", "", 0198 199        elif cmd == "cat":200            if not args:201                return "", "cat: usage: cat [file ...]", 1202            path = args[0]203            if path in self.files:204                return self.files[path], "", 0205            return "", f"cat: {path}: No such file or directory", 1206 207        elif cmd == "sed":208            # Simple implementation for 'sed -i 's/old/new/g' file'209            if "-i" in args:210                try:211                    pattern_idx = args.index("-i") + 1212                    file_idx = pattern_idx + 1213                    pattern = args[pattern_idx]214                    path = args[file_idx]215 216                    if path not in self.files:217                        return "", f"sed: can't read {path}: No such file or directory", 2218 219                    match = re.match(r"s/(.*)/(.*)/g", pattern.strip("'"))220                    if match:221                        old, new = match.groups()222                        self.files[path] = self.files[path].replace(old, new)223                        return "", "", 0224                except (ValueError, IndexError):225                    pass226            return "", "sed: invalid option or pattern", 1227 228        return "", f"bash: {cmd}: command not found", 127229 230 231# ---------------------------------------------------------------------------232# KernelEnvironment233# ---------------------------------------------------------------------------234 235class KernelEnvironment(Environment):236    """237    Real-world SysAdmin troubleshooting environment.238 239    The agent must:240      1. [Easy]   Kill the rogue_app process (PID 1024)241      2. [Medium] Start the nginx service242      3. [Hard]   Fix the typo in /etc/nginx/nginx.conf and restart nginx243 244    Each task is scored by a dedicated Rubric child (score strictly in (0,1)).245    """246 247    SUPPORTS_CONCURRENT_SESSIONS: bool = True248 249    def __init__(250        self,251        *,252        max_steps: int = 15,253        transform: Optional[Any] = None,254        rubric: Optional[Any] = None,255    ):256        # Always use SysAdminRubric (override any passed-in rubric)257        super().__init__(transform=transform, rubric=SysAdminRubric())258        self._max_steps = max_steps259        self._reset_count = 0260        self._terminated = False261        self._cumulative_reward = 0.0262        self._system = MockSystem()263        self._state = self._build_state(episode_id=str(uuid4()))264 265    def _build_state(self, *, episode_id: str, step_count: int = 0) -> State:266        return State(267            episode_id=episode_id,268            step_count=step_count,269            terminated=self._terminated,270            max_steps=self._max_steps,271            cumulative_reward=round(self._cumulative_reward, 4),272            reset_count=self._reset_count,273        )274 275    def _get_system_summary(self) -> Dict[str, Any]:276        return {277            "active_services": [s for s, v in self._system.services.items() if v["status"] == "active"],278            "running_processes": [p["name"] for p in self._system.processes.values()],279            "nginx_config_ok": "liten" not in self._system.files.get("/etc/nginx/nginx.conf", ""),280        }281 282    def _get_tasks_status(self) -> Dict[str, bool]:283        summary = self._get_system_summary()284        return {285            "task_1_kill_rogue": "rogue_app" not in summary["running_processes"],286            "task_2_nginx_active": "nginx" in summary["active_services"],287            "task_3_nginx_config_fixed": summary["nginx_config_ok"],288        }289 290    def _compute_reward(self) -> float:291        """292        Per-step reward: incremental progress since last step.293        Returns a value in [0, 1). Never exactly 1.0 (max 0.5+0.3+0.2=1.0294        but cumulative prevents reaching exact 1.0 in a single step).295        """296        status = self._get_tasks_status()297        reward = 0.0298        if status["task_1_kill_rogue"]:299            reward += 0.2300        if status["task_2_nginx_active"]:301            reward += 0.3302        if status["task_3_nginx_config_fixed"]:303            reward += 0.5304 305        step_reward = max(0.0, reward - self._cumulative_reward)306        return round(step_reward, 4)307 308    def reset(309        self,310        seed: Optional[int] = None,311        episode_id: Optional[str] = None,312        **_: Any,313    ) -> KernelObservation:314        del seed315        self._reset_rubric()316        self._reset_count += 1317        self._terminated = False318        self._cumulative_reward = 0.0319        self._system = MockSystem()320        self._state = self._build_state(episode_id=episode_id or str(uuid4()))321 322        return KernelObservation(323            stdout=(324                "System boot complete. Welcome to SysAdmin Shell.\n"325                "Tasks:\n"326                "1. [Easy]   Kill the rogue_app process (PID 1024).\n"327                "2. [Medium] Start the nginx service.\n"328                "3. [Hard]   Fix the typo in /etc/nginx/nginx.conf "329                "('liten' -> 'listen') and restart nginx."330            ),331            system_state=self._get_system_summary(),332            tasks_status=self._get_tasks_status(),333            done=False,334            reward=0.0,335            metadata={336                "episode_id": self._state.episode_id,337                "step_count": self._state.step_count,338            },339        )340 341    def step(342        self,343        action: KernelAction,344        timeout_s: Optional[float] = None,345        **_: Any,346    ) -> KernelObservation:347        if self._terminated:348            raise RuntimeError("episode is terminated; call reset() before step() again")349 350        self._state.step_count += 1351 352        stdout, stderr, exit_code = self._system.run_command(action.command)353 354        step_reward = self._compute_reward()355        self._cumulative_reward += step_reward356 357        tasks = self._get_tasks_status()358        self._terminated = (self._state.step_count >= self._max_steps) or all(tasks.values())359 360        observation = KernelObservation(361            stdout=stdout,362            stderr=stderr,363            exit_code=exit_code,364            system_state=self._get_system_summary(),365            tasks_status=tasks,366            done=self._terminated,367            reward=step_reward,368            metadata={369                "episode_id": self._state.episode_id,370                "step_count": self._state.step_count,371                "cumulative_reward": round(self._cumulative_reward, 4),372            },373        )374 375        self._state = self._build_state(376            episode_id=self._state.episode_id,377            step_count=self._state.step_count,378        )379 380        return self._apply_transform(observation)381 382    @property383    def state(self) -> State:384        return self._state385 386    def get_metadata(self):387        metadata = super().get_metadata()388        metadata.description = (389            "A real-world SysAdmin troubleshooting environment where an agent "390            "identifies and fixes system issues using shell commands."391        )392        return metadata393