Repository navigation
perf: encode json with orjson in prime-runs and prime-traces - #1109
Conversation
Trace records, sample batches and request bodies now go through orjson instead of the stdlib encoder. Records may hold numpy arrays, so a caller like verifiers can hand over its per-token streams without first copying them into Python lists. orjson has no allow_nan=False: NaN and infinity now go out as null instead of raising. The sample projections already nulled them; trace records and request bodies now match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1df533bee2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1df533b. Configure here.
Episode.to_record() returns its per-token streams as numpy arrays, and the training viewer table embeds the record in the info column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The config file read, the upload metadata part and the tests' decoding handle small payloads, where orjson gains nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…prime#1109 The SDK encodes the numpy record streams with orjson from #1109 on. TODO: replace the git pin with the PyPI release asap and raise the prime-runs minimum to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ime main Verifiers moves to verifiers#2980 merged with verifiers main (4ae941f9c). prime-runs and prime-traces come from prime main at PrimeIntellect-ai/prime#1109's merge commit until a release has it. TODO: replace the git pins with the PyPI release asap and raise the prime-runs minimum. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Summary - `Episode.to_record()` and `Trace.to_record()` return the per-token node streams as C-contiguous numpy arrays. That covers `token_ids`, `mask`, `is_content`, the rounded float streams and `loss_weights`. - Floats are float64, and the rounded streams use `np.round`. - orjson with `OPT_SERIALIZE_NUMPY` writes each stream in one pass. - Before, each stream became a Python list, each float went through `round()`, and pydantic serialized every element. - `to_record()` dumps the model without the streams, then adds them as arrays. Pydantic's JSON mode cannot emit numpy arrays, and its own encoder (`model_dump_json`) is 3.5x slower on real episodes (see Verification). - The streams now come last in each node record. - Add `Episode.to_json()` and `Trace.to_json()`: `to_record()` encoded with orjson, as compact UTF-8 bytes. The pair mirrors pydantic's `model_dump`/`model_dump_json`, which stay unchanged. - `to_record()` and `to_json()` take `exclude`, a pydantic exclude spec merged with the record's own excludes. The per-token streams are always included. - JSON-mode dumps (`model_dump(mode="json")`, `model_dump_json`) round with `np.round` instead of a per-value `round()`. The output is the same. - The agentic and best-of-n judge envs write the trace file with `to_json(exclude=...)`, which drops `ok` and the hidden reasoning fields. Before, they edited the record dict. Add `orjson` as a dependency. - `vf-eval --push` keeps passing the episodes to the prime SDK. The SDK encodes them with orjson from PrimeIntellect-ai/prime#1109 on. prime-rl counterpart: PrimeIntellect-ai/prime-rl#4257. In a replayed batch with PrimeIntellect-ai/prime#1109, the ship under monitor contention drops from 1016 s to 114 s. **Pinned until release:** PrimeIntellect-ai/prime#1109 is merged but not released yet, so `prime-runs` (and `prime-traces` through `override-dependencies`) are pinned to its merge commit on prime main (`adf8d25`), with a TODO to replace them with the PyPI release asap and raise the `prime-runs` minimum. The pins only apply to this repo's lock; consumers need the release. ## Breaking - `to_record()` node streams are numpy arrays, not lists. `json.dumps(record)` with the stdlib `json` raises `TypeError`. - Migration: encode with orjson and `OPT_SERIALIZE_NUMPY`. - Prime SDK releases before PrimeIntellect-ai/prime#1109 cannot encode `to_record()`, so their uploads fail with `TypeError`. - Migration: upgrade `prime-runs`. ## Verification Real data: one 2,048-episode training batch with 193M tokens. Each step below ran single-threaded over the whole batch. | Per batch | Before | After | |---|---|---| | `to_record()` + orjson (trace-file write) | 103.3 s (50.4 ms/episode) | 18.1 s (8.8 ms/episode) | A second check ran on 256 episodes of the same batch: | Per episode | Time | |---|---| | `to_record()` + orjson (this branch) | 13.1 ms | | `model_dump_json` with the same excludes | 47.9 ms | | `model_dump(mode="json")` + orjson | 46.3 ms | - All three paths produce the same parsed content for each episode. - New tests: - the record arrays and `to_json()` have the content of the plain JSON dump, for traces and episodes, with and without rounding; - `exclude` merges with the record's own excludes and keeps the streams; - `round_stream` matches `round()` on 100k float32 values. - The judges' trace files parse to the same content as the old edit-then-encode path, for 32 real episodes. - `tests/unit`: all passed (Docker, `PRIME_API_KEY` and harbor tests skipped). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the trace/episode serialization contract (NumPy in records, bytes from `to_json`) and pins Prime packages from git, which affects eval uploads and any code that assumed list-shaped streams or stdlib JSON. > > **Overview** > **Trace and episode JSON records** now attach per-token fields (`token_ids`, `mask`, `is_content`, float training streams, `loss_weights`) as **NumPy arrays** instead of Python lists. `to_record()` dumps the model without those streams, then merges them via `add_record_streams` / `MessageNode.record_streams`; float streams use **`np.round`** (`round_stream`) at the configured precision. > > Adds **`Trace.to_json()`** and **`Episode.to_json()`** — compact UTF-8 bytes from `orjson` with `OPT_SERIALIZE_NUMPY`. Both `to_record` and `to_json` accept an optional **`exclude`** merged with `RECORD_DUMP_EXCLUDE` via **`merge_exclude`** (judge envs use this to drop `ok` and hidden reasoning instead of mutating dicts). > > **Dependencies:** runtime **`orjson`**, and temporary **git pins** for `prime-runs` / `prime-traces` until a PyPI release ships prime#1109-compatible encoding. > > **Breaking:** `to_record()` dicts are no longer `json.dumps`-safe; callers must use `to_json` or orjson with NumPy support. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4ae941f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- Macroscope's pull request summary starts here --> <!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. --> <!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. --> > [!NOTE] > ### Keep record streams as NumPy arrays and encode records with orjson > - `Trace.to_record` and `Episode.to_record` now attach node streams (token IDs, mask, content flags, rounded float streams, loss weights) as NumPy arrays, with float streams rounded per `float_decimals` via `round_stream` in [graph.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2980/files#diff-64c9d635eb215902a512c9e8ab316fd9b9967c36db03d4049aacc812cdfbd740) > - `Trace.to_json` and `Episode.to_json` encode records with orjson's NumPy option; NaN and infinity become `null` > - `to_record` accepts an `exclude` argument merged with built-in record exclusions via `merge_exclude` in [trace.py](https://github.com/PrimeIntellect-ai/verifiers/pull/2980/files#diff-834820c3aa80d87ee1c1c57fb54c74f09c66ac727036d964e6005c8613c00b5e); `JudgeTask.from_trace` and `RankTask.record` now use `Trace.to_json` with exclusions instead of mutating records and using stdlib JSON > - Adds `orjson>=3.10.0` as a dependency in [pyproject.toml](https://github.com/PrimeIntellect-ai/verifiers/pull/2980/files#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711) > - Behavioral Change: JSON output format changes — NaN/inf encode as `null`, and records contain arrays instead of lists for stream fields; check consumers of `to_json`/`to_record` output > > <!-- Macroscope's review summary starts here --> > > <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 4ae941f.</sup> > <!-- Macroscope's review summary ends here --> > <!-- Macroscope's pull request summary ends here --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary - Bump verifiers to PrimeIntellect-ai/verifiers#2980. With it, `Episode.to_record()` returns the per-token node streams as numpy arrays. - The file monitor's orjson (`OPT_SERIALIZE_NUMPY`) writes them in one pass, so the file monitor needs no code change. - The bump adds `orjson` to the verifiers dependencies. `uv.lock` is relocked for it. - The prime monitors keep passing the episodes to the SDK. From PrimeIntellect-ai/prime#1109 on, the SDK encodes the numpy streams with orjson. - `test_episode_records.py` checks the upload path with the SDK's own encoders: the trace line, the samples batch and the training viewer table. **Pinned until release:** PrimeIntellect-ai/prime#1109 is merged but not released yet, so `prime-runs` and `prime-traces` are pinned to its merge commit on prime main (`adf8d25`), with a TODO to replace the pins with the PyPI release and raise the `prime-runs` minimum. `deps/verifiers` is on verifiers main with PrimeIntellect-ai/verifiers#2980 (f166cd5). With the released SDK, the prime monitors' uploads fail with `TypeError`. ## Verification Real data: one 2,048-episode training batch with 193M tokens, replayed through the real `TrainSink` and `Orchestrator.finalize_train_batch` with a no-op sender. | | Before | After | |---|---|---| | File monitor serialization, single-threaded, per batch | 103.3 s | 18.1 s | | `FileMonitor.log_episodes` during arrival, per batch | 113.4 s | 19.9 s | | Ship with the file and prime serializers running in background threads | 1015.7 s | 114.2 s | - Ship time runs from taking the batch to shipping it. The last row is a worst case: each monitor's real per-episode serializer loops in its own thread, as with a backlog. These threads hold the GIL in long C calls and starve the ship's pure-Python steps. Without them, the ship on this base takes ~42 s. - In the last row, before is `main` with the released SDK, and after is this stack with PrimeIntellect-ai/prime#1109. GC time in the ship drops from 103.7 s to 0.6 s, and the longest pause from 4.52 s to 0.01 s. - The prime serializer thread still costs 147 ms per episode after. Of that, 12 ms is the encode and the rest is the SDK's level-9 gzip, which releases the GIL. - The prime SDK trace line drops from 114.7 ms per episode (`main` and the released SDK) to 12.2 ms (this stack with PrimeIntellect-ai/prime#1109) on 512 episodes, with the same content. - `pytest tests/unit/orchestrator tests/unit/utils tests/unit/train/sft/test_prime_monitor.py tests/unit/test_dashboard_artifacts.py` with the PrimeIntellect-ai/prime#1109 SDK: 181 passed. With the released SDK, `test_received_episode_uploads_plain_json` fails with `TypeError: Object of type ndarray is not JSON serializable`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Temporary git pins on core upload/training SDK packages change resolution and reproducibility until PyPI catches up; behavior change is limited to serialization of episode traces but affects all prime monitor uploads. > > **Overview** > **Pins `prime-runs` and `prime-traces` to the Prime main commit that ships PrimeIntellect-ai/prime#1109** (orjson-based trace/record encoding for numpy per-token streams), via `tool.uv.sources`, an override on `prime-traces`, and a refreshed `uv.lock`. TODOs call out swapping these git pins for the next PyPI releases. > > **Aligns upload-path tests** in `test_episode_records.py` with the SDK: trace lines use `prime_traces._encode_record`, and sample JSON uses `prime_runs._http.encode_json`, with an explicit check that encoded `token_ids` remain plain JSON lists—not the old `json.dumps` / `to_mapping` stand-ins. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d5b8db3. 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 Opus 5.5 <noreply@anthropic.com>
## Summary Replaces the git pins added in #2980 with the PyPI release of PrimeIntellect-ai/prime#1109 (orjson-encoded records and numpy streams). That release is PrimeIntellect-ai/prime#1126, which ships `prime-runs` 0.1.5 and `prime-traces` 0.0.11. Both are now on PyPI. `uv.lock` moves `prime-runs` and `prime-traces` from the `adf8d25c` git sources to PyPI 0.1.5 / 0.0.11. No other package moves. The lockfile also changes from `revision = 4` to `5`, because uv 0.13 writes `default-groups` into it. ## Changes - `prime-runs>=0.1.4` → `prime-runs>=0.1.5`. - Removes the `prime-runs` git source in `[tool.uv.sources]`, pinned to `adf8d25c`. - Removes the `prime-traces` git override in `override-dependencies`, pinned to the same commit. `prime-runs` 0.1.5 requires `prime-traces>=0.0.11`, so verifiers no longer needs to pin it. - Adds `prime-traces = false` to `[tool.uv.exclude-newer-package]`. Without it, the 7-day `exclude-newer` cooldown hides `prime-traces` 0.0.11 and the lock fails to resolve. `prime-runs` is already exempt. ## Checks - In a fresh venv, `pip install prime-runs==0.1.5` resolves `prime-traces` 0.0.11. - `uv sync` installs `prime-runs` 0.1.5 and `prime-traces` 0.0.11. - `ruff check .` passes. - `tests/unit`: 243 passed, 10 skipped. The skips need `harbor` or `PRIME_API_KEY`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- Macroscope's pull request summary starts here --> <!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. --> <!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. --> > [!NOTE] > ### Require prime-runs 0.1.5 from PyPI > Bumps the minimum `prime-runs` version to 0.1.5 in [pyproject.toml](https://github.com/PrimeIntellect-ai/verifiers/pull/3001/files#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711) and removes the uv sources and override-dependencies entries that pulled `prime-runs` and `prime-traces` from specific Git revisions. Adds `prime-traces = false` to `tool.uv.exclude-newer-package` so the cutoff does not apply to it. Behavioral Change: both packages now resolve from PyPI instead of pinned Git commits. > > <!-- Macroscope's changelog starts here --> > #### Changes since #3001 opened > > - Updated the dependency lock to require `prime-runs` version 0.1.5 from PyPI. [2d3f59a] > <!-- Macroscope's changelog ends here --> > > <!-- Macroscope's review summary starts here --> > > <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 1b749f4.</sup> > <!-- Macroscope's review summary ends here --> > <!-- Macroscope's pull request summary ends here --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Dependency and lockfile-only change with no application code edits; behavior shifts from pinned git commits to released PyPI versions already exercised via the prior pins. > > **Overview** > This PR **drops temporary git pins** for `prime-runs` and `prime-traces` and **consumes the published PyPI releases** instead (`prime-runs` **0.1.5**, `prime-traces` **0.0.11** via `prime-runs`' dependency). > > **`pyproject.toml`** raises the minimum to `prime-runs>=0.1.5`, removes the `prime-runs` git entry under `[tool.uv.sources]`, removes the `prime-traces` git `override-dependencies` block (and related TODO comments), and adds **`prime-traces = false`** to `[tool.uv.exclude-newer-package]` so the 7-day `exclude-newer` window does not block resolving the new trace package. > > **`uv.lock`** reflects PyPI sources for both packages, drops the manifest override for `prime-traces`, and bumps lock **revision** to 5 (uv writing `default-groups` on the root package). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2d3f59a. 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 Opus 5.5 <noreply@anthropic.com>
## Summary - Bump `prime-sandboxes` 0.5.1 → 0.5.3 so live sandbox processes are no longer capped at 24h (via PrimeIntellect-ai/verifiers#3043). - Bump `deps/verifiers` `60290d4` → `bccc2c5` (verifiers main, with PrimeIntellect-ai/verifiers#3043 merged). - Require `prime-runs[train]>=0.1.5` from PyPI. This drops the `prime-runs` git source and the `prime-traces` git override, both pinned to `adf8d25c`. The new verifiers requires `prime-runs>=0.1.5` (PrimeIntellect-ai/verifiers#3001), and the old git pin no longer resolves. 0.1.5 contains `adf8d25c`, so it includes PrimeIntellect-ai/prime#1109. `uv.lock`: `prime-runs` 0.1.4 (git) → 0.1.5, `prime-traces` 0.0.10 (git) → 0.0.11. ### Why On Prime, the SDK sent a fixed 24h deadline on every live process stream, and the sandbox ended the process when it expired. A long agent segment then lost its ACP process. The trace stopped as `harness_error` (`EOFError: ACP process closed its stdout`) instead of `agent_timeout`, so `agent_timeout` undercounted real 24h cap hits. ### prime-sandboxes changes (0.5.1 → 0.5.3) 0.5.3: - fix(sandboxes): stop capping live processes at 24h (PrimeIntellect-ai/prime#1142) 0.5.2: - feat(sandbox): add localhost forwards to the CLI and SDK (PrimeIntellect-ai/prime#1124) - feat(sandboxes): add pause and resume to the SDK and CLI (PrimeIntellect-ai/prime#1056) - fix(cli): write credential files owner-only (PrimeIntellect-ai/prime#1130); the SDK auth cache is written 0600 ### verifiers changes (`60290d4` → `bccc2c5`) Fixes and features: - fix(serve): discard kept episodes when clients disconnect (PrimeIntellect-ai/verifiers#2997) - Reuse interception tunnels for task-scoped MCP servers (PrimeIntellect-ai/verifiers#3006) - Run Prime task services as native VMs and fix Harbor setup (PrimeIntellect-ai/verifiers#2910) - Run Compose services as native Prime VMs (PrimeIntellect-ai/verifiers#2918) - Accept Harbor Compose tasks on native VMs (PrimeIntellect-ai/verifiers#2999) - Add a native Compose integration test on Prime (PrimeIntellect-ai/verifiers#3010) - fix(harbor): reject ignored verifier user overrides (PrimeIntellect-ai/verifiers#3014) - Honor configured disk size for native Compose sidecars (PrimeIntellect-ai/verifiers#3018) - Size native Compose disks per service (PrimeIntellect-ai/verifiers#3020) - fix: use apk-installed Node and npm on Alpine (PrimeIntellect-ai/verifiers#3004) - fix: retry Node downloads before extracting (PrimeIntellect-ai/verifiers#3005) - fix(CVE-2026-111497) in Brotli and Node (PrimeIntellect-ai/verifiers#2998) Dependency pins: - bump prime-sandboxes to 0.5.3 (PrimeIntellect-ai/verifiers#3043) - chore: require prime-runs 0.1.5 from PyPI (PrimeIntellect-ai/verifiers#3001) Harness versions: - Bump Codex to 0.166.0 and codex-acp to 2.5.0 (PrimeIntellect-ai/verifiers#3021) - Bump DeepSeek harness to 0.2.1-rc.5 (PrimeIntellect-ai/verifiers#3023) - Bump mini-swe-agent's LiteLLM dependency to 1.106.6 (PrimeIntellect-ai/verifiers#3031) - Bump Hermes Agent to v0.22.5 (PrimeIntellect-ai/verifiers#3024) - Bump Claude Code to 2.1.314 and claude-agent-acp to 0.99.1 (PrimeIntellect-ai/verifiers#3022) - Bump Kimi Code to 2.4.1 (PrimeIntellect-ai/verifiers#3025) - Bump OpenCode to 2.2.5 (PrimeIntellect-ai/verifiers#3026) - Bump OpenClaw to 2026.10.9 (PrimeIntellect-ai/verifiers#3027) - Bump Prime Agent to 0.10.2 (PrimeIntellect-ai/verifiers#3028) - Bump nano-rlm to e4d4407f (PrimeIntellect-ai/verifiers#3029) - Bump Terminus 2 to Harbor 0.25.0 (PrimeIntellect-ai/verifiers#3030) Dependabot lockfile bumps in verifiers (they do not change prime-rl's lock): - urllib3 2.7.0 → 2.8.0 (PrimeIntellect-ai/verifiers#2733), pyjwt 2.13.0 → 2.15.0 (PrimeIntellect-ai/verifiers#2735), opentelemetry-api 1.44.0 → 1.45.1 (PrimeIntellect-ai/verifiers#2964), authlib 1.7.2 → 1.9.1 (PrimeIntellect-ai/verifiers#2990), msgpack 1.2.1 → 1.2.3 (PrimeIntellect-ai/verifiers#2991), litellm 1.98.0 → 1.100.4 (PrimeIntellect-ai/verifiers#2992), dnspython 2.8.0 → 2.9.0 (PrimeIntellect-ai/verifiers#2993), filelock 3.32.4 → 4.2.1 (PrimeIntellect-ai/verifiers#3002), virtualenv 21.14.12 → 21.14.13 (PrimeIntellect-ai/verifiers#3008), joserfc 1.7.4 → 1.7.6 (PrimeIntellect-ai/verifiers#3017), oauthlib 3.3.1 → 4.0.0 (PrimeIntellect-ai/verifiers#3035) ## Verification - `uv sync --frozen --all-packages --all-extras` installs `prime-sandboxes` 0.5.3. `prime_sandboxes.sandbox._LIVE_PROCESS_NO_TIMEOUT_MS == 9999999999` is the default deadline for every `open_process` stream. - `uv.lock` changes only `prime-sandboxes`, `prime-runs` and `prime-traces` (plus a uv-written reorder of the `exclude-newer-package` table). - `pytest tests/unit --ignore=tests/unit/train`: 837 passed, 1 xfailed. - `tests/unit/train` (RTX PRO 6000 Blackwell, SM120): `qwen3_8_flash_next` and `test_fp8_grouped_gemm` show the same failures, and `test_fused_lm_head.py::test_inject_prime_lm_head_vanilla` kills the process, on this branch and on `main` (`823ed27`). These GPU kernel tests do not touch this diff. - verifiers#3043: the live Prime runtime tests (`tests/unit/test_runtimes.py -k prime`, including `test_exec` on a live process) pass on 0.5.3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Summary
_encode_recordwrites trace lines with orjson, usingOPT_SERIALIZE_NUMPY | OPT_NON_STR_KEYS | OPT_APPEND_NEWLINE.encode_json(request bodies and sample batches) and the Parquet JSON columns use orjson.infocolumn embeds the episode record.orjson>=3.10.0to both packages. prime-rl already depends on it. verifiers adds it in perf: keep record streams as numpy arrays verifiers#2980.json.Why: today
to_record()builds a Python list for every per-token stream, and the stdlib encodes each element. That takes 114.7 ms per real training episode. PrimeIntellect-ai/verifiers#2980 makesto_record()return the streams as numpy arrays, which the stdlib encoder cannot write. With this change, orjson writes them in one pass, and verifiers and prime-rl keep passing the episodes as they are.Breaking
null. Before, they raisedValueErrorbefore the request. orjson has noallow_nan=False.null. Trace records and request bodies now do the same.test_records_reject_nonfinite_json_instead_of_sending_an_opaque_400is nowtest_records_send_nonfinite_floats_as_null_instead_of_an_opaque_400and checks fornull.1e-05becomes0.00001. The same records can therefore get a different content-addressed upload ID than with an older SDK.TypeErrorduring encoding. The stdlib encoder wrote them.Verification
Real data: 512 training episodes, each passed as is to
_encode_record(stamp_run(to_mapping(episode), ...)), as prime-rl does.mainand the released SDK (prime-runs 0.1.4, prime-traces 0.0.10).to_record()builds Python lists, and the stdlib encodes them.to_record()keeps the streams as numpy arrays, and orjson writes them.TypeError: Type is not JSON serializable: numpy.ndarray).🤖 Generated with Claude Code
Note
Medium Risk
Breaking serialization semantics (non-finite floats, float formatting, large integers) affect upload idempotency keys and API payloads; otherwise limited to encode paths with broad downstream impact on training/eval uploads.
Overview
prime-traces and prime-runs now serialize hot JSON paths with orjson (
>=3.10.0) instead of the stdlibjsonmodule, usingOPT_SERIALIZE_NUMPYandOPT_NON_STR_KEYSso episode/trace records can include numpy arrays without a list copy first.Trace JSONL encoding (
_encode_record), HTTP bodies (encode_json), and Parquet JSON columns (_json_column) all go through orjson. Upload metadata and other small payloads still use stdlibjson.Behavior changes: non-finite floats are emitted as JSON
null(flush no longer raisesValueErroron NaN). Some numeric literals may encode differently, so content-addressed trace upload IDs can change vs older SDKs. Integers wider than 64 bits now fail at encode time withTypeError.Tests were updated for NaN→
nullon eval samples and a new Parquet test covers numpytoken_idsin embedded episode records.Reviewed by Cursor Bugbot for commit 7dbeaaf. Bugbot is set up for automated code reviews on this repo. Configure here.