Skip to content

feat(continue): typed continue-gate errors and batch skips instead of silent drops - #1084

Open
JeremyJC67 wants to merge 2 commits into
benchflow-ai:mainfrom
JeremyJC67:feat/continue-triage-and-snapshot-stages
Open

feat(continue): typed continue-gate errors and batch skips instead of silent drops#1084
JeremyJC67 wants to merge 2 commits into
benchflow-ai:mainfrom
JeremyJC67:feat/continue-triage-and-snapshot-stages

Conversation

@JeremyJC67

@JeremyJC67 JeremyJC67 commented Aug 31, 2026

Copy link
Copy Markdown

Continue triage: say why a run can't be resumed, and don't let one unsupported run tax the batch

Implements items 2 and 3 of step 1 in the proposed sequence from #1083. No replay-protocol code is touched.

Why

bench eval continue rejected every non-openhands run with benchflow continue currently supports the 'openhands' agent only. The statement is true but uninformative in three ways, which is why the limitation keeps getting read as "continue only supports open models":

  • it gives no reason, so it reads as a policy about model openness rather than what it is — a wire-protocol constraint at the replay seam;
  • it does not distinguish a temporary limit (this agent has no replay ingress yet) from a permanent one (the run was never recorded, so no ingress could ever help it);
  • in batch mode the affected runs were dropped silently at discovery, so an operator sweeping 200 timed-out runs never learned that part of the sweep was left behind.

What's in scope here

A typed, self-explaining gate. CONTINUE_SUPPORTED_AGENTS is now the single source of truth — a future ingress adds its agent there and nowhere else. ContinueUnsupportedError carries a reason_code and recoverable_in_principle so callers can triage without parsing prose; UnsupportedAgentError and MissingRecordingError are its two cases. Both remain RunFolderError subclasses, so every existing catch site is unaffected.

The message now states the supported set, that the set is derived from the replay wire protocol (the proxy serves POST /v1/chat/completions only; the sandbox receives LLM_BASE_URL/LLM_API_KEY/LLM_MODEL, which only the OpenHands template consumes; another wire would 404 and fall back to host credentials, burning a live run the artifacts would then mislabel as a replay), and whether this particular run is recoverable at all.

That last point required moving the agent gate ahead of trajectory parsing. Previously a run that was both unsupported and unrecorded heard only the missing-artifact complaint. Now a subscription-auth run is told plainly that it can never be continued — before a protocol ingress ships and raises false hope.

Batch continue reports skips instead of scoring them as failures. One correction to the framing in #1083: continue_batch._one already wrapped each run, so a batch never aborted — the real defect was quieter. discover_timeout_run_folders swallowed unsupported runs at discovery with a bare except RunFolderError: continue, so a sweep of 200 with 10 unsupported printed Continuing 190 timeout run(s) and exited 0 with the operator never learning the 10 existed; and anything that did reach the runner was recorded as a plain failure, indistinguishable from a continuation that actually broke.

Now: continue_batch catches the typed error per run and records a skip; summarize_batch reports skipped/skips separately from failed/errors, each skip carrying its reason and recoverable_in_principle; skips do not set the batch's exit status. discover_timeout_run_folders gained an optional on_skip observer (default None; signature and return type unchanged) and continue-batch uses it to surface what discovery drops — only timeout candidates, so a finished run from another agent stays quiet in a mixed jobs dir.

The single-run path is deliberately unchanged: bench eval continue <folder> on an unsupported run still prints the error and exits 1. An explicit request should fail loudly; only a sweep should skip.

Deferred

Testing

10 new tests, each verified red before its change. Full suite 5895 passed / 94 skipped / 7 deselected against a pre-change baseline of 5885 / 94 / 7 — the delta is exactly the new tests. ruff check, ruff format --check src tests tools, and ty check src/ all clean. tests/test_cli_docs_drift.py passes unmodified; no CLI flag was added, so the docs change is prose only.

Closes nothing on its own — #1083 stays open for the protocol work.


Devin Review

JeremyJC67 and others added 2 commits August 31, 2026 15:32
`benchflow continue` rejected any non-openhands run with a bare
"currently supports the 'openhands' agent only". True, but it left the
operator with no way to tell a *temporary* limit (no replay ingress for
this agent yet) from a *permanent* one (the run was never recorded, so
no ingress can ever help it), and no reason for the limit at all — which
is why it keeps being read as "continue only supports open models".

- `CONTINUE_SUPPORTED_AGENTS` is now the single source of truth; a future
  ingress adds its agent there and nowhere else.
- `ContinueUnsupportedError` carries a `reason_code` and
  `recoverable_in_principle`, so callers can triage without parsing prose.
  `UnsupportedAgentError` and `MissingRecordingError` are its two cases.
- The message states the supported set, that the set is derived from the
  replay wire protocol (proxy serves POST /v1/chat/completions; the
  sandbox gets LLM_BASE_URL/LLM_API_KEY/LLM_MODEL, which only the
  OpenHands template consumes), and whether *this* run is recoverable.
- The agent gate moved ahead of trajectory parsing so an unsupported run
  missing `trajectory/llm_trajectory.jsonl` hears the permanent verdict
  (subscription-auth: never continuable) instead of the agent complaint.
- The snapshot alternative is offered conditionally ("if ... was
  captured"), never as a promise.

Refs benchflow-ai#1083 (proposed sequence, step 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZggU3vNYzr3hfDZJUE7jX
…them

`continue-batch` over a mixed sweep silently dropped every non-openhands
timeout run at discovery, then, for anything that slipped past, recorded
it as a plain failure — indistinguishable from a continuation that
actually broke. An operator with 200 timed-out runs learned neither that
part of the sweep was left behind nor why, and the batch's exit status
blamed them for it.

- `BatchContinueResult.skipped` / `.reason_code` /
  `.recoverable_in_principle` carry the gate's typed verdict, built by
  `BatchContinueResult.skip()` from the `ContinueUnsupportedError`.
- `continue_batch` catches that error specifically, so one unsupported
  run costs only itself; the other 199 still run.
- `summarize_batch` reports `skipped`/`skips` apart from `failed`/
  `errors`. Skips do not set the batch's exit status.
- `discover_timeout_run_folders` gained an optional `on_skip` observer,
  and `continue-batch` uses it to surface the runs discovery drops.
  Only timeout *candidates* are reported — a finished run from another
  agent was never a candidate and stays quiet.
- The single-run path is untouched: `bench eval continue <folder>` on an
  unsupported run still prints the error and exits 1.

Refs benchflow-ai#1083 (proposed sequence, step 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZggU3vNYzr3hfDZJUE7jX

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 2 potential issues.

3 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +85 to +87
except ContinueUnsupportedError as exc:
if on_skip is not None and is_timeout_run(folder):
on_skip(folder, exc)

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.

🟡 Limit permits oversized batch summaries

With --limit, candidates collected by on_skip do not consume the cap. The command can report more timeout runs than requested.

Prompt for agents
Make discover_timeout_run_folders apply limit to the complete set of selected timeout candidates, including candidates delivered to on_skip, so continue-batch never summarizes more runs than requested. Preserve deterministic ordering and clarify whether invalid/non-timeout folders consume the cap. Add coverage for unsupported candidates before supported ones and an all-unsupported tree with a small limit.
Devin Review

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

f"missing required artifact: {path} — record-replay needs the LLM "
"trajectory. Was this run captured with usage tracking enabled?"
)
raise MissingRecordingError(path)

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.

🟡 Unusable recordings receive incorrect triage

Empty or wholly malformed trajectories bypass MissingRecordingError, while is_file() marks them recoverable. Runs vanish silently or receive false recovery guidance.

Prompt for agents
Classify recording usability from parsed LLM exchanges rather than file existence alone. Empty or wholly malformed trajectory files need the same typed no-recording verdict as an absent file, including reason_code=no_llm_recording and recoverable_in_principle=false. Ensure unsupported-agent triage can distinguish a usable recording from a merely present file without losing the intended agent-first error, and add discovery/CLI tests for empty and all-malformed files.
Devin Review

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b6e4ef5f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

agent = str(config.get("agent") or result.get("agent") or "")
if agent not in CONTINUE_SUPPORTED_AGENTS:
raise UnsupportedAgentError(
agent=agent, has_recording=trajectory_path.is_file()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Treat unusable trajectory files as missing recordings

When a timed-out run has a zero-byte or entirely malformed llm_trajectory.jsonl, this existence check reports that an unsupported agent has a recoverable recording; for OpenHands, load_llm_exchanges instead raises a plain RunFolderError, which discovery silently swallows. Such files contain no replayable recording and are especially plausible when a run fails before its first model exchange, so they must produce no_llm_recording skips just like an absent file rather than disappearing from the batch summary or receiving an incorrect recovery verdict.

Useful? React with 👍 / 👎.

# Single source of truth: a future replay ingress adds its agent here and
# nowhere else. The membership is *protocol*-derived, not a policy about open
# vs closed model weights — see ``_WHY_PROTOCOL`` below for the mechanism.
CONTINUE_SUPPORTED_AGENTS: frozenset[str] = frozenset({"openhands"})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the supported-agent gate aligned with the orchestrator

If a future replay ingress follows this constant's advertised one-line extension path, load_run_folder will accept the new agent, but build_rollout_config and the sandbox-proxy path in orchestrator.py still hard-code agent="openhands" when constructing the rollout and resolving provider environment. The accepted run would therefore launch OpenHands rather than its recorded agent and could produce a mislabeled continuation; either the orchestrator must dispatch using the accepted agent or support registration must include the corresponding ingress implementation rather than being controlled solely here.

Useful? React with 👍 / 👎.


@pytest.mark.asyncio
async def test_mixed_batch_skips_unsupported_and_continues_the_rest(tmp_path):
"""A batch of mostly-openhands runs continues; the odd ones out are skips."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Name the guarded PR or commit in regression-test docstrings

This newly added regression test, along with the other new tests in test_batch.py and test_run_folder.py, uses behavioral prose or issue #1083 rather than naming the PR or commit it guards. Update each regression-test docstring with the required PR/commit attribution so future maintainers can trace the protected behavior.

AGENTS.md reference: AGENTS.md:L16-L17

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant