Skip to content

prime-runs - #2415

Merged
kcoopermiller merged 33 commits into
mainfrom
cooper/prime-runs
Sep 8, 2026
Merged

kcoopermiller merged 33 commits into
mainfrom
cooper/prime-runs

Conversation

@kcoopermiller

@kcoopermiller kcoopermiller commented Aug 20, 2026 •

Copy link
Copy Markdown
Member

Why

The eval CLI hand-rolled its own platform client: verifiers/v1/utils/platform.py was 317 lines of resolve env → create → post samples → finalize over raw httpx, run once after the last rollout. prime#856 moves that lifecycle into prime-runs, a leaf SDK (httpx + prime-traces) where a run is an object held for the length of the eval rather than three stateless calls at the end of it.

What

platform.py is now ~120 lines of wiring; the SDK owns transport, retries, backpressure and the terminal status:

run = open_run(config, push_state, num_examples=len(tasks))   # before the first rollout; assigns config.run.id

async def on_complete(episode):
    episode.record_run(EvalRunInfo(id=config.run.id, name=config.run.name))
    await append_episode(out, episode, write_lock)
    await asyncio.to_thread(run.log_episodes, [episode])   # streams as rollouts land

await asyncio.to_thread(finish_run, run, episodes, push_state)

Gone: push_traces, credentials, json_bytes, run_metrics, the batching loop, the payload ceiling, the in-memory buffer. trace_to_sample and build_samples moved to prime_runs.projection (knowledge about a platform wire format, not about an eval framework). build_samples stays importable from here because prime-rl's released monitor imports it across the repo boundary; nothing imports trace_to_sample.

file what changed
utils/platform.py open_run / finish_run / abort_run + PushState, the dashboard's read-through view of the run
cli/eval/runner.py both runners open the run before the first rollout, stream to it, and close it out on every exit path; gather_rollouts cancels sibling rollouts on the first failure so nothing keeps uploading into a run that is being closed
cli/eval/main.py drops the end-of-run push
configs/cli/eval.py RunConfig mints no id of its own: open_run assigns the prime-runs handle's id in every mode (the platform's evaluation id online, the SDK's local id otherwise), so the run dir, every trace on disk and the dashboard share one id. Linking uploads to the platform run is the SDK's job regardless (it re-keys every record it sends)
cli/dashboard/eval.py the footer reads the live run: dim Pushing traces (<url>) while it streams, white Traces pushed (<url>), red on failure
tests/v1/conftest.py the E2E fixture sets push=False
serve/client.py EnvClient.close() gathers only the still-pending cancel senders: on Python 3.12+ a gather over finished tasks completes without yielding, so the loop spun at 100% CPU on Ctrl-C once the runner started waiting for rollouts to unwind before teardown

Note

Replace manual HTTP trace pushing with prime-runs SDK in platform.py

  • Replaces the hand-built HTTP upload implementation in platform.py with the prime-runs SDK for opening runs, logging episodes, and closing out runs.
  • Moves platform run lifecycle management into run_eval in runner.py, removing the legacy post-run push_traces call from main.py.
  • Adds gather_rollouts helper to cancel and await all concurrent rollout tasks when any task fails or is cancelled before re-raising.
  • Changes cli.RunConfig.id to be assigned by open_run during execution instead of being eagerly generated as a UUID.
  • Risk: cli.RunConfig.id property now raises if accessed before open_run assigns an ID.

Changes since #2415 opened

  • Added a closing flag to verifiers.v1.cli.eval.runner.run_eval that prevents abort_run from being called once finish_run has been initiated [10fea3a]
  • Updated documentation and logging in verifiers.v1.utils.platform to clarify finish_run semantics and report actual run status [10fea3a]
  • Modified run_eval function in the evaluation runner to shield finish_run execution from cancellation using run_shielded wrapping asyncio.to_thread, removed the closing boolean guard, and updated exception handling to unconditionally invoke abort_run for any exception in the try block [9706c82]
  • Modified abort_run function to return early when run.status is not pr.RunStatus.RUNNING in addition to checking if run.finished is True, and updated the docstring to document that finish() sets run.status as it starts [9706c82]

Macroscope summarized e06c307.


Note

Medium Risk
Changes eval lifecycle, run identity, and platform upload timing across runner and config; failures are mostly best-effort, but code that reads run.id before open_run will now raise.

Overview
Replaces the eval CLI’s hand-rolled httpx upload (create eval → batch samples → finalize after the last rollout) with prime-runs, which owns the run for the whole eval.

run_eval now calls open_run before rollouts, log_episodes as each episode finishes (and for resumed episodes), and finish_run / abort_run on success or failure. The post-run push_traces path in main is removed. gather_rollouts cancels sibling rollouts when one fails so uploads don’t continue into a closing run; close-out runs off the event loop behind run_shielded.

RunConfig.id is no longer a client UUID—it is assigned once by open_run from the SDK handle (platform id online, local id when push is off). PushState and the rich dashboard footer reflect streaming status, partial upload loss, and close-out errors.

Adds prime-runs>=0.1.1 (and lock entries for prime-traces); E2E tests set push=False.

Reviewed by Cursor Bugbot for commit 9706c82. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread verifiers/v1/cli/eval/runner.py Resolved
Comment thread verifiers/v1/cli/dashboard/eval.py Resolved
Comment thread pyproject.toml Resolved
Comment thread verifiers/v1/cli/eval/runner.py Resolved
Comment thread verifiers/v1/cli/eval/runner.py Resolved
kcoopermiller and others added 10 commits August 21, 2026 15:34
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RpNehr64sniVfqKo62GKPw
`run_config()` now carries what the dashboard reads unconditionally — `model`,
`num_examples` (the selected task count, passed in by the runner; -1 for "all"
when unknown) and `rollouts_per_example` — alongside the `exclude_unset` dump,
which drops defaults and so left the evals list showing "Unknown Model" and
"0 examples".

Credentials are stripped producer-side, so nothing sensitive reaches
`metadata`: every value under a `headers` table and any value whose key names
a credential (`api_key`, `Authorization`, `hf_token`; references such as
`api_key_var` are kept) is masked in the structured dump, and the launched
config file is uploaded verbatim only when parsing it (TOML/JSON) finds no
credential-holding key — otherwise, or when it cannot be inspected, it is left
out with a warning naming the offending paths.

`open_run` honours `PRIME_RUNS_MODE=disabled`, which the explicit
`mode="online"` used to override. A run that stays local keeps its own uuid
instead of adopting the SDK's `disabled-…` placeholder. The E2E fixture sets
`push=False`: `run_eval` opens a platform run before the first rollout when a
key is present, and CI has one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyW4j57juAg5cheTX8PmUR
The only places an eval config can carry a credential are a client's
`headers` table and a seat's `harness.env` table: the API key itself is
`api_key_var` (an env-var name) and `forward_env` exists so harness secrets
need not be written down. Mask everything under those two tables and skip
the verbatim `config_source` when the file sets either, instead of guessing
from key names. Drops the credential-key regex, its reference-key
exemption list and the dotted-path scan.

Also widen `_close(summary=)` to `Mapping` so `metrics.RunSummary` (a
TypedDict) type-checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDrFiuDUjdJrv8Qu4oud8C
…on't flip import order

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDEqDAXcNvbJ2GqsWttDhy
…ckouts don't flip import order"

This reverts commit cca4860.
kcoopermiller and others added 3 commits September 1, 2026 14:25
Upload only the three v0 metadata keys the dashboard reads (model,
num_examples, rollouts_per_example), as the pre-SDK push did. The
exclude_unset config dump and the launched file (config_source) are
deferred to a follow-up: both can carry credentials (client.headers,
harness.env), and the masking that made them safe was most of this
module. This also keeps metadata.env a string, so the evals list no
longer needs platform#4740 before this can ship.

Removed: scrub_secrets, credential_tables and their helpers,
RunConfig.source/record_source, and config_file_ref.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bpEF1rKjCijybTYmfTGw2
Shorter docstrings and comments throughout; the PushState footer no
longer reports per-sink losses (the SDK logs them); PushState is always
constructed so the platform helpers take it unconditionally; run_config
is inlined into open_run with num_examples required; build_samples is
re-exported with a noqa instead of __all__.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bpEF1rKjCijybTYmfTGw2
close() waited for in-flight cancel senders with
`while self._cancel_tasks: await gather(*self._cancel_tasks)`. On 3.12+
a gather over tasks that are all already done completes eagerly without
yielding to the loop, so the discard callbacks that empty the set never
run and the coroutine spins at 100% CPU. It was latent on main, where
close() is reached while the senders are still pending; the prime-runs
integration waits for the rollouts to unwind first, so every Ctrl-C on a
served eval hung. Gather only the pending tasks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bpEF1rKjCijybTYmfTGw2
Comment thread verifiers/v1/configs/cli/eval.py Resolved
`adopt_id` existed only because prime-runs kept a record's own `run` block,
so the platform's id had to be written into every episode from here. The SDK
now sets `run.id`/`run.type` on the uploaded copy itself (keeping `name`), so
`RunConfig.id` stays the local uuid in every mode and nothing in the eval CLI
learns the platform's id. Resumed episodes are uploaded as they are on disk.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHMjWstGWd1oGKjoj5RgeT
The run no longer mints a uuid of its own. `open_run` gives `RunConfig` the
SDK run's id in every mode (the platform's evaluation id online, the SDK's
local id otherwise), so the run dir, every trace on disk and the dashboard
share one id. Linking uploads to the platform run stays the SDK's job (it
re-keys every record it sends); this only decides what the local records say.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHMjWstGWd1oGKjoj5RgeT
kcoopermiller added a commit to PrimeIntellect-ai/prime that referenced this pull request Sep 4, 2026
## Why

Eval and training jobs need one call that opens a run on the platform, streams records while they execute, and marks the run finished, failed, cancelled, or crashed when they stop. verifiers and prime-rl each hand-roll that lifecycle against the raw API today — two copies of the same thing — and nobody uploads the config a run was actually launched with, which is why the Config tab shows a v0 blob instead of anyone's `eval.toml`.

## What

A new leaf package, `prime-runs` (`httpx` + `prime-traces`; `pyarrow` behind a `train` extra), where the run is an object:

```python
import prime_runs as pr

run = pr.init(name="gsm8k-qwen3-8b", environments=["gsm8k"],
              model=config.model, framework="verifiers",
              config="eval.toml")              # stored byte-for-byte
print(run.url)

for episode in rollouts:
    run.log_episodes([episode])               # a queue put; streams on a background thread

run.finish(summary=pr.metrics.from_episodes(episodes))
```

Training runs use the same handle over the RFT external-runs API (folded in from #873):

```python
run = pr.init(kind="train", model=base_model, environments=[env_id],
              training=pr.TrainingSpec(max_steps=1000, batch_size=64),
              config=cfg.model_dump(), team_id=team)   # or id=os.environ["RUN_ID"] to attach
run.log_episodes(step_episodes)        # every 10th step -> Parquet presign / PUT / confirm
run.log_metrics(metrics, step=step)    # POST /rft/metrics, on its own uploader
run.finish()                           # idempotent finalize; failed/cancelled/crashed -> status PUT
```

`Run` exposes `id`, `url`, `kind`, `log_episodes()` / `log_traces()` / `log_metrics()`, `update_summary()`, `flush()`, `finish()`, `fail()`, a context manager (an exception → `failed`, Ctrl-C → `cancelled`, an exit without `finish()` → `crashed` via atexit), and loss counters (`dropped_records`, `failed_records`, `errors`).

Records are keyed to the run at upload: the traces sink sets `run.id` / `run.type` on the uploaded copy of every trace and episode (member traces included) and keeps the producer's `name` / `work`, so verifiers and prime-rl keep their own local run ids and never learn the platform's.

Every record is written to two places during the transition: Prime Traces (the system of record; an account outside the beta has that sink retire quietly) and the table today's viewer reads — the v0 sample table for evals, per-step Parquet objects for training. Once the viewers read traces natively, the legacy sink is one line to drop and no producer changes.

How it degrades: queued batches coalesce so the request rate tracks upload latency; a full queue drops (counted) rather than stalling the run; a transient failure costs its batch and three in a row retire the sink — a training run pauses it for five minutes instead, since it outlives any platform deploy; non-finite numbers become `null` in the sample tables rather than losing the row. `POST /evaluations/`, `/rft/external-runs` and `/samples` are never replayed after an ambiguous failure (a retry could duplicate); presign, confirm, `/rft/metrics` (one row per step server-side) and `/rft/finalize` are.

Integrations: [verifiers#2415](PrimeIntellect-ai/verifiers#2415) (eval), [prime-rl#3459](PrimeIntellect-ai/prime-rl#3459) (training).

## Known gaps

- **No producer-facing way to mark an evaluation FAILED** in the API as deployed today: `finalize` only moves PROCESSING → COMPLETED, so a crashed or interrupted eval stays RUNNING on the dashboard. The SDK records status/error under `metadata.prime_runs` and warns. platform#4740 adds `PUT /evaluations/{id} {status: FAILED|CANCELLED, error_message}` with a 409 guard; once deployed, `EvalsBackend.finalize` should use it and keep the metadata fallback for older backends (`RunStatus.CANCELLED` already exists for that).
- **`config_source` is stored, not yet shown.** `init(config=<path>)` keeps the launched file byte-for-byte under `metadata.config_source` (`run_config.config_source` for training). The eval Config tab renders it once platform#4740 lands; verifiers#2415 sends only the three v0 keys for now and adds the file in a follow-up, and the training Config tab needs the same branch.
- **Attach targets external runs.** `init(kind="train", id=...)` works for runs created through `POST /rft/external-runs`; the v1 monitoring endpoints answer 400 for a hosted run's id (the same endpoints prime-rl's old monitor used, so nothing regresses).
- **Training samples are keyed by dispatch step.** Objects are additive per step on the platform, so an off-policy episode landing in a later batch adds an object for its dispatch step; each object numbers its `sample_id`s in its own range (a forked child in a random band). "Step N" in the sample viewer means dispatched at N, while the metrics at N describe the batch trained at N.

## Before merge

1. ~~prime-traces 0.0.3 on PyPI (#872), then `prime-traces>=0.0.3` here.~~ Done.
2. A PyPI **pending publisher** for `prime-runs` (owner `PrimeIntellect-ai`, repo `prime`, workflow `release-runs.yml`, environment `pypi-prod`). The name is unclaimed; the first run of the release workflow creates the project. The workflow publishes before it tags, so a failed first publish leaves no tag and re-runs from `main` via `workflow_dispatch`.
3. ~~Coalesce queued sample batches, add `RunStatus.CANCELLED`, fold #873 in so 0.1.0 carries the training backend.~~ Done.
4. Squash-merge (`main` requires linear history); `release-runs.yml` publishes 0.1.0 with eval + train. verifiers#2415 and prime-rl#3459 then drop their `[tool.uv.sources]` git pins and relock.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> New client code talks to production eval/RFT/traces APIs with nuanced retry and upload semantics; mistakes could duplicate runs, drop samples, or mis-report terminal status, though behavior is heavily tested and defaults to warn-not-raise.
> 
> **Overview**
> Introduces **`prime-runs`**, a new leaf Python package (`httpx` + `prime-traces`, optional `pyarrow` for training) that centralizes platform run lifecycle: **`init()`** registers or attaches to a run, **`log_episodes` / `log_traces` / `log_metrics`** stream records on background uploaders, and **`finish()`** drains queues and closes the run (with context-manager, atexit crash reporting, and fork-safe HTTP/traces clients).
> 
> **Eval** runs use **`EvalsBackend`** (`/evaluations`, hub environment resolve) and dual sinks to Prime Traces plus the legacy v0 sample table; **training** runs use **`RftBackend`** (`/rft/external-runs`, attach via `id=`), Parquet sample uploads on a step cadence, and a separate metrics uploader. The SDK stamps platform **`run.id` / `run.type`** on records, stores launch **`config_source`** verbatim, and implements deliberate retry/idempotency (no replay on ambiguous `POST` creates or append-only samples).
> 
> **Repo wiring:** CI gains **`test-runs`** (Python 3.11–3.13) and version-bump enforcement for `packages/prime-runs`; **`release-runs.yml`** publishes to PyPI and tags `prime-runs-v*`. Hermetic tests cover HTTP, backends, sinks, projection, and end-to-end `init()` flows.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b65b9a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYdQmKAVnGteT1tkimJb3g
kcoopermiller and others added 2 commits September 3, 2026 19:49
prime-runs 0.1.0 is published (prime#856 → prime-runs-v0.1.0), so the
TEMPORARY git source is gone; `prime-runs>=0.1.0` now resolves from PyPI
and the lock moves prime-traces from the git 0.0.2 to PyPI 0.0.3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYdQmKAVnGteT1tkimJb3g
@kcoopermiller
kcoopermiller marked this pull request as ready for review September 4, 2026 02:58
Comment thread verifiers/v1/utils/platform.py Resolved
@kcoopermiller

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-04T03:10:32.515472Z 9955f74 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread verifiers/v1/cli/eval/runner.py Resolved
@macroscopeapp

macroscopeapp Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new Prime Runs integration and changes default evaluation behavior by opening, streaming, and finalizing platform runs throughout execution. The external side effects, lifecycle/identity changes, and unresolved run-ID ownership design question warrant human review.

You can add or adjust custom eligibility rules. Learn more.

@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: 9955f74c4b

ℹ️ 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".

Comment thread verifiers/v1/cli/eval/runner.py Resolved
Comment thread verifiers/v1/cli/eval/runner.py Resolved
Comment thread pyproject.toml Resolved
…ffort

- `abort_run` maps Ctrl-C / task cancellation to `RunStatus.CANCELLED`
  (was CRASHED), matching the SDK's own `__exit__` and the platform's
  producer-closable vocabulary.
- `log_episodes` wraps `run.log_episodes` so the hand-off is best effort
  like `open_run` and `_close` already are: the SDK keeps upload failures
  on its uploader thread under `on_error="warn"`, so this only guards the
  call itself, but a platform problem must never fail a rollout or leave
  the run open. Both runner call sites (resume replay, `on_complete`) use it.

Verified live: a two-task eval interrupted mid-rollout closes out
`cancelled` in ~4 s; one interrupted after the last rollout stays
`completed` with both samples.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYdQmKAVnGteT1tkimJb3g
Comment thread verifiers/v1/utils/platform.py Resolved
`_close` used to leave `PushState.error` unset whenever `run.finish()`
returned, so the footer read `Traces pushed` even when the SDK's uploader
had dropped records (queue overrun) or a sink had failed a batch. Read
`run.failed_records` / `run.dropped_records` after the close-out into
`PushState.incomplete`, log it, and render it as a red suffix on the
`Traces pushed` line: the run exists and holds what did land. A sink that
switched itself off quietly (Prime Traces outside the beta) is not counted
by the SDK and so does not redden the footer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYdQmKAVnGteT1tkimJb3g
Picks up prime-runs 0.1.1 (prime#896): the platform client normalises only
the URL path when recognising an API root and sends its auth headers per
request as well as on the pool. No behaviour change for the eval path; the
relock moves nothing else.

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

Copy link
Copy Markdown
Member Author

Re-triggering CI: GitHub dropped the workflow trigger for 65be671 (only CodeQL ran). Reopening immediately.

Resolves `verifiers/v1/serve/client.py` in main's favour: #2518 fixes the
same eager-gather spin in `EnvClient.close()` that this branch had patched
(pop each cancel task before awaiting it), so the branch's hunk is dropped.
`uv lock` re-run on the merged tree; prime-runs stays 0.1.1, prime-traces
0.0.3, and main's Python 3.14 bump carries through.

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

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e06c307. Configure here.

Comment thread verifiers/v1/cli/eval/runner.py Resolved
Once `finish_run` has started, a Ctrl-C cancels only the runner's await;
the worker thread completes the run regardless, and the SDK serialises
`finish()` so the first caller decides the status. Routing that interrupt
through `abort_run` started a second, no-op `finish()` that then logged
`--push: cancelled` for a run that had completed. The runner now skips
`abort_run` once close-out has begun, and `_close` logs the run's own
status rather than the one it asked for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYdQmKAVnGteT1tkimJb3g
Comment thread verifiers/v1/cli/eval/runner.py Resolved
A Ctrl-C while the close-out was still queued on the executor could cancel
it before it ran: `closing` had already been set, so `abort_run` was skipped
too and the run was left for the atexit hook to report as crashed. The
close-out now runs under `run_shielded`, which awaits it to completion and
re-raises the cancellation after, so the flag goes.

verifiers' first Ctrl-C raises KeyboardInterrupt inside the loop rather than
cancelling the task, so `abort_run` can still be reached while `finish_run`
is mid-flight on its thread. The SDK serialises `finish()` and sets
`run.status` as one starts; `abort_run` now stands down for a run whose
status is no longer running, instead of adding a no-op second finish and a
duplicate log line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYdQmKAVnGteT1tkimJb3g
@kcoopermiller
kcoopermiller requested a review from samsja September 8, 2026 18:27
@kcoopermiller
kcoopermiller merged commit 392d596 into main Sep 8, 2026
14 checks passed
@kcoopermiller
kcoopermiller deleted the cooper/prime-runs branch September 8, 2026 23:05
mikasenghaas added a commit that referenced this pull request Sep 14, 2026
Main moved the eval CLI's platform upload onto prime-runs (#2415, #2569,
#2577) and taught the runner `run.attach`. All of that lives in files this
branch deletes, so the merge keeps the deletions and drops the new
`tests/v1/test_platform_run.py` with them. Nothing in verifiers imports
`prime_runs` any more, so the dependency goes too; prime-rl depends on it
directly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

3 participants