yashash045 commited on
Commit
0bb595c
·
verified ·
1 Parent(s): 16487b5

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -114,15 +114,17 @@ Failure space: 4 services x 3 failure types x 2 severities = 24 distinct failure
114
 
115
  ## Reward Design
116
 
117
- Dense per-step reward that creates a learnable gradient for RL training. Investigation rewards are **information-gain aware** — viewing logs on a degraded service gives +0.04 (high info-gain) while a healthy service gives +0.01 (low info-gain). Health improvements give proportional reward via system health delta (+0.005 per 1% improvement). Breaking healthy services is heavily penalized (-0.30). Repeated identical actions are penalized (-0.02). All grading is outcome-based — no procedure-based criteria. Rewards are **task-adaptive** — harder tasks with time pressure get steeper gradients (1.0x–1.5x urgency scaling), creating a curriculum-aware reward landscape. Rewards are bounded [-0.35, +0.20] per step to prevent training instability.
118
 
119
  | Signal | Reward | Condition |
120
  |--------|--------|-----------|
121
  | Service deployed to production | +0.15 | Service reaches prod successfully |
122
  | Service verified in staging | +0.05 | Staging health check passes |
123
- | Investigation (degraded svc) | +0.04 | First-time view on unhealthy service |
124
- | Investigation (healthy svc) | +0.01 | First-time view on healthy service |
125
- | Investigation (global) | +0.02 | First-time view_pipeline |
 
 
126
  | Health improvement | +0.005/1% | System health delta |
127
  | Broke healthy service | -0.30 | Service went from healthy to degraded/down |
128
  | Repeated investigation | -0.01 | Same view action on same target |
 
114
 
115
  ## Reward Design
116
 
117
+ Dense per-step reward that creates a learnable gradient for RL training. Investigation rewards use **diminishing-returns exploration** — first investigation of an unhealthy service gives +0.04, with decay as more services are investigated. Health improvements give proportional reward via system health delta (+0.005 per 1% improvement). **Sub-goal milestones** reward intermediate progress: config fixes (+0.08), migrations (+0.06), and alert resolution (+0.03). Breaking healthy services is heavily penalized (-0.30). All grading is outcome-based — no procedure-based criteria. Rewards are **task-adaptive** — harder tasks with time pressure get steeper gradients (1.0x–1.5x urgency scaling), creating a curriculum-aware reward landscape. Rewards are bounded [-0.35, +0.20] per step to prevent training instability.
118
 
119
  | Signal | Reward | Condition |
120
  |--------|--------|-----------|
121
  | Service deployed to production | +0.15 | Service reaches prod successfully |
122
  | Service verified in staging | +0.05 | Staging health check passes |
123
+ | Config error fixed | +0.08 | Service health improved after config change |
124
+ | Migration completed | +0.06 | Pending migration count decreased |
125
+ | Alert resolved | +0.03 | Alert count decreased |
126
+ | Investigation (degraded svc) | +0.04× | First-time view on unhealthy service (with decay) |
127
+ | Investigation (healthy svc) | +0.01× | First-time view on healthy service (with decay) |
128
  | Health improvement | +0.005/1% | System health delta |
129
  | Broke healthy service | -0.30 | Service went from healthy to degraded/down |
130
  | Repeated investigation | -0.01 | Same view action on same target |
inference.py CHANGED
@@ -4,6 +4,7 @@ import asyncio
4
  import json
5
  import os
6
  import textwrap
 
7
 
8
  from openai import OpenAI
9
 
@@ -33,7 +34,7 @@ def log_start(task: str, env: str, model: str) -> None:
33
  print(f"[START] task={task} env={env} model={model}", flush=True)
34
 
35
 
36
- def log_step(step: int, action: str, reward: float, done: bool, error: str | None) -> None:
37
  error_val = error if error else "null"
38
  done_val = str(done).lower()
39
  print(
@@ -43,7 +44,7 @@ def log_step(step: int, action: str, reward: float, done: bool, error: str | Non
43
  )
44
 
45
 
46
- def log_end(success: bool, steps: int, score: float, rewards: list[float]) -> None:
47
  rewards_str = ",".join(f"{r:.2f}" for r in rewards)
48
  print(
49
  f"[END] success={str(success).lower()} steps={steps} "
 
4
  import json
5
  import os
6
  import textwrap
7
+ from typing import List, Optional
8
 
9
  from openai import OpenAI
10
 
 
34
  print(f"[START] task={task} env={env} model={model}", flush=True)
35
 
36
 
37
+ def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
38
  error_val = error if error else "null"
39
  done_val = str(done).lower()
40
  print(
 
44
  )
45
 
46
 
47
+ def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
48
  rewards_str = ",".join(f"{r:.2f}" for r in rewards)
49
  print(
50
  f"[END] success={str(success).lower()} steps={steps} "
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
- openenv-core>=0.2.1
2
  pydantic>=2.0
3
  fastapi>=0.104.0
4
  uvicorn>=0.24.0
 
 
1
+ openenv-core[core]>=0.2.2
2
  pydantic>=2.0
3
  fastapi>=0.104.0
4
  uvicorn>=0.24.0
5
+ openai>=1.0.0
server/__init__.py CHANGED
@@ -5,7 +5,3 @@
5
  # LICENSE file in the root directory of this source tree.
6
 
7
  """DevOps Pipeline environment server components."""
8
-
9
- from server.pipeline_environment import PipelineEnvironment
10
-
11
- __all__ = ["PipelineEnvironment"]
 
5
  # LICENSE file in the root directory of this source tree.
6
 
7
  """DevOps Pipeline environment server components."""
 
 
 
 
server/app.py CHANGED
@@ -106,6 +106,9 @@ async def run_grader(task_name: str = "clean_deploy"):
106
  return {"task": task_name, "score": 0.0, "error": "No active session. Call /reset first."}
107
  if not env.get_episode_history():
108
  return {"task": env.get_task_name(), "score": 0.0, "error": "No steps taken. Call /step first."}
 
 
 
109
  score = _grade_task(
110
  env.get_task_name(),
111
  env.get_episode_history(),
 
106
  return {"task": task_name, "score": 0.0, "error": "No active session. Call /reset first."}
107
  if not env.get_episode_history():
108
  return {"task": env.get_task_name(), "score": 0.0, "error": "No steps taken. Call /step first."}
109
+ active_task = env.get_task_name()
110
+ if task_name and task_name != "clean_deploy" and task_name != active_task:
111
+ return {"task": task_name, "score": 0.0, "error": f"Task mismatch: active session is '{active_task}', not '{task_name}'."}
112
  score = _grade_task(
113
  env.get_task_name(),
114
  env.get_episode_history(),
server/graders.py CHANGED
@@ -133,12 +133,18 @@ def grade_judgment_call(episode_history, engine):
133
  elif api_gw.latency_ms < 500:
134
  score += 0.10 # Partial improvement
135
 
136
- # web-frontend collateral damage
137
  if web_fe:
138
- if web_fe.health.value == "healthy" and web_fe.error_rate < 2.0:
139
  score += 0.25
 
 
 
 
140
  elif web_fe.error_rate < 10.0:
141
  score += 0.10
 
 
142
 
143
  # Time to resolution — when api-gateway actually became healthy (outcome-based)
144
  resolution_step = len(episode_history)
@@ -255,8 +261,12 @@ def grade_capacity_crisis(episode_history, engine):
255
  # Root cause: database protected (0.30)
256
  if db:
257
  max_conn = int(db.config.get("max_connections", "50"))
258
- if max_conn >= 100 and db.cpu_percent < 85:
259
- score += 0.30
 
 
 
 
260
  elif max_conn >= 75 and db.cpu_percent < 85:
261
  score += 0.20
262
  elif max_conn >= 75:
@@ -320,7 +330,11 @@ def grade_random_incident(episode_history, engine):
320
  if not scenario.check_config_error(failing_name, failing_svc.config):
321
  score += 0.20
322
  elif getattr(scenario, '_failure_type', '') == 'degraded_performance':
323
- score += 0.20 # No config error to fix for degraded_performance
 
 
 
 
324
 
325
  # No collateral damage (0.10) — outcome-based, not procedure-based
326
  any_broke = any(entry.get("broke_healthy", False) for entry in episode_history)
 
133
  elif api_gw.latency_ms < 500:
134
  score += 0.10 # Partial improvement
135
 
136
+ # web-frontend collateral damage (smooth gradient)
137
  if web_fe:
138
+ if web_fe.health.value == "healthy" and web_fe.error_rate < 0.5:
139
  score += 0.25
140
+ elif web_fe.health.value == "healthy" and web_fe.error_rate < 2.0:
141
+ score += 0.20
142
+ elif web_fe.health.value == "healthy" and web_fe.error_rate < 5.0:
143
+ score += 0.15
144
  elif web_fe.error_rate < 10.0:
145
  score += 0.10
146
+ elif web_fe.error_rate < 20.0:
147
+ score += 0.05
148
 
149
  # Time to resolution — when api-gateway actually became healthy (outcome-based)
150
  resolution_step = len(episode_history)
 
261
  # Root cause: database protected (0.30)
262
  if db:
263
  max_conn = int(db.config.get("max_connections", "50"))
264
+ shared_buf = db.config.get("shared_buffers", "4GB")
265
+ shared_gb = int(shared_buf.replace("GB", "")) if "GB" in str(shared_buf) else 4
266
+ if max_conn >= 100 and db.cpu_percent < 85 and shared_gb >= 6:
267
+ score += 0.30 # Both configs optimized
268
+ elif max_conn >= 100 and db.cpu_percent < 85:
269
+ score += 0.25 # Connections fixed, buffers not
270
  elif max_conn >= 75 and db.cpu_percent < 85:
271
  score += 0.20
272
  elif max_conn >= 75:
 
330
  if not scenario.check_config_error(failing_name, failing_svc.config):
331
  score += 0.20
332
  elif getattr(scenario, '_failure_type', '') == 'degraded_performance':
333
+ # No config error — check if agent actually improved the service
334
+ if failing_svc.error_rate < 5.0 and failing_svc.health.value == "healthy":
335
+ score += 0.20 # Fully recovered
336
+ elif failing_svc.error_rate < 10.0:
337
+ score += 0.10 # Partially improved
338
 
339
  # No collateral damage (0.10) — outcome-based, not procedure-based
340
  any_broke = any(entry.get("broke_healthy", False) for entry in episode_history)
server/pipeline_engine.py CHANGED
@@ -620,11 +620,14 @@ class PipelineEngine:
620
  "latency_ms": s.latency_ms,
621
  "prod_deployed": s.prod_deployed,
622
  "staging_verified": s.staging_verified,
 
623
  }
624
  for name, s in self.services.items()
625
  },
626
  "system_health": self.get_system_health(),
627
  "pipeline_stage": self.pipeline_stage.value,
 
 
628
  }
629
 
630
  def get_system_health(self):
 
620
  "latency_ms": s.latency_ms,
621
  "prod_deployed": s.prod_deployed,
622
  "staging_verified": s.staging_verified,
623
+ "config": dict(s.config),
624
  }
625
  for name, s in self.services.items()
626
  },
627
  "system_health": self.get_system_health(),
628
  "pipeline_stage": self.pipeline_stage.value,
629
+ "migrations_pending": list(self.migrations_pending),
630
+ "alerts": list(self.alerts),
631
  }
632
 
633
  def get_system_health(self):
server/pipeline_environment.py CHANGED
@@ -31,7 +31,7 @@ TASK_SEEDS = {
31
  "judgment_call": 3003,
32
  "cascading_failure": 4004,
33
  "capacity_crisis": 5005,
34
- "random_incident": 6006,
35
  }
36
 
37
  TASK_MAX_STEPS = {
@@ -232,6 +232,19 @@ class PipelineEnvironment(Environment):
232
  )
233
  elif svc_state.cpu_percent > 80:
234
  alerts.append(f"CAUTION: {name} CPU high ({svc_state.cpu_percent:.0f}%)")
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  summary = "; ".join(alerts) if alerts else "All services nominal."
236
 
237
  return PipelineObservation(
 
31
  "judgment_call": 3003,
32
  "cascading_failure": 4004,
33
  "capacity_crisis": 5005,
34
+ "random_incident": int(os.environ.get("DEVOPS_SEED", "6006")),
35
  }
36
 
37
  TASK_MAX_STEPS = {
 
232
  )
233
  elif svc_state.cpu_percent > 80:
234
  alerts.append(f"CAUTION: {name} CPU high ({svc_state.cpu_percent:.0f}%)")
235
+ # Add dependency chain hints for degraded services
236
+ for name, svc_state in self._engine.services.items():
237
+ if svc_state.health in (ServiceHealth.DEGRADED, ServiceHealth.DOWN):
238
+ upstream_issues = [
239
+ d for d in svc_state.dependencies
240
+ if d in self._engine.services
241
+ and self._engine.services[d].health in (ServiceHealth.DEGRADED, ServiceHealth.DOWN)
242
+ ]
243
+ if upstream_issues:
244
+ alerts.append(
245
+ f"HINT: {name} depends on {', '.join(upstream_issues)} "
246
+ f"(also unhealthy — root cause likely upstream)"
247
+ )
248
  summary = "; ".join(alerts) if alerts else "All services nominal."
249
 
250
  return PipelineObservation(
server/rewards.py CHANGED
@@ -23,58 +23,77 @@ TASK_URGENCY = {
23
  def calculate_reward(prev_snapshot, current_snapshot, action, viewed_actions,
24
  last_action_key=None, task_name="clean_deploy"):
25
  """
26
- Outcome-based reward. No procedure bonuses.
27
- viewed_actions: a set tracking first-time investigation keys (e.g. "view_logs:api-gateway")
28
- last_action_key: string key of previous action for repeat detection
29
- task_name: current task for urgency scaling
30
  Returns a float bounded to [-0.35, +0.20].
31
  """
32
  reward = 0.0
33
 
34
- # 1. System health delta (primary signal)
35
  health_delta = current_snapshot["system_health"] - prev_snapshot["system_health"]
36
- reward += health_delta * 0.005 # +0.005 per 1% health improvement
37
 
38
- # 2. Deployment progress
39
  for name, curr_svc in current_snapshot["services"].items():
40
  prev_svc = prev_snapshot["services"].get(name, {})
41
  if curr_svc["prod_deployed"] and not prev_svc.get("prod_deployed"):
42
- reward += 0.15 # Service successfully reached production
43
  if curr_svc["staging_verified"] and not prev_svc.get("staging_verified"):
44
- reward += 0.05 # Service verified in staging
45
 
46
- # 3. Broke something that was working
47
  for name, curr_svc in current_snapshot["services"].items():
48
  prev_svc = prev_snapshot["services"].get(name, {})
49
  if prev_svc.get("health") == "healthy" and curr_svc["health"] in ("degraded", "down"):
50
- reward -= 0.30 # Catastrophic penalty
51
 
52
- # 4. Investigation bonus (info-gain aware)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  if action.action_type in (ActionType.VIEW_PIPELINE, ActionType.VIEW_LOGS, ActionType.VIEW_CONFIG):
54
  action_key = f"{action.action_type.value}:{action.service_name or 'global'}"
55
  if action_key not in viewed_actions:
56
  viewed_actions.add(action_key)
57
- # Higher reward for investigating unhealthy services (more info gain)
 
58
  if action.service_name:
59
  svc_data = current_snapshot["services"].get(action.service_name, {})
60
  if svc_data.get("health") in ("degraded", "down"):
61
- reward += 0.04 # High info-gain
62
  else:
63
- reward += 0.01 # Low info-gain (healthy service)
64
  else:
65
- reward += 0.02 # view_pipeline (global)
66
  else:
67
- reward -= 0.01 # Repeated investigation of same target
68
-
69
- # Apply urgency scaling and bound
70
- reward *= TASK_URGENCY.get(task_name, 1.0)
71
- return max(min(reward, 0.20), -0.35)
72
 
73
- # 5. Repeated exact action penalty
74
- current_action_key = f"{action.action_type.value}:{action.service_name or ''}"
75
- if last_action_key and current_action_key == last_action_key:
76
- reward -= 0.02 # Exact repeat penalty
 
77
 
78
- # 6. Apply task urgency scaling and bound
79
  reward *= TASK_URGENCY.get(task_name, 1.0)
80
  return max(min(reward, 0.20), -0.35)
 
23
  def calculate_reward(prev_snapshot, current_snapshot, action, viewed_actions,
24
  last_action_key=None, task_name="clean_deploy"):
25
  """
26
+ Outcome-based reward. No procedure bonuses. No early returns.
27
+ ALL actions go through the full reward pipeline.
 
 
28
  Returns a float bounded to [-0.35, +0.20].
29
  """
30
  reward = 0.0
31
 
32
+ # 1. System health delta (ALL actions)
33
  health_delta = current_snapshot["system_health"] - prev_snapshot["system_health"]
34
+ reward += health_delta * 0.005
35
 
36
+ # 2. Deployment progress (ALL actions — recovery ticks can change state)
37
  for name, curr_svc in current_snapshot["services"].items():
38
  prev_svc = prev_snapshot["services"].get(name, {})
39
  if curr_svc["prod_deployed"] and not prev_svc.get("prod_deployed"):
40
+ reward += 0.15
41
  if curr_svc["staging_verified"] and not prev_svc.get("staging_verified"):
42
+ reward += 0.05
43
 
44
+ # 3. Broke healthy service penalty (ALL actions)
45
  for name, curr_svc in current_snapshot["services"].items():
46
  prev_svc = prev_snapshot["services"].get(name, {})
47
  if prev_svc.get("health") == "healthy" and curr_svc["health"] in ("degraded", "down"):
48
+ reward -= 0.30
49
 
50
+ # 4. Sub-goal completion rewards (intermediate milestones for RL)
51
+ # Config change detected — check if it fixed an error
52
+ for name, curr_svc in current_snapshot["services"].items():
53
+ prev_svc = prev_snapshot["services"].get(name, {})
54
+ prev_config = prev_svc.get("config", {})
55
+ curr_config = curr_svc.get("config", {})
56
+ if prev_config and curr_config and prev_config != curr_config:
57
+ # Config changed — reward if health improved on this service
58
+ if prev_svc.get("health") in ("degraded", "down") and curr_svc["health"] == "healthy":
59
+ reward += 0.08
60
+
61
+ # Migration completed
62
+ prev_pending = len(prev_snapshot.get("migrations_pending", []))
63
+ curr_pending = len(current_snapshot.get("migrations_pending", []))
64
+ if curr_pending < prev_pending:
65
+ reward += 0.06
66
+
67
+ # Alert resolved
68
+ prev_alerts = len(prev_snapshot.get("alerts", []))
69
+ curr_alerts = len(current_snapshot.get("alerts", []))
70
+ if curr_alerts < prev_alerts:
71
+ reward += 0.03
72
+
73
+ # 5. Investigation bonus with diminishing returns (view_* actions only)
74
  if action.action_type in (ActionType.VIEW_PIPELINE, ActionType.VIEW_LOGS, ActionType.VIEW_CONFIG):
75
  action_key = f"{action.action_type.value}:{action.service_name or 'global'}"
76
  if action_key not in viewed_actions:
77
  viewed_actions.add(action_key)
78
+ investigation_count = len(viewed_actions)
79
+ decay_factor = 1.0 / (1 + (investigation_count - 1) * 0.3)
80
  if action.service_name:
81
  svc_data = current_snapshot["services"].get(action.service_name, {})
82
  if svc_data.get("health") in ("degraded", "down"):
83
+ reward += 0.04 * decay_factor
84
  else:
85
+ reward += 0.01 * decay_factor
86
  else:
87
+ reward += 0.02 * decay_factor
88
  else:
89
+ reward -= 0.01
 
 
 
 
90
 
91
+ # 6. Repeated exact action penalty (non-view actions)
92
+ if action.action_type not in (ActionType.VIEW_PIPELINE, ActionType.VIEW_LOGS, ActionType.VIEW_CONFIG):
93
+ current_action_key = f"{action.action_type.value}:{action.service_name or ''}"
94
+ if last_action_key and current_action_key == last_action_key:
95
+ reward -= 0.02
96
 
97
+ # 7. Apply task urgency scaling and bound
98
  reward *= TASK_URGENCY.get(task_name, 1.0)
99
  return max(min(reward, 0.20), -0.35)