Support external harness - #1985
Conversation
ApprovabilityVerdict: Needs human review Unable to check for correctness in 80ab00a. This PR introduces new external harness support with significant runtime lifecycle changes. Three unresolved medium-severity bugs were identified in the review comments affecting the new stop_confirmed() methods - including lifecycle state not being reset on restart and potential resource leaks in Modal cleanup paths. You can customize Macroscope's approvability policy. Learn more. |
2bd29f2 to
cf6e274
Compare
|
@samsja This is ready for human review at All required checks pass on Python 3.11 through 3.13, Ruff, Ty, Environments, CodeQL, and Cursor. The change remains provider-generic and adds bounded confirmed teardown, hard runtime lifetime propagation, provider-ID preservation, and Prime idle-timeout validation. Research-environments #678 is the downstream consumer and is also green. No sandbox or evaluation was launched. |
|
Follow-up |
|
Friendly follow-up @samsja: this remains automated-review green at |
|
@samsja Review update for current head
Please review the current head when available. No sandbox, model request, or evaluation was launched. |
stop_confirmed raised on 404 when a Prime sandbox was already removed by idle timeout, max lifetime, or earlier best-effort cleanup, leaving _confirmed_stop_id unset so every retry kept failing. Now a 404 is treated as confirmed success; other delete errors still propagate. Addresses Cursor Bugbot high-severity finding on PR #1985.
Resolve conflict in verifiers/v1/loaders.py by keeping the branch's robust external plugin loading (dual-target import with dependency error filtering) and merging main's default→bash rename hint and builtin_harness_ids hint. Update test_loaders_external_plugins.py for main's config API (taskset on env, harness on agent, Environment→Env).
stop_confirmed now checks isinstance(exc, APIError) with an 'HTTP 404' prefix, not a loose '404' substring, so unrelated errors can't be misread as confirmed deletion. Add regression tests for non-APIError and non-404 APIError rejection. Add the timeout field to PrimeConfig and ModalConfig in the evaluate-environments skill reference, addressing the Bugbot finding about omitted config contract fields.
…t runtime lifetime stop_confirmed in PrimeRuntime and ModalRuntime now set self.stopped after confirmed teardown, matching the base stop() path, so borrow checks reject a dead runtime and no new rollout is placed into an already-deleted sandbox. cap_remote_harness_timeout now caps at the runtime's configured timeout (via getattr fallback to 24h), not a fixed 24h, so a shorter hard lifetime correctly bounds the harness timeout. Add regression tests for both fixes.
…ider ID When PrimeRuntime.start fails before assigning info.id, the live _client remains set. stop_confirmed previously raised without closing it, leaking the client. Now the client is released before raising, matching teardown's cleanup path. Add regression test.
…etime Modal truncates the hard timeout to int() before sending it to the provider, but cap_remote_harness_timeout compared against the full float. A fractional lifetime could leave the harness limit above the sandbox's actual lifetime. Now the cap uses int() to match the provider's truncation. Add regression test.
| **{k: v for k, v in options.items() if v is not None}, | ||
| ) | ||
| ) | ||
| self._confirmed_stop_id = None |
There was a problem hiding this comment.
🟡 Medium runtimes/prime.py:165
start() resets self._confirmed_stop_id to None when provisioning a new sandbox but does not reset self.stopped, which stop_confirmed() sets to True. After a restart of a PrimeRuntime that was previously confirmed-stopped, a new sandbox is created and running, yet self.stopped remains True, so any lifecycle check that gates on that flag incorrectly treats the live sandbox as dead. Reset self.stopped = False next to the _confirmed_stop_id reset.
| self._confirmed_stop_id = None | |
| self._confirmed_stop_id = None | |
| self.stopped = False |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/runtimes/prime.py around line 165:
`start()` resets `self._confirmed_stop_id` to `None` when provisioning a new sandbox but does not reset `self.stopped`, which `stop_confirmed()` sets to `True`. After a restart of a `PrimeRuntime` that was previously confirmed-stopped, a new sandbox is created and running, yet `self.stopped` remains `True`, so any lifecycle check that gates on that flag incorrectly treats the live sandbox as dead. Reset `self.stopped = False` next to the `_confirmed_stop_id` reset.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2e0103f. Configure here.
| **{k: v for k, v in options.items() if v is not None}, | ||
| ) | ||
| ) | ||
| self._confirmed_stop_id = None |
There was a problem hiding this comment.
Restart leaves runtime unborrowable
Medium Severity
start clears _confirmed_stop_id after a successful create so a runtime can be restarted after stop_confirmed, but it never resets stopped to False. A restarted box therefore stays permanently rejected by borrow checks even though a live sandbox was provisioned again.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2e0103f. Configure here.
| if runtime_id is None: | ||
| raise RuntimeError( | ||
| "modal sandbox termination cannot be confirmed without a provider ID" | ||
| ) |
There was a problem hiding this comment.
Modal skips terminate without ID
Medium Severity
When Modal still has a live _sandbox handle but info.id is missing, stop_confirmed raises without calling terminate or releasing the handle. Controllers that only use the confirmed path then cannot clean up a sandbox they already hold a handle to, so the remote resource leaks until max lifetime.
Reviewed by Cursor Bugbot for commit 2e0103f. Configure here.


Summary
__all__export rule, and specialized config loadingEvalConfigandEnvironmentconstructionstop_confirmed()calls idempotent while preserving fail-closed missing-handle states and retryability after provider failureThis stays generic. It contains no Emulator Bench or private harness implementation.
Validation
uv sync --frozenPRIME_API_KEYuv run --no-sync ruff check .uv run --no-sync ruff format --check .uv run --no-sync ty check verifiersd6d52d7c418cbd297fcdfa09e1779a6586d29814, patch SHA-256c7d84fab007e74c08d71dbbcdbefb1c12b5b2402991869d417fce448617de4a9Scope
The obsolete unrelated MMMU skip was removed while refreshing this branch onto current
main.Note
Medium Risk
Prime CLI now defaults eval dispatch through the v1 bridge (behavior change for all Prime eval invocations), plus remote sandbox deletion and timeout semantics that affect rollout lifecycle.
Overview
Adds external v1 plugin loading for tasksets, harnesses, and judges: the loader tries built-in namespaced imports first, then top-level installed packages, surfaces real dependency errors from a found plugin, and documents the packaging ABI (
__all__, ID normalization, typed config).Introduces
eval_v1_primeas the Prime CLI eval entrypoint. v1 TOMLs with[env.taskset]route toeval @ config.tomlafter stripping Prime-injected v0 flags; other targets still use legacy eval. Unsupported bridge args fail closed.Prime and Modal gain configurable hard
timeout(60–86400s), stricteridle_timeoutvalidation on Prime, provider forwarding of lifetime limits, andstop_confirmed()for fail-closed teardown (404-as-gone on Prime, idempotent success,stopped=True). Harness timeouts are capped against the runtime's configured timeout instead of a fixed 24h.Docs and tests cover Prime v1 eval usage, external harness packaging, loader regressions, compile capping, and runtime stop behavior.
Reviewed by Cursor Bugbot for commit 80ab00a. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add external harness support and v1 eval bridge for the Prime CLI plugin
.tomlfiles containing[env.taskset]to the v1 evaluator and falling back to the legacy v0 path; unknown flags fail closed.stop_confirmedto Prime and Modal runtimes with idempotency, 404-as-success handling for Prime, and cleanup guarantees.timeoutfield (60–86400 s) toPrimeConfigandModalConfig; sandbox creation now uses this value instead of a fixed 24 h ceiling, and harness timeouts are capped against it.Macroscope summarized 80ab00a.