Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 97 additions & 5 deletions scripts/native_eval/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@
"trial_name",
"result_path",
"classification",
"execution_outcome",
"execution_exit_code",
"execution_reason",
"reward",
"exception_type",
"exception_message",
Expand Down Expand Up @@ -142,6 +145,8 @@
"infra_dominated",
"harness_wide_failure",
"harness_wide_failure_signature",
"run_accepted",
"run_acceptance_reason",
"canonical_model_identity",
"trajectory_complete",
"parity_validated",
Expand Down Expand Up @@ -278,8 +283,18 @@ def _is_infra(exception_type: str, exception_message: str) -> bool:
return any(pattern in combined for pattern in INFRA_MESSAGE_PATTERNS)


def _classify(result: dict[str, Any], reward: int | float | None) -> str:
def _classify(
result: dict[str, Any],
reward: int | float | None,
execution_kind: str,
) -> str:
exception_type, exception_message, _ = _exception_fields(result)
if execution_kind in {"harness_error", "infra_error"}:
return "infra"
if execution_kind == "verifier_error":
return "verifier_missing_reward"
if execution_kind == "agent_error":
return "agent_exit"
if exception_type in MISSING_REWARD_EXCEPTION_TYPES:
return "verifier_missing_reward"
if exception_type:
Expand All @@ -295,6 +310,40 @@ def _classify(result: dict[str, Any], reward: int | float | None) -> str:
return "pass"


def _execution_fields(
result: dict[str, Any],
*,
harness: str,
) -> tuple[str, int | None, str]:
outcome = result.get("execution_outcome")
if isinstance(outcome, dict):
kind = str(outcome.get("kind") or "")
if kind and kind != "pending":
exit_code = _number(outcome.get("exit_code"))
return (
kind,
int(exit_code) if exit_code is not None else None,
str(outcome.get("reason") or ""),
)

exception_type, exception_message, _ = _exception_fields(result)
exit_match = re.search(r"\bcode\s+(\d+)\b", exception_message)
exit_code = int(exit_match.group(1)) if exit_match else None
if (
harness == "openclaw"
and exception_type == "NonZeroAgentExitCodeError"
and exit_code in {70, 71}
):
return "harness_error", exit_code, exception_message
if exception_type in MISSING_REWARD_EXCEPTION_TYPES:
return "verifier_error", exit_code, exception_message
if exception_type and _is_infra(exception_type, exception_message):
return "infra_error", exit_code, exception_message
if exception_type:
return "agent_error", exit_code, exception_message
return "clean", None, ""


def _scorecard_infra_error(trial_dir: Path) -> tuple[str, str] | None:
scorecard_path = trial_dir / "verifier" / "scorecard.json"
if not scorecard_path.is_file():
Expand Down Expand Up @@ -525,13 +574,21 @@ def _normalize_result(
run_label: str,
pair_label: str,
repetition: int | None,
harness: str,
) -> dict[str, Any]:
task_name, task_path, trial_name = _task_identity(result, result_path.parent)
reward = _reward(result)
exception_type, exception_message, exception_occurred_at = _exception_fields(result)
scorecard_infra = _scorecard_infra_error(result_path.parent)
if scorecard_infra is not None:
exception_type, exception_message = scorecard_infra
execution_kind, execution_exit_code, execution_reason = _execution_fields(
result,
harness=harness,
)
if scorecard_infra is not None:
execution_kind = "infra_error"
execution_reason = exception_message
agent_result = result.get("agent_result")
agent_result = agent_result if isinstance(agent_result, dict) else {}
row: dict[str, Any] = {
Expand All @@ -543,8 +600,13 @@ def _normalize_result(
"trial_name": trial_name,
"result_path": str(result_path),
"classification": (
"infra" if scorecard_infra is not None else _classify(result, reward)
"infra"
if scorecard_infra is not None
else _classify(result, reward, execution_kind)
),
"execution_outcome": execution_kind,
"execution_exit_code": execution_exit_code,
"execution_reason": execution_reason,
"reward": reward,
"exception_type": exception_type,
"exception_message": exception_message,
Expand Down Expand Up @@ -654,6 +716,7 @@ def _load_run(
run_label = str(manifest.get("run_label") or run_label)
pair_label = _pair_label(manifest, run_label)
repetition = _repetition(manifest, run_label)
harness = str(manifest.get("harness") or "")

rows: list[dict[str, Any]] = []
trial_dirs = sorted(path for path in run_dir.iterdir() if path.is_dir())
Expand Down Expand Up @@ -699,6 +762,7 @@ def _load_run(
run_label=run_label,
pair_label=pair_label,
repetition=repetition,
harness=harness,
)
)

Expand Down Expand Up @@ -840,6 +904,10 @@ def _summarize_run(
classifications = [str(row["classification"]) for row in scored_rows]
rewards = [_number(row.get("reward")) for row in scored_rows]
all_classifications = [str(row["classification"]) for row in rows]
execution_kinds = [
str(row.get("execution_outcome") or "")
for row in scored_rows
]
result_file_count = sum(bool(row.get("_has_result_file")) for row in rows)
valid_result_count = sum(bool(row.get("_valid_result")) for row in rows)
completed_result_count = sum(bool(row.get("_completed_result")) for row in rows)
Expand Down Expand Up @@ -874,6 +942,25 @@ def _summarize_run(
rows,
expected_count,
)
acceptance = manifest.get("execution_acceptance")
acceptance = acceptance if isinstance(acceptance, dict) else {}
accepted_value = acceptance.get("accepted")
if isinstance(accepted_value, bool):
run_accepted = accepted_value
else:
invalid_execution_kinds = {
"harness_error",
"infra_error",
"verifier_error",
}
run_accepted = not (
expected_count > 0
and len(execution_kinds) == expected_count
and all(kind in invalid_execution_kinds for kind in execution_kinds)
)
run_acceptance_reason = str(acceptance.get("reason") or "")
if not run_accepted and not run_acceptance_reason:
run_acceptance_reason = "all_trials_harness_or_infrastructure_errors"
native_run = (
manifest.get("runner") == "shellbench-native"
or any(row.get("source") == "shellbench-native" for row in rows)
Expand Down Expand Up @@ -917,6 +1004,7 @@ def _summarize_run(
) = _parity_validation(manifest, native_run=native_run)
eligible = (
not incomplete
and run_accepted
and not infra_dominated
and not harness_wide_failure
and canonical_model_identity
Expand All @@ -926,6 +1014,8 @@ def _summarize_run(
exclusion_reason = "incomplete"
elif infra_dominated:
exclusion_reason = "infra_dominated"
elif not run_accepted:
exclusion_reason = "run_rejected"
elif harness_wide_failure:
exclusion_reason = "harness_wide_failure"
elif not canonical_model_identity:
Expand Down Expand Up @@ -962,6 +1052,8 @@ def _summarize_run(
"infra_dominated": infra_dominated,
"harness_wide_failure": harness_wide_failure,
"harness_wide_failure_signature": harness_wide_failure_signature,
"run_accepted": run_accepted,
"run_acceptance_reason": run_acceptance_reason,
"canonical_model_identity": canonical_model_identity,
"trajectory_complete": trajectory_complete,
"parity_validated": parity_validated,
Expand Down Expand Up @@ -1184,9 +1276,9 @@ def _write_leaderboard(path: Path, pairs: Sequence[dict[str, Any]]) -> None:
lines = [
"# Cleaned Native ShellBench Leaderboard",
"",
"Incomplete, infra-dominated, harness-wide failure, identity-invalid, and "
"trajectory-incomplete repetitions are excluded. Harbor parity validation is "
"reported separately and is not an eligibility gate.",
"Incomplete, rejected, infra-dominated, harness-wide failure, identity-invalid, "
"and trajectory-incomplete repetitions are excluded. Harbor parity validation "
"is reported separately and is not an eligibility gate.",
"",
"| Rank | Pair | Repetitions | Parity validated | Mean | Stdev | Min | Max | Mean exact passes | Pass rate | Clean complete |",
"| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
Expand Down
23 changes: 0 additions & 23 deletions scripts/native_eval/fleet.py
Original file line number Diff line number Diff line change
Expand Up @@ -1246,16 +1246,6 @@ def _archived_exit_status(self, run_label: str) -> int | None:
raise FleetError(f"conflicting archived exit_status values for {run_label}")
return candidates[0] if candidates else None

def _checkpoint_log_has_final(self, run_label: str) -> bool:
log_path = self.config.local_root / "logs" / f"{run_label}.checkpoints.log"
if not log_path.is_file():
return False
for line in log_path.read_text(encoding="utf-8").splitlines():
fields = line.split("\t", 4)
if len(fields) >= 2 and fields[1] == "final":
return True
return False

def _finish_exported(self, entry: dict[str, Any], run: RunSpec) -> bool:
verified, result_count, artifacts = self._verify_final(run.run_label)
if not verified:
Expand All @@ -1282,19 +1272,6 @@ def _finish_exported(self, entry: dict[str, Any], run: RunSpec) -> bool:
"run_exit_code": run_exit_code,
"run_exit_code_source": "archived_exit_status",
}
elif (
result_count == run.expected_task_count
and self._checkpoint_log_has_final(run.run_label)
):
run_exit_code = 0
exit_code_changes = {
"run_exit_code": 0,
"run_exit_code_source": "recovered_full_coverage_remote_done",
"run_exit_code_inference": (
"inferred zero from verified full-coverage archive and "
"checkpoint final event after remote done"
),
}
stop_command = [
self.config.crabbox_bin,
"stop",
Expand Down
35 changes: 35 additions & 0 deletions scripts/native_eval/run_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import subprocess
import uuid
from collections import Counter
from dataclasses import asdict
from pathlib import Path
from typing import Any
Expand Down Expand Up @@ -127,9 +128,12 @@ async def execute(task: TaskSpec) -> None:
state["finished_at"] = utc_now()
state["updated_at"] = state["finished_at"]
_update_job_result(state, results, len(tasks))
acceptance = _execution_acceptance(results, len(tasks))
state["execution_acceptance"] = acceptance
atomic_write_json(job_dir / "result.json", state)
manifest["finished_at_utc"] = state["finished_at"]
manifest["result_json_count"] = len(results)
manifest["execution_acceptance"] = acceptance
agent_results = [
result.get("agent_result")
for result in results
Expand Down Expand Up @@ -204,6 +208,30 @@ def _update_job_result(
state["updated_at"] = utc_now()


def _execution_acceptance(
results: list[dict[str, Any]],
total: int,
) -> dict[str, Any]:
kinds = Counter(
str((result.get("execution_outcome") or {}).get("kind") or "unknown")
for result in results
)
invalid_kinds = {"harness_error", "infra_error", "verifier_error"}
invalid_count = sum(kinds[kind] for kind in invalid_kinds)
accepted = len(results) == total and invalid_count < total
if len(results) != total:
reason = "incomplete_result_coverage"
elif invalid_count == total and total:
reason = "all_trials_harness_or_infrastructure_errors"
else:
reason = None
return {
"accepted": accepted,
"reason": reason,
"outcome_counts": dict(sorted(kinds.items())),
}


def _run_manifest(
run: RunSpec,
*,
Expand Down Expand Up @@ -293,6 +321,11 @@ def _run_manifest(
"started_at_utc": started_at,
"finished_at_utc": None,
"result_json_count": 0,
"execution_acceptance": {
"accepted": None,
"reason": "run_in_progress",
"outcome_counts": {},
},
}


Expand Down Expand Up @@ -434,6 +467,8 @@ def main() -> None:
)
)
print(json.dumps(state, indent=2))
if state["execution_acceptance"]["accepted"] is not True:
raise SystemExit(2)


if __name__ == "__main__":
Expand Down
Loading