Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions src/benchflow/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,10 @@ def _get_task_dirs(self) -> list[Path]:
def _get_completed_tasks(self) -> dict[str, dict]:
"""Load tasks that already have results with rewards or verifier errors.

Scoreless results whose verifier error is infra-retryable (per
``RetryConfig.should_retry_verifier_error``) are not reused: the
verifier never scored the frozen workspace, so the task re-runs.

Scoped to the current job directory (``_jobs_dir / _job_name``) to
prevent cross-job contamination. When multiple result.json files
exist for the same task (retry artifacts), the newest by mtime wins.
Expand All @@ -1109,6 +1113,17 @@ def _get_completed_tasks(self) -> dict[str, dict]:
completed: dict[str, dict] = {}
for task, (_mt, r) in best.items():
if r.get("verifier_error"):
# A scoreless result whose verifier error is infra-retryable
# (same taxonomy as the within-run retry) records no signal
# about the task; reusing it pins a lost score forever.
if r.get("rewards") is None and (
self._config.retry.should_retry_verifier_error(r["verifier_error"])
):
logger.info(
f"Re-running verifier-errored task on resume: {task} "
f"({truncate_end(r['verifier_error'], 80)})"
)
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Resumed learning tasks run out of order

In sequential-shared resumes, _get_completed_tasks reruns an earlier errored task after later tasks advanced the persisted learner state. The reordered task consumes future skills and corrupts the learning curve.

Prompt for agents
The new resume filtering in src/benchflow/evaluation.py::_get_completed_tasks is safe for parallel-independent jobs but breaks sequential-shared ordering. A sequential run can continue after task B has a retryable verifier error, then complete task C and persist C's learner generation. On resume, B is the only remaining task and runs against C's later learner state. Preserve sequence semantics by either rewinding the learner store and invalidating/rerunning the errored task plus every later task, or by keeping retryable verifier failures terminal for sequential-shared mode. Add a regression test with completed A, retryable-error B, completed C, and a persisted learner snapshot.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed: re-run is gated to parallel-independent; sequential-shared keeps the reuse semantics. Regression test added.

logger.info(
f"Reusing completed verifier-errored task on resume: {task} "
f"({truncate_end(r['verifier_error'], 80)})"
Expand Down
30 changes: 28 additions & 2 deletions tests/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,8 @@ async def test_agent_error_still_retries(self, job_factory):


class TestResume:
def test_verifier_errored_is_complete(self, tmp_path, caplog):
"""Guards the PR #819 fix for issue #542's misleading resume log."""
def test_infra_verifier_errored_reruns(self, tmp_path, caplog):
"""A scoreless infra-retryable verifier error must not pin a lost score."""
task_dir = tmp_path / "task1" / "trial-1"
task_dir.mkdir(parents=True)
(task_dir / "result.json").write_text(
Expand All @@ -505,6 +505,32 @@ def test_verifier_errored_is_complete(self, tmp_path, caplog):
)
from benchflow.evaluation import Evaluation, EvaluationConfig

job = Evaluation(
tasks_dir=tmp_path, jobs_dir=tmp_path, config=EvaluationConfig()
)
with caplog.at_level(logging.INFO):
completed = job._get_completed_tasks()
assert "task1" not in completed
assert any(
"Re-running verifier-errored task" in m for m in caplog.messages
)

def test_contract_verifier_errored_is_complete(self, tmp_path, caplog):
"""Guards the PR #819 fix for issue #542's misleading resume log."""
task_dir = tmp_path / "task1" / "trial-1"
task_dir.mkdir(parents=True)
(task_dir / "result.json").write_text(
json.dumps(
{
"task_name": "task1",
"rewards": None,
"error": None,
"verifier_error": "verifier crashed: No reward file found",
}
)
)
from benchflow.evaluation import Evaluation, EvaluationConfig

job = Evaluation(
tasks_dir=tmp_path, jobs_dir=tmp_path, config=EvaluationConfig()
)
Expand Down