| 97 |
165
|
|
).encode()
|
| 98 |
166
|
|
recipe_digest = "harbor:" + hashlib.sha256(recipe_source).hexdigest()
|
| 99 |
167
|
|
|
|
168
|
+ |
report = {
|
|
169
|
+ |
"job_id": result.get("id"),
|
|
170
|
+ |
"trials": [
|
|
171
|
+ |
{
|
|
172
|
+ |
"task": task_name(trial),
|
|
173
|
+ |
"passed": passed(trial),
|
|
174
|
+ |
"exception": (trial.get("exception_info") or {}).get("exception_type")
|
|
175
|
+ |
if trial.get("exception_info")
|
|
176
|
+ |
else None,
|
|
177
|
+ |
}
|
|
178
|
+ |
for trial in trials
|
|
179
|
+ |
],
|
|
180
|
+ |
}
|
|
181
|
+ |
|
|
182
|
+ |
if arguments.run_id:
|
|
183
|
+ |
run_url = f"{arguments.api_url}/api/v1/gym/runs/{arguments.run_id}"
|
|
184
|
+ |
|
|
185
|
+ |
# Each trial's final state, upserted by task. A trial whose verifier
|
|
186
|
+ |
# never ran stays a claimless `ungraded` rather than a grade.
|
|
187
|
+ |
for trial, trial_dir in trial_entries:
|
|
188
|
+ |
state = ("passed" if passed(trial) else "failed") if verifier_ran(trial) else "ungraded"
|
|
189
|
+ |
trial_payload = {"task": task_key(trial_dir), "state": state}
|
|
190
|
+ |
thread_id = thread_id_of(trial_dir)
|
|
191
|
+ |
if thread_id:
|
|
192
|
+ |
trial_payload["thread_id"] = thread_id
|
|
193
|
+ |
try:
|
|
194
|
+ |
request_json(f"{run_url}/trials", token, trial_payload, "POST")
|
|
195
|
+ |
except (urllib.error.URLError, OSError) as error:
|
|
196
|
+ |
print(f"trial upsert failed for {task_key(trial_dir)}: {error}", file=sys.stderr)
|
|
197
|
+ |
|
|
198
|
+ |
if not any(verifier_ran(trial) for trial in trials):
|
|
199
|
+ |
# A crashed grader is not a grade, and a run nobody will grade
|
|
200
|
+ |
# should not stay a forever-running row.
|
|
201
|
+ |
request_json(run_url, token, {"status": "abandoned"}, "PATCH")
|
|
202
|
+ |
print(f"run {arguments.run_id} abandoned: no trial's verifier ran")
|
|
203
|
+ |
return 0
|
|
204
|
+ |
|
|
205
|
+ |
finalize = {
|
|
206
|
+ |
"status": "graded",
|
|
207
|
+ |
"tasks_total": tasks_total,
|
|
208
|
+ |
"tasks_passed": tasks_passed,
|
|
209
|
+ |
"input_tokens": input_tokens or None,
|
|
210
|
+ |
"output_tokens": output_tokens or None,
|
|
211
|
+ |
"duration_seconds": int(duration_seconds) or None,
|
|
212
|
+ |
"recipe_digest": recipe_digest,
|
|
213
|
+ |
"report": report,
|
|
214
|
+ |
"model": model,
|
|
215
|
+ |
"agent_version": agent_version,
|
|
216
|
+ |
}
|
|
217
|
+ |
try:
|
|
218
|
+ |
status, body = request_json(run_url, token, finalize, "PATCH")
|
|
219
|
+ |
except urllib.error.HTTPError as error:
|
|
220
|
+ |
if error.code == 409:
|
|
221
|
+ |
# Another run already holds this digest: a replay, not a
|
|
222
|
+ |
# failure. The registered row is closed by the run that owns
|
|
223
|
+ |
# the digest.
|
|
224
|
+ |
print(f"409: run {arguments.run_id} replayed an existing digest")
|
|
225
|
+ |
return 0
|
|
226
|
+ |
raise
|
|
227
|
+ |
run = body.get("run") or {}
|
|
228
|
+ |
print(
|
|
229
|
+ |
f"{status}: run {run.get('id') or arguments.run_id} "
|
|
230
|
+ |
f"status={run.get('status')} passed={tasks_passed}/{tasks_total}"
|
|
231
|
+ |
)
|
|
232
|
+ |
return 0
|
|
233
|
+ |
|
| 100 |
234
|
|
payload = {
|
| 101 |
235
|
|
"suite": arguments.suite,
|
| 102 |
236
|
|
"agent": "openagents-coder",
|