Spaces:
Sleeping
Sleeping
Download src/heuristic_agent.py from vivekharry/3sacrowd: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/spaces/vivekharry/3sacrowd/resolve/main/src/heuristic_agent.py
- Command line
-
hf download hf://spaces/vivekharry/3sacrowd/src/heuristic_agent.py
-
curl -L -o heuristic_agent.py https://huggingface.co/spaces/vivekharry/3sacrowd/resolve/main/src/heuristic_agent.py
11.5 kB
| """ | |
| Heuristic Expert Agent for ChaosLab. | |
| A hand-coded, rule-based SRE "expert" that reads the ASCII-encoded | |
| observation, detects keywords (DEAD, error, disk, permission, etc.), | |
| and selects the most appropriate action from the discrete ACTION_CATALOG. | |
| Implements a `.predict(obs)` interface identical to stable-baselines3 | |
| models, so it plugs into the same model registry and server code. | |
| No training required β pure CPU logic. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from typing import Tuple, Optional, List | |
| from .rl_env import ACTION_CATALOG, NUM_ACTIONS | |
| # ====================================================================== | |
| # SRE DECISION RULES | |
| # ====================================================================== | |
| # Each rule is (keywords_to_detect, prioritized_action_sequence). | |
| # The agent walks through rules top-to-bottom. The first rule whose | |
| # keywords ALL appear in the observation text fires, and the agent | |
| # picks the first action from that rule's sequence that it hasn't | |
| # already used in this episode. | |
| _RULES: List[Tuple[List[str], List[str]]] = [ | |
| # ββ Phase 0: Initial Triage (always runs first) ββββββββββββββββ | |
| ( | |
| [], # empty = always matches on first call | |
| ["ps aux", "df -h", "free -m"], | |
| ), | |
| # ββ Dead Postgres β restart cascade ββββββββββββββββββββββββββ | |
| ( | |
| ["postgres", "DEAD"], | |
| [ | |
| "systemctl status postgres", | |
| "systemctl restart postgres", | |
| "systemctl status postgres", | |
| ], | |
| ), | |
| ( | |
| ["postgres", "dead"], | |
| [ | |
| "systemctl restart postgres", | |
| "systemctl status postgres", | |
| ], | |
| ), | |
| # ββ Dead App Service ββββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["app", "DEAD"], | |
| [ | |
| "systemctl status app", | |
| "systemctl restart app", | |
| "systemctl status app", | |
| ], | |
| ), | |
| ( | |
| ["app", "dead"], | |
| [ | |
| "systemctl restart app", | |
| ], | |
| ), | |
| # ββ Nginx issues ββββββββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["nginx", "DEAD"], | |
| [ | |
| "systemctl restart nginx", | |
| "cat /etc/nginx/sites-enabled/default", | |
| ], | |
| ), | |
| ( | |
| ["502", "nginx"], | |
| [ | |
| "cat /var/log/nginx/error.log", | |
| "cat /etc/nginx/sites-enabled/default", | |
| 'echo "server {\\n listen 80 default_server;\\n server_name _;\\n\\n location / {\\n proxy_pass http://127.0.0.1:8080;\\n proxy_set_header Host \\$host;\\n proxy_set_header X-Real-IP \\$remote_addr;\\n }\\n}" > /etc/nginx/sites-enabled/default', | |
| "systemctl restart nginx", | |
| ], | |
| ), | |
| # ββ Disk Space Issues βββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["9[0-9]%", "/var/log"], | |
| [ | |
| "du -sh /var/log/*", | |
| "rm /var/log/nginx/error.log", | |
| "rm /var/log/app.log", | |
| "rm /tmp/old_debug.log", | |
| "df -h", | |
| ], | |
| ), | |
| ( | |
| ["disk", "full"], | |
| [ | |
| "df -h", | |
| "du -sh /var/log/*", | |
| "rm /var/log/nginx/error.log", | |
| "rm /tmp/old_debug.log", | |
| "df -h", | |
| ], | |
| ), | |
| # ββ Permission / chmod ββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["Permission", "cleanup"], | |
| [ | |
| "ls -la /home/user/scripts/cleanup.sh", | |
| "chmod 0755 /home/user/scripts/cleanup.sh", | |
| "ls -la /home/user/scripts/cleanup.sh", | |
| ], | |
| ), | |
| ( | |
| ["not executable", "cleanup"], | |
| [ | |
| "chmod 0755 /home/user/scripts/cleanup.sh", | |
| ], | |
| ), | |
| ( | |
| ["backup", "executable"], | |
| [ | |
| "touch /usr/local/bin/backup.sh", | |
| 'echo "#!/bin/bash" > /usr/local/bin/backup.sh', | |
| "chmod 0755 /usr/local/bin/backup.sh", | |
| ], | |
| ), | |
| # ββ Log Analysis ββββββββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["500", "app.log"], | |
| [ | |
| 'grep "500" /var/log/app.log', | |
| ], | |
| ), | |
| ( | |
| ["error", "log"], | |
| [ | |
| "cat /var/log/app.log", | |
| 'grep "error" /var/log/app.log', | |
| 'grep "500" /var/log/app.log', | |
| ], | |
| ), | |
| # ββ Security / SSH brute force ββββββββββββββββββββββββββββββββ | |
| ( | |
| ["Failed password", "ssh"], | |
| [ | |
| "cat /var/log/auth.log", | |
| 'grep "Failed" /var/log/auth.log', | |
| 'grep "10.99.99.5" /var/log/auth.log', | |
| 'grep "Accepted" /var/log/auth.log', | |
| ], | |
| ), | |
| ( | |
| ["brute", "attack"], | |
| [ | |
| "cat /var/log/auth.log", | |
| 'grep "Failed password" /var/log/auth.log', | |
| 'grep "10.99.99.5" /var/log/auth.log', | |
| 'grep -v "Failed" /var/log/auth.log', | |
| ], | |
| ), | |
| # ββ Memory Issues βββββββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["memory", "low"], | |
| [ | |
| "free -m", | |
| "top", | |
| "ps aux", | |
| "systemctl restart app", | |
| ], | |
| ), | |
| # ββ Cron Issues βββββββββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["cron", "fail"], | |
| [ | |
| "cat /var/log/cron.log", | |
| "crontab -l", | |
| "touch /usr/local/bin/backup.sh", | |
| 'echo "#!/bin/bash" > /usr/local/bin/backup.sh', | |
| "chmod 0755 /usr/local/bin/backup.sh", | |
| ], | |
| ), | |
| # ββ Network Issues ββββββββββββββββββββββββββββββββββββββββββββ | |
| ( | |
| ["unreachable", "connection"], | |
| [ | |
| "netstat -tlnp", | |
| "curl localhost:8080", | |
| "systemctl restart app", | |
| "curl localhost:8080", | |
| ], | |
| ), | |
| # ββ Generic Fallback: more diagnostics ββββββββββββββββββββββββ | |
| ( | |
| ["error"], | |
| [ | |
| "cat /var/log/app.log", | |
| "cat /var/log/nginx/error.log", | |
| "cat /var/log/auth.log", | |
| "ps aux", | |
| ], | |
| ), | |
| ] | |
| def _decode_obs(obs: np.ndarray) -> str: | |
| """Decode a Box observation back into a text string.""" | |
| chars = [] | |
| for code in obs: | |
| c = int(code) | |
| if c == 0: | |
| break | |
| if 0 < c < 128: | |
| chars.append(chr(c)) | |
| return "".join(chars) | |
| def _action_index(command: str) -> Optional[int]: | |
| """Find the index of a command in ACTION_CATALOG, or None.""" | |
| try: | |
| return ACTION_CATALOG.index(command) | |
| except ValueError: | |
| return None | |
| class HeuristicAgent: | |
| """ | |
| Rule-based SRE expert. | |
| Matches keyword patterns against the decoded observation text and | |
| walks through a prioritized action list per matched rule. | |
| Implements `.predict(obs, deterministic=True)` matching the SB3 | |
| interface so it can be used as a drop-in replacement. | |
| """ | |
| def __init__(self): | |
| self._used_actions: set = set() | |
| self._triage_done: bool = False | |
| self._step: int = 0 | |
| def reset(self): | |
| self._used_actions = set() | |
| self._triage_done = False | |
| self._step = 0 | |
| def predict( | |
| self, | |
| obs: np.ndarray, | |
| deterministic: bool = True, | |
| ) -> Tuple[np.ndarray, None]: | |
| """ | |
| Pick an action given the current observation. | |
| Returns (action_array, None) matching the SB3 `.predict()` API. | |
| """ | |
| self._step += 1 | |
| text = _decode_obs(obs) | |
| # Phase 0: initial triage (first 3 steps) | |
| if not self._triage_done: | |
| triage_cmds = ["ps aux", "df -h", "free -m"] | |
| for cmd in triage_cmds: | |
| idx = _action_index(cmd) | |
| if idx is not None and idx not in self._used_actions: | |
| self._used_actions.add(idx) | |
| if len(self._used_actions) >= 3: | |
| self._triage_done = True | |
| return np.array(idx), None | |
| self._triage_done = True | |
| # Walk through rules, match keywords against observation text | |
| for keywords, action_sequence in _RULES: | |
| if not keywords: | |
| continue # skip empty-rule (triage already handled) | |
| if all(kw.lower() in text.lower() for kw in keywords): | |
| # Found a matching rule β pick first unused action | |
| for cmd in action_sequence: | |
| idx = _action_index(cmd) | |
| if idx is not None and idx not in self._used_actions: | |
| self._used_actions.add(idx) | |
| return np.array(idx), None | |
| # Fallback: try generic helpful commands we haven't run yet | |
| fallback_cmds = [ | |
| "ps aux", "cat /var/log/app.log", "df -h", | |
| "cat /var/log/auth.log", "systemctl status postgres", | |
| "systemctl restart postgres", "systemctl restart app", | |
| "systemctl restart nginx", "netstat -tlnp", | |
| "cat /var/log/nginx/error.log", "free -m", | |
| "du -sh /var/log/*", "ls -la /home/user/scripts/", | |
| "crontab -l", "cat /var/log/cron.log", | |
| ] | |
| for cmd in fallback_cmds: | |
| idx = _action_index(cmd) | |
| if idx is not None and idx not in self._used_actions: | |
| self._used_actions.add(idx) | |
| return np.array(idx), None | |
| # Absolute fallback: any unused action | |
| for i in range(NUM_ACTIONS): | |
| if i not in self._used_actions: | |
| self._used_actions.add(i) | |
| return np.array(i), None | |
| # All actions exhausted (shouldn't happen with 88 actions) | |
| return np.array(0), None | |
| def get_reasoning(obs: np.ndarray) -> str: | |
| """Return a human-readable explanation of what the agent sees.""" | |
| text = _decode_obs(obs) | |
| detections = [] | |
| checks = [ | |
| ("DEAD", "β Dead service detected"), | |
| ("dead", "β Dead service detected"), | |
| ("error", "π Error entries in logs"), | |
| ("500", "π΄ HTTP 500 errors found"), | |
| ("502", "π΄ HTTP 502 Bad Gateway"), | |
| ("Failed password", "π SSH brute-force activity"), | |
| ("9[0-9]%", "πΎ Disk usage critical (>90%)"), | |
| ("Permission", "π Permission issue detected"), | |
| ("cron", "β° Cron job issue"), | |
| ("memory", "π§ Memory pressure"), | |
| ] | |
| for keyword, label in checks: | |
| if keyword.lower() in text.lower(): | |
| detections.append(label) | |
| if not detections: | |
| return "π Scanning environment for anomalies..." | |
| return " | ".join(detections) | |