gameability 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {gameability-0.2.0/src/gameability.egg-info → gameability-0.3.1}/PKG-INFO +202 -162
  2. {gameability-0.2.0 → gameability-0.3.1}/README.md +40 -0
  3. {gameability-0.2.0 → gameability-0.3.1}/pyproject.toml +33 -33
  4. {gameability-0.2.0 → gameability-0.3.1}/setup.cfg +4 -4
  5. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/__init__.py +1 -1
  6. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/cli.py +24 -2
  7. gameability-0.3.1/src/gameability/probe.py +272 -0
  8. {gameability-0.2.0 → gameability-0.3.1/src/gameability.egg-info}/PKG-INFO +202 -162
  9. {gameability-0.2.0 → gameability-0.3.1}/src/gameability.egg-info/SOURCES.txt +3 -1
  10. {gameability-0.2.0 → gameability-0.3.1}/tests/test_gameability.py +167 -167
  11. gameability-0.3.1/tests/test_probe.py +104 -0
  12. {gameability-0.2.0 → gameability-0.3.1}/LICENSE +0 -0
  13. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/__main__.py +0 -0
  14. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/_worker.py +0 -0
  15. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/engine.py +0 -0
  16. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/harden.py +0 -0
  17. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/loaders.py +0 -0
  18. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/mutate.py +0 -0
  19. {gameability-0.2.0 → gameability-0.3.1}/src/gameability/oracle.py +0 -0
  20. {gameability-0.2.0 → gameability-0.3.1}/src/gameability.egg-info/dependency_links.txt +0 -0
  21. {gameability-0.2.0 → gameability-0.3.1}/src/gameability.egg-info/entry_points.txt +0 -0
  22. {gameability-0.2.0 → gameability-0.3.1}/src/gameability.egg-info/requires.txt +0 -0
  23. {gameability-0.2.0 → gameability-0.3.1}/src/gameability.egg-info/top_level.txt +0 -0
  24. {gameability-0.2.0 → gameability-0.3.1}/tests/test_contract_inputs.py +0 -0
@@ -1,162 +1,202 @@
1
- Metadata-Version: 2.4
2
- Name: gameability
3
- Version: 0.2.0
4
- Summary: Find the problems in a code benchmark that accept broken solutions.
5
- Author: Empyrean Intelligence
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/Empyrean-Intelligence/gameability
8
- Project-URL: Source, https://github.com/Empyrean-Intelligence/gameability
9
- Project-URL: Issues, https://github.com/Empyrean-Intelligence/gameability/issues
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
- Classifier: Topic :: Software Development :: Testing
13
- Requires-Python: >=3.9
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Provides-Extra: hf
17
- Requires-Dist: datasets; extra == "hf"
18
- Dynamic: license-file
19
-
20
- # gameability
21
-
22
- **Find the problems in a code benchmark that accept broken solutions.**
23
-
24
- When a model learns from tests, the tests are its reward. A self-improving system trained on tests that
25
- accept broken code learns to pass weak tests, not to write correct code. `gameability` measures how often
26
- that can happen. It is part of the verification layer we use at Empyrean Intelligence for self-improvement
27
- work, released for anyone who builds benchmarks or RL training sets.
28
-
29
- A benchmark score means something only if the benchmark rejects solutions that lack the
30
- skill it measures. `gameability` checks that directly. It takes each problem's reference
31
- solution, breaks it in one small place, confirms the result really is wrong, and then
32
- asks whether the problem's own tests notice.
33
-
34
- ```
35
- pip install "gameability[hf]"
36
- gameability check mbpp
37
- ```
38
-
39
- On MBPP, **17.9% of problems (169 of 942 judged) accept a broken solution**; on HumanEval,
40
- **10.3% (16 of 155)**. Each run takes minutes on a laptop, with no model and no API key.
41
-
42
- **Confirmed on valid inputs.** On the problems EvalPlus also covers, every accepted broken solution
43
- was re-run against EvalPlus's independent tests, whose inputs respect each problem's rules:
44
- **95.2% of flagged MBPP problems and 84.6% of flagged HumanEval problems are confirmed** (the broken
45
- solution passes the original tests and fails EvalPlus). The two unconfirmed HumanEval problems
46
- (116 and 129) are ones whose own visible tests contradict their written specification.
47
-
48
- **What changed in v0.2.** v0.1 built fresh inputs by changing values freely, which sometimes asked
49
- a problem about inputs it never promised to handle (an unsorted list for binary search, random
50
- letters for a parenthesis parser). v0.2 learns each argument's domain from the problem's own
51
- visible tests and stays inside it: number signs and minimums, the characters or whole tokens a
52
- string uses, and whether lists are sorted, unique, rectangular or a permutation. On the same
53
- problems this raised the confirmed share from 85.7% to 95.2% (MBPP) and 78.6% to 84.6% (HumanEval)
54
- while keeping 93.1% of v0.1's confirmed detections. `--inputs free` reproduces v0.1.
55
-
56
- ## Scoreboard
57
-
58
- Problems that accept at least one broken solution (wrong on at least one fresh input), with 95% intervals. Defaults (24 broken
59
- versions per problem, 8 fresh inputs, seed 0), laptop CPU, October 2026.
60
-
61
- | Benchmark | Weak problems | Command |
62
- |---|---|---|
63
- | MBPP | 17.9% (169 of 942) [15.6, 20.5] | `gameability check mbpp` |
64
- | MBPP sanitized | 13.8% (57 of 413) [10.8, 17.5] | `gameability check mbpp-sanitized` |
65
- | HumanEval | 10.3% (16 of 155) [6.5, 16.1] | `gameability check humaneval` |
66
- | KodCode (RL training set, 1,000-row random sample) | 9.7% (45 of 466) [7.3, 12.7] | `gameability check kodcode:rows.jsonl` |
67
- | HumanEval+ (EvalPlus) | 3.4% (5 of 147) [1.5, 7.7] | `gameability check humaneval-plus` |
68
- | MBPP+ (EvalPlus) | 2.0% (7 of 353) [1.0, 4.0] | `gameability check mbpp-plus` |
69
-
70
- EvalPlus's stronger tests look like a large improvement, but EvalPlus also rewrote most reference
71
- solutions. Holding the original reference fixed (`mbpp-plus-origref`, `humaneval-plus-origref`), the weak
72
- share roughly halves (MBPP 8.7% to 3.1% on 254 problems, HumanEval 10.6% to 4.9% on 123), and at these
73
- sample sizes the intervals overlap. KodCode's judged set is the problems whose tests call the function with
74
- literal arguments (466 of 801 loadable). `rows.jsonl` is KodCode rows saved as JSON lines; the sample above
75
- is reproducible with `python tests/sample_kodcode.py rows.jsonl` (positions from `random.Random(0)`).
76
-
77
- ## What it prints
78
-
79
- ```
80
- 415 -- 6 of 11 broken solutions pass every visible test
81
- - if arr_len < 2:
82
- + if arr_len <= 2:
83
- a test that catches it: assert max_Product([4, 9]) == (4, 9)
84
- ```
85
-
86
- For every weak problem you get the one line that was changed, how many broken versions got
87
- through, and an assert that would have caught one.
88
-
89
- **`--fixes fixes.py` writes those asserts to a file; review them before adding them.** Measured on
90
- 34 MBPP/HumanEval problems against a 1.5B model's real solutions: v0.2's suggested asserts wrongly
91
- reject **2.6% of its correct solutions** (8 of 307; v0.1: 15.3%). They target the broken versions
92
- gameability builds, not the mistakes a particular model makes: they caught 4 of that model's 24
93
- real wrong solutions. Treat them as leads, not as a test suite.
94
-
95
- ## How it works
96
-
97
- For each problem the benchmark supplies (reference solution, visible tests):
98
-
99
- 1. The reference must pass its own visible tests. If it doesn't, the problem is excluded and counted.
100
- 2. Fresh inputs are built by varying the arguments in the visible tests. The reference's
101
- outputs on them are the hidden answer key.
102
- 3. The reference is mutated at a single site: a comparison flipped, an operator swapped, a
103
- constant nudged, a `return` replaced with `return None`.
104
- 4. A mutant counts as **broken** only if it gives a different answer from the reference on
105
- at least one fresh input. Mutants that behave identically are thrown away, so they can't
106
- inflate the number.
107
- 5. A broken mutant that still passes every visible test is a **survivor**. A problem with at
108
- least one survivor accepts a wrong solution.
109
-
110
- Benchmark code runs in a child process with a step budget, so a runaway mutant is stopped at
111
- the same point on any machine; a wall-clock limit is only a backstop. Mutants that never
112
- finish are reported as truncated, never counted as rejected.
113
-
114
- ## Your own benchmark
115
-
116
- One problem per line in a `.jsonl` file:
117
-
118
- ```json
119
- {"id": "clamp", "reference": "def clamp(x, lo, hi): ...", "tests": ["assert clamp(5, 0, 10) == 5"]}
120
- ```
121
-
122
- Optional fields: `entry` (the function name, if it can't be inferred), `setup` (code run
123
- before the tests), `hidden` (held-out asserts you already have; they are used as extra
124
- answer-key inputs), `call_alias` (the name the tests call the solution by, e.g. `candidate`).
125
-
126
- ```
127
- gameability check examples/tiny.jsonl --fixes fixes.py --json report.json
128
- ```
129
-
130
- ## What the number means, and what it doesn't
131
-
132
- - **The attacker is weak.** One small edit is the simplest possible way to be wrong; a model trained
133
- against these tests can search far more. In that sense the rates are a floor.
134
- - **Fresh inputs can still break a problem's rules in ways the tests don't reveal.** v0.2 keeps each
135
- argument inside its observed domain, but it does not model relationships BETWEEN arguments (a
136
- length argument that must equal a list's length) or rules that only the docstring states. On the
137
- problems EvalPlus covers, 4.8% of flagged MBPP and 15.4% of flagged HumanEval problems are still
138
- unconfirmed (v0.1: 14.3% and 21.4%).
139
- - **It does not predict where a model will cheat.** On a 1.5B coder, false passes were no more
140
- common on flagged problems than on others (8.4% vs 10.0%). It audits the tests' strength, not a
141
- model's behaviour.
142
- - **It audits the tests, not any model.** Nothing here says a particular model cheated. It says
143
- which problems would not have caught it.
144
- - **It runs the benchmark's code.** Reference solutions, mutants and tests are executed, each job in
145
- its own throwaway directory (some benchmark tasks write files; v0.1 let them land in your working
146
- folder). That is isolation from your files, not a security sandbox: only run it on benchmarks
147
- you'd be willing to execute.
148
-
149
- ## Reproducing the headline numbers
150
-
151
- ```
152
- gameability check mbpp --json mbpp.json
153
- gameability check humaneval --json humaneval.json
154
- ```
155
-
156
- Defaults: 24 mutants per problem, 8 fresh inputs, seed 0, contract inputs. Add `--inputs free` to
157
- reproduce v0.1's numbers (MBPP 18.5%, HumanEval 11.8%). The known-answer tests run with
158
- `python tests/test_gameability.py` and `python tests/test_contract_inputs.py`.
159
-
160
- ## License
161
-
162
- MIT
1
+ Metadata-Version: 2.4
2
+ Name: gameability
3
+ Version: 0.3.1
4
+ Summary: Find the problems in a code benchmark that accept broken solutions, and RL environments whose reward pays for no work.
5
+ Author: Empyrean Intelligence
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Empyrean-Intelligence/gameability
8
+ Project-URL: Source, https://github.com/Empyrean-Intelligence/gameability
9
+ Project-URL: Issues, https://github.com/Empyrean-Intelligence/gameability/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
+ Classifier: Topic :: Software Development :: Testing
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Provides-Extra: hf
17
+ Requires-Dist: datasets; extra == "hf"
18
+ Dynamic: license-file
19
+
20
+ # gameability
21
+
22
+ [![PyPI](https://img.shields.io/pypi/v/gameability)](https://pypi.org/project/gameability/) [![Python](https://img.shields.io/pypi/pyversions/gameability)](https://pypi.org/project/gameability/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Empyrean Intelligence](https://img.shields.io/badge/by-Empyrean%20Intelligence-111)](https://empyreanintelligence.com)
23
+
24
+ **Find the problems in a code benchmark that accept broken solutions.**
25
+
26
+ When a model learns from tests, the tests are its reward. A self-improving system trained on tests that
27
+ accept broken code learns to pass weak tests, not to write correct code. `gameability` measures how often
28
+ that can happen. It is part of the verification layer we use at Empyrean Intelligence for self-improvement
29
+ work, released for anyone who builds benchmarks or RL training sets.
30
+
31
+ A benchmark score means something only if the benchmark rejects solutions that lack the
32
+ skill it measures. `gameability` checks that directly. It takes each problem's reference
33
+ solution, breaks it in one small place, confirms the result really is wrong, and then
34
+ asks whether the problem's own tests notice.
35
+
36
+ ```
37
+ pip install "gameability[hf]"
38
+ gameability check mbpp
39
+ ```
40
+
41
+ On MBPP, **17.9% of problems (169 of 942 judged) accept a broken solution**; on HumanEval,
42
+ **10.3% (16 of 155)**. Each run takes minutes on a laptop, with no model and no API key.
43
+
44
+ **Confirmed on valid inputs.** On the problems EvalPlus also covers, every accepted broken solution
45
+ was re-run against EvalPlus's independent tests, whose inputs respect each problem's rules:
46
+ **95.2% of flagged MBPP problems and 84.6% of flagged HumanEval problems are confirmed** (the broken
47
+ solution passes the original tests and fails EvalPlus). The two unconfirmed HumanEval problems
48
+ (116 and 129) are ones whose own visible tests contradict their written specification.
49
+
50
+ **New in v0.3: `gameability probe`.** It asks the same question of an RL environment's reward: does
51
+ it pay a policy that does no work? See [Probe an RL environment's reward](#probe-an-rl-environments-reward).
52
+ By default it makes no model calls.
53
+
54
+ **What changed in v0.2.** v0.1 built fresh inputs by changing values freely, which sometimes asked
55
+ a problem about inputs it never promised to handle (an unsorted list for binary search, random
56
+ letters for a parenthesis parser). v0.2 learns each argument's domain from the problem's own
57
+ visible tests and stays inside it: number signs and minimums, the characters or whole tokens a
58
+ string uses, and whether lists are sorted, unique, rectangular or a permutation. On the same
59
+ problems this raised the confirmed share from 85.7% to 95.2% (MBPP) and 78.6% to 84.6% (HumanEval)
60
+ while keeping 93.1% of v0.1's confirmed detections. `--inputs free` reproduces v0.1.
61
+
62
+ ## Scoreboard
63
+
64
+ Problems that accept at least one broken solution (wrong on at least one fresh input), with 95% intervals. Defaults (24 broken
65
+ versions per problem, 8 fresh inputs, seed 0), laptop CPU, October 2026.
66
+
67
+ | Benchmark | Weak problems | Command |
68
+ |---|---|---|
69
+ | MBPP | 17.9% (169 of 942) [15.6, 20.5] | `gameability check mbpp` |
70
+ | MBPP sanitized | 13.8% (57 of 413) [10.8, 17.5] | `gameability check mbpp-sanitized` |
71
+ | HumanEval | 10.3% (16 of 155) [6.5, 16.1] | `gameability check humaneval` |
72
+ | KodCode (RL training set, 1,000-row random sample) | 9.7% (45 of 466) [7.3, 12.7] | `gameability check kodcode:rows.jsonl` |
73
+ | HumanEval+ (EvalPlus) | 3.4% (5 of 147) [1.5, 7.7] | `gameability check humaneval-plus` |
74
+ | MBPP+ (EvalPlus) | 2.0% (7 of 353) [1.0, 4.0] | `gameability check mbpp-plus` |
75
+
76
+ EvalPlus's stronger tests look like a large improvement, but EvalPlus also rewrote most reference
77
+ solutions. Holding the original reference fixed (`mbpp-plus-origref`, `humaneval-plus-origref`), the weak
78
+ share roughly halves (MBPP 8.7% to 3.1% on 254 problems, HumanEval 10.6% to 4.9% on 123), and at these
79
+ sample sizes the intervals overlap. KodCode's judged set is the problems whose tests call the function with
80
+ literal arguments (466 of 801 loadable). `rows.jsonl` is KodCode rows saved as JSON lines; the sample above
81
+ is reproducible with `python tests/sample_kodcode.py rows.jsonl` (positions from `random.Random(0)`).
82
+
83
+ ## What it prints
84
+
85
+ ```
86
+ 415 -- 6 of 11 broken solutions pass every visible test
87
+ - if arr_len < 2:
88
+ + if arr_len <= 2:
89
+ a test that catches it: assert max_Product([4, 9]) == (4, 9)
90
+ ```
91
+
92
+ For every weak problem you get the one line that was changed, how many broken versions got
93
+ through, and an assert that would have caught one.
94
+
95
+ **`--fixes fixes.py` writes those asserts to a file; review them before adding them.** Measured on
96
+ 34 MBPP/HumanEval problems against a 1.5B model's real solutions: v0.2's suggested asserts wrongly
97
+ reject **2.6% of its correct solutions** (8 of 307; v0.1: 15.3%). They target the broken versions
98
+ gameability builds, not the mistakes a particular model makes: they caught 4 of that model's 24
99
+ real wrong solutions. Treat them as leads, not as a test suite.
100
+
101
+ ## How it works
102
+
103
+ For each problem the benchmark supplies (reference solution, visible tests):
104
+
105
+ 1. The reference must pass its own visible tests. If it doesn't, the problem is excluded and counted.
106
+ 2. Fresh inputs are built by varying the arguments in the visible tests. The reference's
107
+ outputs on them are the hidden answer key.
108
+ 3. The reference is mutated at a single site: a comparison flipped, an operator swapped, a
109
+ constant nudged, a `return` replaced with `return None`.
110
+ 4. A mutant counts as **broken** only if it gives a different answer from the reference on
111
+ at least one fresh input. Mutants that behave identically are thrown away, so they can't
112
+ inflate the number.
113
+ 5. A broken mutant that still passes every visible test is a **survivor**. A problem with at
114
+ least one survivor accepts a wrong solution.
115
+
116
+ Benchmark code runs in a child process with a step budget, so a runaway mutant is stopped at
117
+ the same point on any machine; a wall-clock limit is only a backstop. Mutants that never
118
+ finish are reported as truncated, never counted as rejected.
119
+
120
+ ## Probe an RL environment's reward
121
+
122
+ `gameability probe` asks a different question of an RL environment: does its reward pay a policy that
123
+ does no work? It answers every item with an empty string, a refusal, the prompt echoed back, a wrong
124
+ answer in the expected format, and filler text, and scores each through the environment's own rubric.
125
+
126
+ ```
127
+ pip install gameability verifiers
128
+ pip install ./my_env # your verifiers environment
129
+ gameability probe my_env
130
+ ```
131
+
132
+ On a multiple-choice test environment with an exact-match reward (verifiers 0.1.11):
133
+
134
+ ```
135
+ mmlu: 20 items, eval split, scored by the environment's own rubric
136
+ correct answers 1.000 (answer format: plain)
137
+ empty answer 0.000
138
+ refusal 0.000
139
+ prompt echoed back 0.000
140
+ wrong answer, right format 0.000
141
+ filler text 0.000
142
+ (most common answer every time earns 25% of the correct-answer reward; reported, not counted)
143
+ VERDICT: PASS -- no null probe earns 50% of the correct-answer reward (finds only gaming that needs no capability)
144
+ ```
145
+
146
+ An environment that pays for an empty answer instead prints `<-- pays for no work` on that line and
147
+ `VERDICT: GAMEABLE`.
148
+
149
+ The exit code is 1 when the environment is gameable, so it can gate CI. No model is called by default:
150
+ OpenAI clients are pointed at a dead local address, so a reward that needs an LLM judge is reported
151
+ UNREACHABLE instead of spending your credits (`--allow-model-calls` to permit it). A PASS means no
152
+ *no-capability* gaming was found; it is not a proof that the reward is sound.
153
+
154
+ ## Your own benchmark
155
+
156
+ One problem per line in a `.jsonl` file:
157
+
158
+ ```json
159
+ {"id": "clamp", "reference": "def clamp(x, lo, hi): ...", "tests": ["assert clamp(5, 0, 10) == 5"]}
160
+ ```
161
+
162
+ Optional fields: `entry` (the function name, if it can't be inferred), `setup` (code run
163
+ before the tests), `hidden` (held-out asserts you already have; they are used as extra
164
+ answer-key inputs), `call_alias` (the name the tests call the solution by, e.g. `candidate`).
165
+
166
+ ```
167
+ gameability check examples/tiny.jsonl --fixes fixes.py --json report.json
168
+ ```
169
+
170
+ ## What the number means, and what it doesn't
171
+
172
+ - **The attacker is weak.** One small edit is the simplest possible way to be wrong; a model trained
173
+ against these tests can search far more. In that sense the rates are a floor.
174
+ - **Fresh inputs can still break a problem's rules in ways the tests don't reveal.** v0.2 keeps each
175
+ argument inside its observed domain, but it does not model relationships BETWEEN arguments (a
176
+ length argument that must equal a list's length) or rules that only the docstring states. On the
177
+ problems EvalPlus covers, 4.8% of flagged MBPP and 15.4% of flagged HumanEval problems are still
178
+ unconfirmed (v0.1: 14.3% and 21.4%).
179
+ - **It does not predict where a model will cheat.** On a 1.5B coder, false passes were no more
180
+ common on flagged problems than on others (8.4% vs 10.0%). It audits the tests' strength, not a
181
+ model's behaviour.
182
+ - **It audits the tests, not any model.** Nothing here says a particular model cheated. It says
183
+ which problems would not have caught it.
184
+ - **It runs the benchmark's code.** Reference solutions, mutants and tests are executed, each job in
185
+ its own throwaway directory (some benchmark tasks write files; v0.1 let them land in your working
186
+ folder). That is isolation from your files, not a security sandbox: only run it on benchmarks
187
+ you'd be willing to execute.
188
+
189
+ ## Reproducing the headline numbers
190
+
191
+ ```
192
+ gameability check mbpp --json mbpp.json
193
+ gameability check humaneval --json humaneval.json
194
+ ```
195
+
196
+ Defaults: 24 mutants per problem, 8 fresh inputs, seed 0, contract inputs. Add `--inputs free` to
197
+ reproduce v0.1's numbers (MBPP 18.5%, HumanEval 11.8%). The known-answer tests run with
198
+ `python tests/test_gameability.py` and `python tests/test_contract_inputs.py`.
199
+
200
+ ## License
201
+
202
+ MIT
@@ -1,5 +1,7 @@
1
1
  # gameability
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/gameability)](https://pypi.org/project/gameability/) [![Python](https://img.shields.io/pypi/pyversions/gameability)](https://pypi.org/project/gameability/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Empyrean Intelligence](https://img.shields.io/badge/by-Empyrean%20Intelligence-111)](https://empyreanintelligence.com)
4
+
3
5
  **Find the problems in a code benchmark that accept broken solutions.**
4
6
 
5
7
  When a model learns from tests, the tests are its reward. A self-improving system trained on tests that
@@ -26,6 +28,10 @@ was re-run against EvalPlus's independent tests, whose inputs respect each probl
26
28
  solution passes the original tests and fails EvalPlus). The two unconfirmed HumanEval problems
27
29
  (116 and 129) are ones whose own visible tests contradict their written specification.
28
30
 
31
+ **New in v0.3: `gameability probe`.** It asks the same question of an RL environment's reward: does
32
+ it pay a policy that does no work? See [Probe an RL environment's reward](#probe-an-rl-environments-reward).
33
+ By default it makes no model calls.
34
+
29
35
  **What changed in v0.2.** v0.1 built fresh inputs by changing values freely, which sometimes asked
30
36
  a problem about inputs it never promised to handle (an unsorted list for binary search, random
31
37
  letters for a parenthesis parser). v0.2 learns each argument's domain from the problem's own
@@ -92,6 +98,40 @@ Benchmark code runs in a child process with a step budget, so a runaway mutant i
92
98
  the same point on any machine; a wall-clock limit is only a backstop. Mutants that never
93
99
  finish are reported as truncated, never counted as rejected.
94
100
 
101
+ ## Probe an RL environment's reward
102
+
103
+ `gameability probe` asks a different question of an RL environment: does its reward pay a policy that
104
+ does no work? It answers every item with an empty string, a refusal, the prompt echoed back, a wrong
105
+ answer in the expected format, and filler text, and scores each through the environment's own rubric.
106
+
107
+ ```
108
+ pip install gameability verifiers
109
+ pip install ./my_env # your verifiers environment
110
+ gameability probe my_env
111
+ ```
112
+
113
+ On a multiple-choice test environment with an exact-match reward (verifiers 0.1.11):
114
+
115
+ ```
116
+ mmlu: 20 items, eval split, scored by the environment's own rubric
117
+ correct answers 1.000 (answer format: plain)
118
+ empty answer 0.000
119
+ refusal 0.000
120
+ prompt echoed back 0.000
121
+ wrong answer, right format 0.000
122
+ filler text 0.000
123
+ (most common answer every time earns 25% of the correct-answer reward; reported, not counted)
124
+ VERDICT: PASS -- no null probe earns 50% of the correct-answer reward (finds only gaming that needs no capability)
125
+ ```
126
+
127
+ An environment that pays for an empty answer instead prints `<-- pays for no work` on that line and
128
+ `VERDICT: GAMEABLE`.
129
+
130
+ The exit code is 1 when the environment is gameable, so it can gate CI. No model is called by default:
131
+ OpenAI clients are pointed at a dead local address, so a reward that needs an LLM judge is reported
132
+ UNREACHABLE instead of spending your credits (`--allow-model-calls` to permit it). A PASS means no
133
+ *no-capability* gaming was found; it is not a proof that the reward is sound.
134
+
95
135
  ## Your own benchmark
96
136
 
97
137
  One problem per line in a `.jsonl` file:
@@ -1,33 +1,33 @@
1
- [build-system]
2
- requires = ["setuptools>=77"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "gameability"
7
- version = "0.2.0"
8
- description = "Find the problems in a code benchmark that accept broken solutions."
9
- readme = "README.md"
10
- license = "MIT"
11
- license-files = ["LICENSE"]
12
- requires-python = ">=3.9"
13
- authors = [{ name = "Empyrean Intelligence" }]
14
- dependencies = []
15
- classifiers = [
16
- "Programming Language :: Python :: 3",
17
- "Topic :: Scientific/Engineering :: Artificial Intelligence",
18
- "Topic :: Software Development :: Testing",
19
- ]
20
-
21
- [project.optional-dependencies]
22
- hf = ["datasets"]
23
-
24
- [project.urls]
25
- Homepage = "https://github.com/Empyrean-Intelligence/gameability"
26
- Source = "https://github.com/Empyrean-Intelligence/gameability"
27
- Issues = "https://github.com/Empyrean-Intelligence/gameability/issues"
28
-
29
- [project.scripts]
30
- gameability = "gameability.cli:main"
31
-
32
- [tool.setuptools.packages.find]
33
- where = ["src"]
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gameability"
7
+ version = "0.3.1"
8
+ description = "Find the problems in a code benchmark that accept broken solutions, and RL environments whose reward pays for no work."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [{ name = "Empyrean Intelligence" }]
14
+ dependencies = []
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
18
+ "Topic :: Software Development :: Testing",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ hf = ["datasets"]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/Empyrean-Intelligence/gameability"
26
+ Source = "https://github.com/Empyrean-Intelligence/gameability"
27
+ Issues = "https://github.com/Empyrean-Intelligence/gameability/issues"
28
+
29
+ [project.scripts]
30
+ gameability = "gameability.cli:main"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
@@ -1,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -16,4 +16,4 @@ from .engine import Problem, audit, census, report, summarise
16
16
  from .harden import witnesses
17
17
 
18
18
  __all__ = ["Problem", "audit", "census", "summarise", "report", "witnesses"]
19
- __version__ = "0.2.0"
19
+ __version__ = "0.3.1"
@@ -1,8 +1,9 @@
1
- """Command line: gameability check <benchmark>
1
+ """Command line: gameability check <benchmark> | gameability probe <rl-environment>
2
2
 
3
3
  gameability check mbpp
4
4
  gameability check humaneval --limit 50
5
5
  gameability check my_benchmark.jsonl --fixes fixes.py --json report.json
6
+ gameability probe my_env --n 20
6
7
  """
7
8
  import argparse
8
9
  import ast
@@ -109,7 +110,8 @@ def check(a):
109
110
 
110
111
  def main(argv=None):
111
112
  ap = argparse.ArgumentParser(prog="gameability",
112
- description="Find the problems in a code benchmark that accept broken solutions.")
113
+ description="Find the problems in a code benchmark that accept broken solutions, "
114
+ "and the RL environments whose reward pays for no work.")
113
115
  ap.add_argument("--version", action="version", version="gameability " + __version__)
114
116
  sub = ap.add_subparsers(dest="cmd", required=True)
115
117
  c = sub.add_parser("check", help="audit a benchmark")
@@ -126,9 +128,29 @@ def main(argv=None):
126
128
  c.add_argument("--show", type=int, default=5, help="print this many weak problems with the changed line")
127
129
  c.add_argument("--fixes", help="write suggested asserts to this file")
128
130
  c.add_argument("--json", help="write the full report to this file")
131
+ p = sub.add_parser("probe", help="check whether an RL environment's reward pays for no work")
132
+ p.add_argument("env", help="id of a verifiers environment installed in this Python")
133
+ p.add_argument("--n", type=int, default=20, help="number of items to score (default 20)")
134
+ p.add_argument("--args", default="{}", help="JSON keyword arguments for load_environment")
135
+ p.add_argument("--allow-model-calls", action="store_true",
136
+ help="let rewards that use an LLM judge call your OpenAI endpoint (costs money)")
137
+ p.add_argument("--json", help="write the full result to this file")
129
138
  a = ap.parse_args(argv)
130
139
  if a.cmd == "check":
131
140
  return check(a)
141
+ if a.cmd == "probe":
142
+ return probe_cmd(a)
143
+
144
+
145
+ def probe_cmd(a):
146
+ from .probe import probe, report as probe_report
147
+ res = probe(a.env, n_items=a.n, load_args=json.loads(a.args), allow_model_calls=a.allow_model_calls)
148
+ probe_report(res)
149
+ if a.json:
150
+ with open(a.json, "w", encoding="utf-8") as f:
151
+ json.dump(dict(res, version=__version__), f, indent=1, default=str)
152
+ print("wrote %s" % a.json)
153
+ return 1 if res.get("gameable") else 0
132
154
 
133
155
 
134
156
  if __name__ == "__main__":