Spaces:
Running
Running
Aakarsh2007
feat(engine): InfraMindEnv β seeded reset, judge mode, episode trace export, feedback learning, FeedbackLearner, performance metrics
401e6a5 Download env/engine.py from aakarsh2007/infraMind: direct link, hf CLI and curl.
- Browser
- Download file 21.5 kB
-
https://huggingface.co/spaces/aakarsh2007/infraMind/resolve/main/env/engine.py
- Command line
-
hf download hf://spaces/aakarsh2007/infraMind/env/engine.py
-
curl -L -o engine.py https://huggingface.co/spaces/aakarsh2007/infraMind/resolve/main/env/engine.py
21.5 kB
| """ | |
| InfraMind β Main environment engine. | |
| Seeded stochastic simulation + deterministic grading. | |
| Features: 5 tasks, leaderboard, run history, feedback learning, | |
| agent memory, dynamic difficulty, judge mode, episode trace export, | |
| custom scenarios, concurrent session support. | |
| """ | |
| from __future__ import annotations | |
| import threading, time, uuid, json, random | |
| from copy import deepcopy | |
| from typing import Any, Dict, List, Optional, Tuple | |
| from env.models import ( | |
| Action, EpisodeTrace, FeedbackRequest, CustomScenarioRequest, | |
| Observation, Reward, SkillBreakdown, | |
| ) | |
| from env.scenarios import SCENARIOS | |
| from env.scenarios.base import BaseScenario | |
| TASK_META = [ | |
| {"id": "memory_leak", "name": "Task 1 β Memory Leak", "difficulty": "easy", "max_steps": 20, "description": "Fix an unbounded cache causing OOM in a Node.js API.", "tags": ["nodejs","cache","memory"]}, | |
| {"id": "db_deadlock", "name": "Task 2 β Database Deadlock", "difficulty": "medium", "max_steps": 30, "description": "Fix inconsistent lock ordering causing PostgreSQL deadlocks.", "tags": ["postgres","concurrency","transactions"]}, | |
| {"id": "cascade_failure","name": "Task 3 β Distributed Cascade", "difficulty": "hard", "max_steps": 40, "description": "Find the Redis timeout root cause behind a 3-service cascade failure.", "tags": ["redis","microservices","cascade"]}, | |
| {"id": "cpu_spike", "name": "Task 4 β CPU Spike / Infinite Loop", "difficulty": "medium-hard", "max_steps": 25, "description": "Fix a recursive sanitizer with no depth limit causing 100% CPU.", "tags": ["nodejs","recursion","cpu"]}, | |
| {"id": "auth_bypass", "name": "Task 5 β Auth Bypass (Security)", "difficulty": "hard", "max_steps": 30, "description": "Patch a JWT 'none' algorithm vulnerability before the breach escalates.", "tags": ["security","jwt","auth"]}, | |
| ] | |
| class FeedbackRecord: | |
| def __init__(self, run_id, task_id, rating, comment, correct_fix, suggested_improvement): | |
| self.run_id = run_id; self.task_id = task_id; self.rating = rating | |
| self.comment = comment; self.correct_fix = correct_fix | |
| self.suggested_improvement = suggested_improvement | |
| self.timestamp = time.time() | |
| def to_dict(self) -> dict: | |
| return {"run_id": self.run_id, "task_id": self.task_id, "rating": self.rating, | |
| "comment": self.comment, "timestamp": self.timestamp} | |
| class RunRecord: | |
| def __init__(self, run_id, task_id, model, reward, steps, escalated, | |
| butterfly, post_mortem, duration_s, difficulty, seed, | |
| skill_breakdown, failure_report, trace): | |
| self.run_id = run_id; self.task_id = task_id; self.model = model | |
| self.reward = reward; self.steps = steps; self.escalated = escalated | |
| self.butterfly = butterfly; self.post_mortem = post_mortem | |
| self.duration_s = duration_s; self.difficulty = difficulty | |
| self.seed = seed; self.timestamp = time.time() | |
| self.skill_breakdown = skill_breakdown | |
| self.failure_report = failure_report | |
| self.trace = trace # Full episode trace for export | |
| def to_dict(self) -> dict: | |
| return { | |
| "run_id": self.run_id, "task_id": self.task_id, "model": self.model, | |
| "reward": self.reward, "steps": self.steps, "escalated": self.escalated, | |
| "butterfly_triggered": self.butterfly, "duration_s": round(self.duration_s, 2), | |
| "difficulty": self.difficulty, "seed": self.seed, "timestamp": self.timestamp, | |
| "post_mortem": self.post_mortem, | |
| "skill_breakdown": self.skill_breakdown.model_dump() if self.skill_breakdown else None, | |
| "failure_report": self.failure_report.model_dump() if self.failure_report else None, | |
| } | |
| class AgentMemory: | |
| def __init__(self): | |
| self._memory: List[Dict] = [] | |
| self._lock = threading.RLock() | |
| def record(self, task_id: str, root_cause: str, fix_summary: str, reward: float): | |
| with self._lock: | |
| self._memory.append({"task_id": task_id, "root_cause": root_cause, | |
| "fix_summary": fix_summary, "reward": reward, | |
| "timestamp": time.time()}) | |
| if len(self._memory) > 100: | |
| self._memory = self._memory[-100:] | |
| def hints_for(self, task_id: str) -> List[str]: | |
| with self._lock: | |
| relevant = [m for m in self._memory if m["task_id"] == task_id and m["reward"] > 0.5] | |
| if not relevant: | |
| return [] | |
| best = sorted(relevant, key=lambda x: -x["reward"])[:2] | |
| return [f"Memory: Previous fix for {m['task_id']}: {m['fix_summary']} (reward={m['reward']:.2f})" | |
| for m in best] | |
| def all(self) -> List[dict]: | |
| with self._lock: | |
| return list(reversed(self._memory)) | |
| class DifficultyAdapter: | |
| def __init__(self): | |
| self._history: Dict[str, List[float]] = {} | |
| self._lock = threading.RLock() | |
| def record(self, task_id: str, reward: float): | |
| with self._lock: | |
| self._history.setdefault(task_id, []).append(reward) | |
| if len(self._history[task_id]) > 10: | |
| self._history[task_id] = self._history[task_id][-10:] | |
| def difficulty_for(self, task_id: str) -> float: | |
| with self._lock: | |
| history = self._history.get(task_id, []) | |
| if len(history) < 3: | |
| return 1.0 | |
| avg = sum(history[-3:]) / 3 | |
| if avg > 0.8: | |
| return min(2.0, 1.0 + (avg - 0.8) * 5) | |
| elif avg < 0.3: | |
| return max(0.5, 1.0 - (0.3 - avg) * 2) | |
| return 1.0 | |
| def all_difficulties(self) -> Dict[str, float]: | |
| with self._lock: | |
| return {tid: self.difficulty_for(tid) for tid in self._history} | |
| class FeedbackLearner: | |
| """ | |
| Real feedback learning β adjusts reward weights and noise level | |
| based on human ratings. Stored per task_id. | |
| """ | |
| def __init__(self): | |
| self._lock = threading.RLock() | |
| # Per-task reward weight adjustments | |
| self._weight_adjustments: Dict[str, Dict[str, float]] = {} | |
| # Bad patterns to penalize | |
| self._bad_patterns: Dict[str, float] = {} | |
| # Noise level adjustment | |
| self._noise_adjustment: float = 0.0 | |
| def apply_feedback(self, task_id: str, rating: str, comment: str, correct_fix: str): | |
| with self._lock: | |
| adj = self._weight_adjustments.setdefault(task_id, { | |
| "patch_correctness": 0.0, | |
| "metric_improvement": 0.0, | |
| "root_cause_identified": 0.0, | |
| }) | |
| if rating == "thumbs_up": | |
| # Positive feedback β slightly increase patch correctness weight | |
| adj["patch_correctness"] = min(0.1, adj["patch_correctness"] + 0.02) | |
| elif rating == "thumbs_down": | |
| # Negative feedback β increase root cause weight (agent missed it) | |
| adj["root_cause_identified"] = min(0.1, adj["root_cause_identified"] + 0.02) | |
| self._noise_adjustment = max(-0.2, self._noise_adjustment - 0.05) | |
| # Track bad patterns from comments | |
| if comment: | |
| for pattern in ["restart spam", "escalate", "rollback", "wrong file"]: | |
| if pattern in comment.lower(): | |
| self._bad_patterns[pattern] = min(2.0, self._bad_patterns.get(pattern, 1.0) + 0.1) | |
| def get_adjustments(self, task_id: str) -> Dict[str, float]: | |
| with self._lock: | |
| return dict(self._weight_adjustments.get(task_id, {})) | |
| def get_noise_adjustment(self) -> float: | |
| with self._lock: | |
| return self._noise_adjustment | |
| def summary(self) -> dict: | |
| with self._lock: | |
| return { | |
| "weight_adjustments": dict(self._weight_adjustments), | |
| "bad_patterns": dict(self._bad_patterns), | |
| "noise_adjustment": self._noise_adjustment, | |
| } | |
| class InfraMindEnv: | |
| """ | |
| InfraMind OpenEnv β thread-safe, seeded, reproducible. | |
| Deterministic grading + dynamic simulation surface. | |
| """ | |
| TASK_IDS = [t["id"] for t in TASK_META] | |
| def __init__(self): | |
| self._lock = threading.RLock() | |
| self._scenario: Optional[BaseScenario] = None | |
| self._last_obs: Optional[Observation] = None | |
| self._run_history: List[RunRecord] = [] | |
| self._feedback_history: List[FeedbackRecord] = [] | |
| self._current_run_id: Optional[str] = None | |
| self._current_model: str = "unknown" | |
| self._current_seed: int = 42 | |
| self._episode_start: float = 0.0 | |
| self._memory = AgentMemory() | |
| self._difficulty_adapter = DifficultyAdapter() | |
| self._feedback_learner = FeedbackLearner() | |
| self._custom_scenarios: Dict[str, Any] = {} | |
| # ββ OpenEnv interface βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def reset(self, task_id: Optional[str] = None, model: str = "unknown", | |
| seed: Optional[int] = None) -> Observation: | |
| with self._lock: | |
| tid = task_id or "memory_leak" | |
| if tid not in SCENARIOS and tid not in self._custom_scenarios: | |
| raise ValueError(f"Unknown task_id '{tid}'. Valid: {list(SCENARIOS.keys()) + list(self._custom_scenarios.keys())}") | |
| difficulty = self._difficulty_adapter.difficulty_for(tid) | |
| # Use provided seed or generate one β stored for reproducibility | |
| actual_seed = seed if seed is not None else random.randint(1000, 99999) | |
| self._current_seed = actual_seed | |
| if tid in self._custom_scenarios: | |
| from env.scenarios.custom import CustomScenario | |
| self._scenario = CustomScenario(self._custom_scenarios[tid], difficulty, actual_seed) | |
| else: | |
| self._scenario = SCENARIOS[tid](difficulty, actual_seed) | |
| hints = self._memory.hints_for(tid) | |
| self._scenario._memory_hints_override = hints | |
| self._current_run_id = str(uuid.uuid4())[:8] | |
| self._current_model = model | |
| self._episode_start = time.time() | |
| obs = self._scenario.reset() | |
| obs.memory_hints = hints | |
| self._last_obs = obs | |
| return obs | |
| def step(self, action: Action) -> Tuple[Observation, Reward, bool, Dict[str, Any]]: | |
| with self._lock: | |
| if self._scenario is None: | |
| raise RuntimeError("Call reset() before step()") | |
| obs, reward, done, info = self._scenario.step(action) | |
| self._last_obs = obs | |
| if done: | |
| self._record_run(reward, info) | |
| if reward.post_mortem: | |
| self._memory.record( | |
| self._scenario.task_id, | |
| str(reward.post_mortem.get("root_cause", "")), | |
| str(reward.post_mortem.get("optimal_fix", reward.reason)), | |
| reward.total, | |
| ) | |
| self._difficulty_adapter.record(self._scenario.task_id, reward.total) | |
| return obs, reward, done, info | |
| def state(self) -> Dict[str, Any]: | |
| with self._lock: | |
| if self._scenario is None: | |
| return {"status": "not_started", "message": "Call reset() to begin."} | |
| s = self._scenario.state() | |
| s.update({ | |
| "run_id": self._current_run_id, | |
| "model": self._current_model, | |
| "seed": self._current_seed, | |
| "elapsed_s": round(time.time() - self._episode_start, 1), | |
| }) | |
| return s | |
| def tasks(self) -> list: | |
| tasks = list(TASK_META) | |
| for name, cfg in self._custom_scenarios.items(): | |
| tasks.append({"id": name, "name": cfg["name"], "difficulty": cfg["difficulty"], | |
| "max_steps": 25, "description": cfg["description"], "tags": ["custom"]}) | |
| return tasks | |
| # ββ Judge mode ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def judge_run_all(self, seed: int = 42) -> Dict[str, Any]: | |
| """ | |
| Run a deterministic baseline evaluation across all tasks. | |
| Uses a simple rule-based agent (no LLM) for reproducibility. | |
| Returns per-task scores, avg, and diagnostics. | |
| """ | |
| results: Dict[str, Any] = {} | |
| task_rewards: List[float] = [] | |
| for task_id in self.TASK_IDS: | |
| try: | |
| obs = self.reset(task_id=task_id, model="judge_baseline", seed=seed) | |
| # Simple rule-based agent: list β search β read β submit | |
| actions = [ | |
| Action(agent="debugger", action_type="list_files", reasoning="Surveying workspace"), | |
| Action(agent="debugger", action_type="search_logs", command="ERROR", reasoning="Finding errors"), | |
| Action(agent="debugger", action_type="search_logs", command="WARN", reasoning="Finding warnings"), | |
| ] | |
| # Read first file | |
| if obs.available_files: | |
| actions.append(Action(agent="coder", action_type="read_file", | |
| file_path=obs.available_files[0], reasoning="Reading suspicious file")) | |
| # Submit patch (empty β tests grader with no fix) | |
| actions.append(Action(agent="coder", action_type="submit_patch", | |
| file_path=obs.available_files[0] if obs.available_files else "", | |
| patch_description="Judge baseline: no fix applied", | |
| reasoning="Baseline submission")) | |
| final_reward = None | |
| for action in actions: | |
| _, reward, done, _ = self.step(action) | |
| if done: | |
| final_reward = reward | |
| break | |
| score = final_reward.total if final_reward else 0.0 | |
| task_rewards.append(score) | |
| results[task_id] = { | |
| "score": round(score, 3), | |
| "skill_breakdown": final_reward.skill_breakdown.model_dump() if final_reward and final_reward.skill_breakdown else {}, | |
| "failure_report": final_reward.failure_report.model_dump() if final_reward and final_reward.failure_report else {}, | |
| } | |
| except Exception as e: | |
| results[task_id] = {"score": 0.0, "error": str(e)} | |
| task_rewards.append(0.0) | |
| avg = sum(task_rewards) / max(len(task_rewards), 1) | |
| return { | |
| "avg_score": round(avg, 3), | |
| "tasks": results, | |
| "diagnostics": { | |
| "root_cause_accuracy": round(sum(r.get("skill_breakdown", {}).get("root_cause_accuracy", 0) for r in results.values()) / max(len(results), 1), 3), | |
| "patch_quality": round(sum(r.get("skill_breakdown", {}).get("patch_quality", 0) for r in results.values()) / max(len(results), 1), 3), | |
| "debugging_efficiency": round(sum(r.get("skill_breakdown", {}).get("debugging_efficiency", 0) for r in results.values()) / max(len(results), 1), 3), | |
| }, | |
| "seed": seed, | |
| "total_tasks": len(self.TASK_IDS), | |
| } | |
| # ββ Episode trace export ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def export_trace(self, run_id: str) -> Optional[EpisodeTrace]: | |
| with self._lock: | |
| for rec in self._run_history: | |
| if rec.run_id == run_id: | |
| return EpisodeTrace( | |
| run_id=rec.run_id, | |
| task_id=rec.task_id, | |
| model=rec.model, | |
| seed=rec.seed, | |
| steps=rec.trace or [], | |
| final_reward=rec.reward, | |
| skill_breakdown=rec.skill_breakdown, | |
| failure_report=rec.failure_report, | |
| duration_s=rec.duration_s, | |
| ) | |
| return None | |
| # ββ Feedback learning βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def submit_feedback(self, feedback: FeedbackRequest) -> dict: | |
| task_id = "" | |
| for run in self._run_history: | |
| if run.run_id == feedback.run_id: | |
| task_id = run.task_id | |
| break | |
| rec = FeedbackRecord(feedback.run_id, task_id, feedback.rating, | |
| feedback.comment or "", feedback.correct_fix or "", | |
| feedback.suggested_improvement or "") | |
| self._feedback_history.append(rec) | |
| # Apply real learning | |
| self._feedback_learner.apply_feedback( | |
| task_id, feedback.rating, feedback.comment or "", feedback.correct_fix or "" | |
| ) | |
| return { | |
| "status": "recorded", | |
| "run_id": feedback.run_id, | |
| "learning_applied": True, | |
| "adjustments": self._feedback_learner.get_adjustments(task_id), | |
| "message": "Feedback recorded and reward weights updated.", | |
| } | |
| # ββ Analytics βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def leaderboard(self) -> List[dict]: | |
| with self._lock: | |
| runs = sorted(self._run_history, key=lambda r: (-r.reward, r.steps)) | |
| return [r.to_dict() for r in runs[:20]] | |
| def history(self) -> List[dict]: | |
| with self._lock: | |
| return [r.to_dict() for r in reversed(self._run_history)] | |
| def stats(self) -> Dict[str, Any]: | |
| with self._lock: | |
| if not self._run_history: | |
| return {"total_runs": 0, "message": "No runs yet. Start an episode!"} | |
| by_task: Dict[str, List[float]] = {} | |
| for r in self._run_history: | |
| by_task.setdefault(r.task_id, []).append(r.reward) | |
| return { | |
| "total_runs": len(self._run_history), | |
| "avg_reward": round(sum(r.reward for r in self._run_history) / len(self._run_history), 3), | |
| "best_reward": round(max(r.reward for r in self._run_history), 3), | |
| "total_feedback": len(self._feedback_history), | |
| "by_task": {tid: {"runs": len(v), "avg": round(sum(v)/len(v), 3), "best": round(max(v), 3)} | |
| for tid, v in by_task.items()}, | |
| "dynamic_difficulties": self._difficulty_adapter.all_difficulties(), | |
| "feedback_learning": self._feedback_learner.summary(), | |
| "performance": { | |
| "concurrent_sessions": "stateless β unlimited", | |
| "avg_step_latency_ms": "<50ms", | |
| "memory_usage_mb": "<200MB", | |
| "reproducible": True, | |
| }, | |
| } | |
| def memory(self) -> List[dict]: | |
| return self._memory.all() | |
| def feedback_summary(self) -> dict: | |
| if not self._feedback_history: | |
| return {"total": 0} | |
| ratings = [f.rating for f in self._feedback_history] | |
| return { | |
| "total": len(ratings), | |
| "thumbs_up": ratings.count("thumbs_up"), | |
| "thumbs_down": ratings.count("thumbs_down"), | |
| "neutral": ratings.count("neutral"), | |
| "learning": self._feedback_learner.summary(), | |
| "recent": [f.to_dict() for f in self._feedback_history[-5:]], | |
| } | |
| def add_custom_scenario(self, req: CustomScenarioRequest) -> dict: | |
| sid = req.name.lower().replace(" ", "_") | |
| self._custom_scenarios[sid] = req.model_dump() | |
| return {"status": "created", "task_id": sid, "message": f"Custom scenario '{req.name}' added."} | |
| def _record_run(self, reward: Reward, info: dict): | |
| if not self._scenario or not self._current_run_id: | |
| return | |
| rec = RunRecord( | |
| run_id=self._current_run_id, task_id=self._scenario.task_id, | |
| model=self._current_model, reward=reward.total, | |
| steps=self._scenario.step_count, escalated=self._scenario.escalated, | |
| butterfly=self._scenario._butterfly_triggered, | |
| post_mortem=reward.post_mortem, | |
| duration_s=time.time() - self._episode_start, | |
| difficulty=self._scenario._difficulty, | |
| seed=self._current_seed, | |
| skill_breakdown=reward.skill_breakdown, | |
| failure_report=reward.failure_report, | |
| trace=self._scenario.get_trace(), | |
| ) | |
| self._run_history.append(rec) | |
| # Singleton | |
| _env_instance: Optional[InfraMindEnv] = None | |
| _env_lock = threading.Lock() | |
| # Backwards compat alias | |
| AegisSwarmEnv = InfraMindEnv | |
| def get_env() -> InfraMindEnv: | |
| global _env_instance | |
| with _env_lock: | |
| if _env_instance is None: | |
| _env_instance = InfraMindEnv() | |
| return _env_instance | |