Skip to content

Commit 2fe3fbb

Browse files
committed
Keep task scores inside the open interval
1 parent 3e39afd commit 2fe3fbb

7 files changed

Lines changed: 68 additions & 43 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ The report includes:
183183
- optional Markdown export
184184
- optional scripted-vs-random comparison
185185

186-
Normalized score is computed against each task's `max_reward` and capped to the `0.0-1.0` range for clean benchmark reporting.
187-
The surfaced environment `reward` and benchmark `score` stay in the required `0.0-1.0` range, while raw shaped reward remains available through observation metadata and benchmark report fields for debugging.
186+
Normalized score is computed against each task's `max_reward` and then clamped strictly inside `(0, 1)` for validator-safe reporting.
187+
The surfaced environment `reward` and benchmark `score` therefore stay strictly between `0.0` and `1.0`, while raw shaped reward remains available through observation metadata and benchmark report fields for debugging.
188188

189189
## Submission Compliance Notes
190190

benchmark.csv

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
task_id,title,difficulty,terminal_outcome,steps,total_reward,max_reward,reward_ratio,normalized_score
2-
rollback_alpha,Rollback a broken canary release,easy,success,4,24.4,20.0,1.22,1.0
3-
hotfix_ci_beta,Fix-forward a blocked release,easy,success,8,25.6,25.0,1.024,1.0
4-
public_payments_incident,Recover a customer-facing payments incident,medium,success,7,34.1,28.0,1.218,1.0
5-
auth_fix_forward,Fix-forward a security-sensitive auth release,medium,success,15,48.5,35.0,1.386,1.0
6-
schema_migration_fix,Repair a release blocked by schema migration failure,medium,success,9,28.3,25.0,1.132,1.0
7-
customer_timeline_hard,Run a full incident lifecycle with postmortem hygiene,hard,success,10,42.2,38.0,1.111,1.0
8-
checkout_fix_forward_major,Contain and fix-forward a checkout incident,hard,success,16,51.2,40.0,1.28,1.0
9-
orders_pause_and_rollback,Pause rollout and rollback an orders release,medium,success,6,29.8,28.0,1.064,1.0
10-
database_rollback_simple,Rollback a database-impacting release,easy,success,4,24.4,20.0,1.22,1.0
11-
api_gateway_fix_forward,Fix-forward a broken API gateway release,medium,success,9,28.3,28.0,1.011,1.0
12-
notification_service_sev2,Handle a sev2 notification service incident,medium,success,7,34.1,28.0,1.218,1.0
13-
ml_pipeline_hard,Recover a broken ML inference pipeline with full incident lifecycle,hard,success,10,42.2,40.0,1.055,1.0
1+
task_id,title,difficulty,terminal_outcome,steps,score,total_reward,max_reward,reward_ratio,normalized_score
2+
rollback_alpha,Rollback a broken canary release,easy,success,4,0.999,24.4,20.0,1.22,0.999
3+
hotfix_ci_beta,Fix-forward a blocked release,easy,success,8,0.999,25.6,25.0,1.024,0.999
4+
public_payments_incident,Recover a customer-facing payments incident,medium,success,7,0.999,34.1,28.0,1.218,0.999
5+
auth_fix_forward,Fix-forward a security-sensitive auth release,medium,success,15,0.999,48.5,35.0,1.386,0.999
6+
schema_migration_fix,Repair a release blocked by schema migration failure,medium,success,9,0.999,28.3,25.0,1.132,0.999
7+
customer_timeline_hard,Run a full incident lifecycle with postmortem hygiene,hard,success,10,0.999,42.2,38.0,1.111,0.999
8+
checkout_fix_forward_major,Contain and fix-forward a checkout incident,hard,success,16,0.999,51.2,40.0,1.28,0.999
9+
orders_pause_and_rollback,Pause rollout and rollback an orders release,medium,success,6,0.999,29.8,28.0,1.064,0.999
10+
database_rollback_simple,Rollback a database-impacting release,easy,success,4,0.999,24.4,20.0,1.22,0.999
11+
api_gateway_fix_forward,Fix-forward a broken API gateway release,medium,success,9,0.999,28.3,28.0,1.011,0.999
12+
notification_service_sev2,Handle a sev2 notification service incident,medium,success,7,0.999,34.1,28.0,1.218,0.999
13+
ml_pipeline_hard,Recover a broken ML inference pipeline with full incident lifecycle,hard,success,10,0.999,42.2,40.0,1.055,0.999

benchmark.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,29 @@
77
- Success: `12/12`
88
- Success rate: `100.0%`
99
- Average reward: `34.43`
10-
- Average normalized score: `1.000`
10+
- Average normalized score: `0.999`
1111

1212
## By Difficulty
1313

1414
| Difficulty | Tasks | Success | Success Rate | Avg Reward | Avg Normalized |
1515
|---|---:|---:|---:|---:|---:|
16-
| easy | 3 | 3 | 100.0% | 24.80 | 1.000 |
17-
| hard | 3 | 3 | 100.0% | 45.20 | 1.000 |
18-
| medium | 6 | 6 | 100.0% | 33.85 | 1.000 |
16+
| easy | 3 | 3 | 100.0% | 24.80 | 0.999 |
17+
| hard | 3 | 3 | 100.0% | 45.20 | 0.999 |
18+
| medium | 6 | 6 | 100.0% | 33.85 | 0.999 |
1919

2020
## Per Task
2121

2222
| Task | Difficulty | Outcome | Steps | Reward | Max Reward | Normalized |
2323
|---|---|---|---:|---:|---:|---:|
24-
| rollback_alpha | easy | success | 4 | 24.4 | 20.0 | 1.000 |
25-
| hotfix_ci_beta | easy | success | 8 | 25.6 | 25.0 | 1.000 |
26-
| public_payments_incident | medium | success | 7 | 34.1 | 28.0 | 1.000 |
27-
| auth_fix_forward | medium | success | 15 | 48.5 | 35.0 | 1.000 |
28-
| schema_migration_fix | medium | success | 9 | 28.3 | 25.0 | 1.000 |
29-
| customer_timeline_hard | hard | success | 10 | 42.2 | 38.0 | 1.000 |
30-
| checkout_fix_forward_major | hard | success | 16 | 51.2 | 40.0 | 1.000 |
31-
| orders_pause_and_rollback | medium | success | 6 | 29.8 | 28.0 | 1.000 |
32-
| database_rollback_simple | easy | success | 4 | 24.4 | 20.0 | 1.000 |
33-
| api_gateway_fix_forward | medium | success | 9 | 28.3 | 28.0 | 1.000 |
34-
| notification_service_sev2 | medium | success | 7 | 34.1 | 28.0 | 1.000 |
35-
| ml_pipeline_hard | hard | success | 10 | 42.2 | 40.0 | 1.000 |
24+
| rollback_alpha | easy | success | 4 | 24.4 | 20.0 | 0.999 |
25+
| hotfix_ci_beta | easy | success | 8 | 25.6 | 25.0 | 0.999 |
26+
| public_payments_incident | medium | success | 7 | 34.1 | 28.0 | 0.999 |
27+
| auth_fix_forward | medium | success | 15 | 48.5 | 35.0 | 0.999 |
28+
| schema_migration_fix | medium | success | 9 | 28.3 | 25.0 | 0.999 |
29+
| customer_timeline_hard | hard | success | 10 | 42.2 | 38.0 | 0.999 |
30+
| checkout_fix_forward_major | hard | success | 16 | 51.2 | 40.0 | 0.999 |
31+
| orders_pause_and_rollback | medium | success | 6 | 29.8 | 28.0 | 0.999 |
32+
| database_rollback_simple | easy | success | 4 | 24.4 | 20.0 | 0.999 |
33+
| api_gateway_fix_forward | medium | success | 9 | 28.3 | 28.0 | 0.999 |
34+
| notification_service_sev2 | medium | success | 7 | 34.1 | 28.0 | 0.999 |
35+
| ml_pipeline_hard | hard | success | 10 | 42.2 | 40.0 | 0.999 |

benchmark_comparison.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
| Policy | Success Rate | Avg Reward | Avg Normalized |
77
|---|---:|---:|---:|
8-
| scripted | 100.0% | 34.43 | 1.000 |
8+
| scripted | 100.0% | 34.43 | 0.999 |
99
| random | 25.0% | 15.96 | 0.569 |
1010

1111
## Delta
1212

1313
- Success rate delta: `75.0%`
1414
- Average reward delta: `18.47`
15-
- Average normalized score delta: `0.431`
15+
- Average normalized score delta: `0.430`

benchmark_report.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
try:
1515
from opsgauntlet.models import OpsGauntletAction, ToolCallRequest
16-
from opsgauntlet.server.environment import OpsGauntletEnvironment
16+
from opsgauntlet.server.environment import OpsGauntletEnvironment, clamp_strict_score
1717
from opsgauntlet.server.task_bank import TASK_BANK, get_task_by_id
1818
except ImportError: # pragma: no cover
1919
from models import OpsGauntletAction, ToolCallRequest # type: ignore
20-
from server.environment import OpsGauntletEnvironment # type: ignore
20+
from server.environment import OpsGauntletEnvironment, clamp_strict_score # type: ignore
2121
from server.task_bank import TASK_BANK, get_task_by_id # type: ignore
2222

2323

@@ -43,7 +43,7 @@ def collect_benchmark_results(
4343
"score": round(score, 3),
4444
"raw_total_reward": round(raw_total_reward, 2),
4545
"reward_ratio": round(raw_ratio, 3),
46-
"normalized_score": round(max(0.0, min(score, 1.0)), 3),
46+
"normalized_score": clamp_strict_score(score),
4747
}
4848
)
4949

server/environment.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@
3030
from .task_bank import Task, get_random_task
3131
from .tool_registry import ToolRegistry
3232

33+
STRICT_SCORE_EPSILON = 0.001
34+
35+
36+
def clamp_strict_score(value: float) -> float:
37+
"""Keep surfaced task scores strictly inside the open unit interval."""
38+
39+
return round(max(STRICT_SCORE_EPSILON, min(value, 1.0 - STRICT_SCORE_EPSILON)), 3)
40+
3341

3442
class OpsGauntletEnvironment(
3543
Environment[OpsGauntletAction, OpsGauntletObservation, State]
@@ -228,6 +236,6 @@ def _append_timeline(self, result: ToolResult) -> None:
228236
def _normalized_episode_score(self) -> float:
229237
assert self._task is not None
230238
if self._task.max_reward <= 0:
231-
return 0.0
239+
return clamp_strict_score(0.5)
232240
normalized = self._raw_total_reward / self._task.max_reward
233-
return round(max(0.0, min(normalized, 1.0)), 3)
241+
return clamp_strict_score(normalized)

tests/test_environment.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def test_rollback_scenario_can_complete_successfully():
5959
)
6060

6161
assert result.done is True
62-
assert result.reward is not None and 0.0 <= result.reward <= 1.0
63-
assert result.metadata["episode_score"] == 1.0
62+
assert result.reward is not None and 0.0 < result.reward < 1.0
63+
assert 0.0 < result.metadata["episode_score"] < 1.0
6464
assert result.metadata["raw_total_reward"] > 5
6565
assert "rolled_back_safely" in result.completed_objectives
6666
assert "internal_comms_sent" in result.completed_objectives
@@ -146,7 +146,7 @@ def test_cannot_close_customer_incident_before_recovery():
146146
def test_scripted_baseline_solves_all_tasks():
147147
outcomes = [run_episode(task.task_id, verbose=False) for task in TASK_BANK]
148148
assert all(item["terminal_outcome"] == "success" for item in outcomes)
149-
assert all(0.0 <= item["score"] <= 1.0 for item in outcomes)
149+
assert all(0.0 < item["score"] < 1.0 for item in outcomes)
150150

151151

152152
def test_auto_policy_defaults_to_scripted_without_api_key(monkeypatch):
@@ -202,9 +202,26 @@ def test_benchmark_report_covers_all_tasks_with_normalized_scores():
202202
report = collect_benchmark_results(scope="all", seed=7)
203203
assert report["task_count"] == len(TASK_BANK)
204204
assert report["success_count"] == len(TASK_BANK)
205-
assert 0.0 <= report["average_normalized_score"] <= 1.0
205+
assert 0.0 < report["average_normalized_score"] < 1.0
206206
for item in report["results"]:
207-
assert 0.0 <= item["normalized_score"] <= 1.0
207+
assert 0.0 < item["normalized_score"] < 1.0
208+
209+
210+
def test_failed_terminal_scores_stay_inside_open_interval():
211+
env = OpsGauntletEnvironment()
212+
observation = env.reset(task_id="rollback_alpha")
213+
214+
while not observation.done:
215+
observation = step(
216+
env,
217+
"notify_slack",
218+
{"channel": "#eng", "message": "premature update"},
219+
reasoning="Keep doing the wrong thing until the episode times out.",
220+
)
221+
222+
assert observation.metadata["terminal_outcome"] == "failure"
223+
assert 0.0 < observation.reward < 1.0
224+
assert 0.0 < observation.metadata["episode_score"] < 1.0
208225

209226

210227
def test_root_page_and_robots_are_served():

0 commit comments

Comments
 (0)