Skip to content

feat(coevolution): PR 5a — wall-time instrumentation + pretrain-arm smoke (pre-pilot polish) - #151

Merged
chrisjz merged 7 commits into
mainfrom
feat/m5-coevolution-pr5a-pre-pilot-polish
May 9, 2026
Merged

feat(coevolution): PR 5a — wall-time instrumentation + pretrain-arm smoke (pre-pilot polish)#151
chrisjz merged 7 commits into
mainfrom
feat/m5-coevolution-pr5a-pre-pilot-polish

Conversation

@chrisjz

@chrisjz chrisjz commented May 9, 2026

Copy link
Copy Markdown
Member

Summary

PR 5a of the M5 co-evolution arms-race milestone (pre-pilot polish, ahead of the pilot run in PR 6). Two mechanical, dependency-free items:

  • Wall-time instrumentation in CoevolutionLoop._run_one_k_block — master-side time.perf_counter() brackets around _evaluate_in_worker dispatch. Per-eval timing for the sequential path; per-batch amortised timing for the pool path (batch_wall / population_size). Worker ABI unchanged. Per-eval + per-generation aggregate rows written to top-level walltime.csv with schema (scope, side, generation, index, parallel_workers, wall_seconds).
  • Aggregator extension (scripts/campaigns/aggregate_m5_pilot.py): reads walltime.csv, emits walltime_summary.csv (per-seed roll-up: mean eval/gen walls per side, total run wall, modal parallel_workers_used), and adds a "Wall-time reconciliation" markdown table to summary.md. PR 6's task 9.5 reconciliation row is now populated automatically rather than reverse-engineered from session logs.
  • Pretrain-arm smoke validation: ran the heuristic-imitation pretrain bootstrap path end-to-end (smoke config + predator_gen0_bootstrap: heuristic_imitation_pretrain); EXIT=0; ~141s wall (vs ~138s for cold-start; ~3s delta covering the inline pretrain). Confirms pilot arm A path is healthy before the ~3.5-7 hour campaign launches. No code change.

Why PR 5a is decoupled from PR 5b: PR 5b wires the deferred _probe_one_opponent body. That depends on a design conversation about prey-side held-out semantics. PR 5a's items are mechanical and have no dependency on that conversation, so they can ship first.

Test plan

  • 70 unit tests pass (PR 5's 25 + 5 new for wall-time instrumentation + 1 regression test for the resume-safe CSV fix).
  • uv run pre-commit run --all-files clean.
  • openspec validate add-coevolution-arms-race --strict valid.
  • Smoke pilot (cold-start + pretrain arm both): aggregator emits walltime_summary.csv with real numbers; reconciliation table renders in summary.md.
  • Smoke wall numbers (cold-start, smoke pop=4, K=2, gen_pairs=1, parallel_workers=1): prey eval 5.75s mean (15 ep/eval = 0.38s/episode), predator eval 11.49s mean (5 ep/eval = 2.30s/episode), total run 137.94s. Both within ~2x of design.md D4's 0.75-1.5s/episode envelope (smoke is parallel_workers=1; pilot uses 4).
  • (TBD) Pilot run via phase5_m5_coevolution_pilot.sh (out of PR 5a scope; PR 6).

Reviews completed

  • Pre-push self-review: 0 blocking + 3 should-fix + 6 notes. Three should-fixes applied:
    • Resume-safe CSV init (real bug): walltime.csv and generality_probe.csv were truncated on resume because __init__ opened them in "w" mode unconditionally. Fixed: write header only when file doesn't exist; regression test added.
    • tasks.md 8a.2 wording mismatched the smoke config (1-gen K-blocks vs actual K=2).
    • Aggregator docstring listed 5 artefacts but omitted the new walltime_summary.csv.

Deferred / out of scope

  • PR 5b (probe body wiring) — separate PR. Begins with a design conversation about prey-side held-out semantics, then implementation. PR 5b can ship before or after this PR; both are pre-flight for PR 6.
  • Pool-mode regression coverage: the only end-to-end test runs at parallel_workers=1. Pool-path is exercised in real production but lacks unit coverage. Documented as a Note in the review; cheap to add (~10 LoC test) post-merge.
  • Resume + parallel_workers config drift: if a campaign resumes under different parallel_workers, the modal value in walltime_summary.csv will silently mask the change. Not blocking; documented.

ðŸĪ– Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Records wall-time per evaluation and per generation; produces per-seed rollups, a walltime_summary CSV, and a "Wall-time reconciliation" section in reports.
  • Bug Fixes

    • Timing CSV initialization is resume-safe so existing timing data is preserved across restarts.
  • Tests

    • Added unit and end-to-end tests covering wall-time logging, aggregation, summary outputs, and handling of malformed/missing timing data.
  • Documentation

    • Added pre-pilot polish and probe-wiring checklist entries; updated PR split breakdown to 11 PRs.

chrisjz and others added 3 commits May 9, 2026 10:20
Two new pre-pilot PR sections inserted ahead of PR 6 (pilot run):

- **8a (PR 5a) — Pre-pilot polish**: wall-time instrumentation in
  `_run_one_k_block` (8a.1) so PR 6's logbook reconciliation row is
  populated automatically; pretrain-arm smoke validation (8a.2) to
  verify the heuristic-imitation pretrain path doesn't crash before
  launching the ~3.5-7 hour pilot arm A.
- **8b (PR 5b) — Probe body wiring**: real per-opponent fitness in
  `_probe_one_opponent` (currently NaN). Begins with a design
  conversation resolving the prey-side held-out semantic ambiguity
  (8b.1), then implementation (8b.2), then OpenSpec sync (8b.3).

PR-splitting reference table updated: 9 PRs → 11 PRs. PR 5a and
PR 5b ship in either order (independent); both are pre-flight for
PR 6.
â€Ķension + pretrain smoke

Pre-pilot polish (tasks 8a.1 + 8a.2). Decoupled from PR 5b's probe
wiring because these items are mechanical and dependency-free.

**Wall-time instrumentation (8a.1):**
- `CoevolutionLoop.__init__` initialises top-level `walltime.csv` with
  header `(scope, side, generation, index, parallel_workers,
  wall_seconds)`; pattern mirrors `generality_probe.csv`.
- `_run_one_k_block` brackets `_evaluate_in_worker` dispatch with
  `time.perf_counter()`. Sequential path records true per-eval wall;
  pool path records `batch_wall / population_size` (amortised).
  Worker ABI unchanged — preserves the spec's "11-tuple worker tuple
  ABI does NOT change for co-evolution" contract.
- `_record_walltime` helper writes one evaluation row per child plus
  a generation-aggregate row.
- Aggregator extension: `_load_walltime_csv` + `_walltime_summary`
  (per-side mean eval/gen walls + total run wall + modal
  `parallel_workers_used` + n_eval/n_gen counts).
- New output `walltime_summary.csv` (one row per seed) emitted
  alongside `verdict.csv`.
- New "Wall-time reconciliation" section in `summary.md` with a
  markdown table per task 9.5. PR 6's reconciliation row is now
  populated automatically.

**Pretrain-arm smoke (8a.2):** ran the heuristic-imitation pretrain
bootstrap path end-to-end (smoke pop=4, K=2, gen_pairs=1 with
`predator_gen0_bootstrap: heuristic_imitation_pretrain`). EXIT=0;
~141s wall (vs ~138s for cold-start). Confirms pilot arm A path is
healthy before the ~3.5-7 hour campaign launches. No code change
landed; documented as a pre-flight check in tasks.md 8a.2.

**Smoke wall numbers** (cold-start, smoke pop=4, K=2, gen_pairs=1,
parallel_workers=1):
- Prey eval: 5.75s mean (15 episodes/eval = 0.38s/episode).
- Predator eval: 11.49s mean (5 episodes/eval = 2.30s/episode).
- Total run: 137.94s.
Both sides within ~2x of design.md D4's 0.75-1.5s/episode envelope
at smoke compute (parallel_workers=1; pilot will run at =4).

**Tests** (5 new, 73 total in PR 5+5a tests):
- `TestWalltimeInstrumentation` (2 cases): walltime.csv initialised
  with header at __init__; run writes the right row count
  (population_size eval rows + 1 gen row per side per K-block).
- `TestWalltimeSummary` (4 cases): empty rows return NaN summary;
  per-side means + total computation; modal parallel_workers when
  split; end-to-end main() emits walltime_summary.csv +
  reconciliation table in summary.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
â€Ķstring drift)

3 self-review should-fix items applied; 6 informational notes left
documented in the review report.

**S1 — `walltime.csv` and `generality_probe.csv` truncated on resume.**
`CoevolutionLoop.__init__` opened both CSVs with `"w"` mode
unconditionally, wiping any prior data. On resume (which re-enters
`__init__`), every prior wall-time row + every prior probe row was
silently lost — `total_run_wall_seconds` for resumed seeds would
understate the campaign total. Pilot is single-shot so this wouldn't
bite PR 6, but the multi-day full run (PR 7) is plausible to resume.
Fix: write the header only when the file doesn't already exist; on
resume the prior run's rows are preserved. Added a regression test
that re-instantiates the loop and verifies forged data rows in both
CSVs survive the second `__init__`.

**S2 — tasks.md 8a.2 wording mismatch with smoke config.** Task said
"1-gen prey + 1-gen predator K-blocks" but the smoke config runs
`K_per_block: 2, generation_pairs: 1` (2 generations per K-block, 1
K-block per side). Tightened the task wording to match what was
actually run, including the explicit `predator_gen0_bootstrap`
override and the ~3s pretrain delta vs cold-start.

**S3 — aggregator docstring drift.** Top-of-file docstring listed
the 5 emitted artefacts but omitted the new `walltime_summary.csv`.
Added it for parity; clarified that summary.md now includes the
wall-time reconciliation table, and that the verdict list includes
INCONCLUSIVE.

Smoke pilot still passes; 70/70 PR 5+5a tests pass; pre-commit
clean; openspec strict still validates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • â–ķïļ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

â„đïļ Recent review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d2fec10-b77e-4685-8f8d-b230332d1112

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between b1a7cf8 and 41cd804.

📒 Files selected for processing (1)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py

📝 Walkthrough

Walkthrough

This change implements wall-time instrumentation for the coevolution training loop. The CoevolutionLoop now measures and logs per-evaluation and per-generation wall-clock durations to a checkpoint-safe walltime.csv. The aggregator reads these logs, computes per-seed rollups (side-specific and total wall-time metrics, modal worker count), and outputs walltime_summary.csv plus a reconciliation table in summary.md.

Changes

Wall-time instrumentation and aggregation

Layer / File(s) Summary
Specification
openspec/changes/add-coevolution-arms-race/tasks.md
OpenSpec tasks added for PR 5a (wall-time instrumentation and pretrain validation) and PR 5b (probe body wiring), with updated 11‑PR splitting plan.
Instrumentation Hooks
packages/quantum-nematode/quantumnematode/evolution/coevolution.py
Import time; initialize resume-safe generality_probe.csv and new walltime.csv headers; collect wall-time metrics during _run_one_k_block (sequential per-eval timings; amortized per-eval for pool runs); persist rows via _record_walltime().
Instrumentation Tests
packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_coevolution.py
Added csv import and TestWalltimeInstrumentation validating header-only CSV creation, evaluation and generation row appends with per-side counts, and resume-safety preserving existing rows.
Aggregation Pipeline
scripts/campaigns/aggregate_m5_pilot.py
Added _load_walltime_csv() and _walltime_summary() to reduce per-session walltime.csv into per-seed rollups (side means, total run seconds, deterministic modal parallel_workers_used or "N/A"); _write_walltime_summary_csv() emits CSV; _format_summary() renders a Wall-time reconciliation section; main() loads per-session walltime, attaches walltime to per-seed rows, and writes walltime_summary.csv.
Aggregation Tests
packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py
Extended _build_synthetic_session() to accept walltime_rows and write walltime.csv; added TestWalltimeSummary with unit tests for _walltime_summary() (NaN handling, per-side means, modal worker selection, malformed-parallel_workers handling) and integration tests asserting walltime_summary.csv and the reconciliation table in summary.md.

Estimated code review effort

ðŸŽŊ 3 (Moderate) | ⏱ïļ ~25 minutes

Possibly related PRs

Poem

🐰 I hop and time each tiny race,

Seconds logged in tidy place.
CSV rows stack in neat array,
Seeds report their measured day,
A tiny hop — the logs are done.

ðŸšĨ Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main changes: wall-time instrumentation and pretrain-arm smoke validation for pre-pilot polish, matching the primary objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏ïļ Tip: You can configure your own custom pre-merge checks in the settings.

âœĻ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • ✅ Committed to branch successfully - (🔄 Check to regenerate)
🧊 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/m5-coevolution-pr5a-pre-pilot-polish

Comment @coderabbitai help to get the list of available commands and usage tips.

@chrisjz
chrisjz marked this pull request as ready for review May 9, 2026 01:26

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

Actionable comments posted: 1

ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/campaigns/aggregate_m5_pilot.py`:
- Around line 195-204: The current modal-selection of parallel_workers_used uses
Counter(...).most_common(1) which makes ties order-dependent; change the logic
in aggregate_m5_pilot.py where parallel_workers_used is set: build a Counter
from workers, determine the maximum frequency, collect all worker-values with
that frequency, and if there is a tie prefer 1 (i.e., set parallel_workers_used
= 1 when 1 is among the tied candidates); otherwise pick a deterministic value
(e.g., the single candidate). Update the block referencing Counter(workers) and
parallel_workers_used to implement this tie-break rule.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d555f075-ace3-48c8-940d-31cd5cd3df0d

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 1ef65c4 and 348c3ca.

📒 Files selected for processing (5)
  • openspec/changes/add-coevolution-arms-race/tasks.md
  • packages/quantum-nematode/quantumnematode/evolution/coevolution.py
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_coevolution.py
  • scripts/campaigns/aggregate_m5_pilot.py

Comment thread scripts/campaigns/aggregate_m5_pilot.py Outdated
@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.57895% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
scripts/campaigns/aggregate_m5_pilot.py 92.53% 3 Missing and 2 partials ⚠ïļ
...-nematode/quantumnematode/evolution/coevolution.py 89.28% 3 Missing ⚠ïļ

ðŸ“Ē Thoughts on this report? Let us know!

â€Ķllel_workers (PR 5a review)

PR 5a review finding: `Counter(workers).most_common(1)[0][0]` made the
modal-selection of `parallel_workers_used` order-dependent on dict
iteration when multiple values shared the highest frequency. The
docstring already promised "1 if mixed" (conservative sequential
interpretation), but the code didn't enforce it.

Fix: build counts, find max frequency, collect all tied values; if 1
is among the tied → return 1; else return `min(tied)` for
deterministic output across runs.

2 new tests cover the tie-break rule (tie-with-1-prefers-1,
tie-without-1-picks-smallest); the existing unambiguous-modal test
stays passing. 31/31 aggregator tests pass; pre-commit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

Actionable comments posted: 2

ðŸ§đ Nitpick comments (1)
scripts/campaigns/aggregate_m5_pilot.py (1)

148-231: 🏗ïļ Heavy lift

Model the walltime payload with a typed schema instead of nested dict[str, Any].

The new walltime path relies on unstructured nested dicts, which makes key/shape regressions easy to miss. Please introduce a dedicated model (e.g., WalltimeSummary with nested side metrics) and use it across parse/reduce/write/render.

As per coding guidelines, Use comprehensive type annotations in all code and Use Pydantic BaseModel for data structures.

Also applies to: 547-603, 753-765

ðŸĪ– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/campaigns/aggregate_m5_pilot.py` around lines 148 - 231, The
_walltime_summary function currently returns and manipulates untyped nested
dicts (dict[str, Any]) which is error-prone; define a Pydantic model (e.g.,
WalltimeSideMetrics and WalltimeSummary) to represent mean_eval_wall_seconds,
mean_gen_wall_seconds, total_run_wall_seconds, parallel_workers_used,
n_eval_rows and n_gen_rows, change _walltime_summary signature to return
WalltimeSummary instead of dict, replace local eval_walls/gen_walls/workers with
typed structures and convert computed values into the Pydantic model before
returning, and update any callers (parse/reduce/write/render code paths that
consume _walltime_summary) to accept the WalltimeSummary type and serialize
(.dict() or .json()) when writing output; ensure all relevant functions have
full type annotations referencing WalltimeSummary so the schema is enforced
across the pipeline.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/campaigns/aggregate_m5_pilot.py`:
- Around line 166-173: When walltime_rows is empty the summary currently sets
"parallel_workers_used": 0 which contradicts the desired "N/A" for older runs;
change the no-data return to set "parallel_workers_used" to a sentinel string
"N/A" instead of integer 0 (update the block that checks if not walltime_rows
and any other identical block around lines 753-765), and ensure any callers that
consume parallel_workers_used (or formatting code) can handle the string value
or convert it for display; reference the variable walltime_rows and the JSON key
"parallel_workers_used" when making the change.
- Around line 153-160: The docstring for the return value parallel_workers_used
is inaccurate: update the text for the "parallel_workers_used" entry (and the
duplicate doc text around the other docstring instance) to match the selection
logic used later in the file — i.e., state it is the modal value across rows; if
multiple values tie for highest frequency choose 1 if 1 is among the tied
values, otherwise choose the smallest tied value. Locate the two docstring
occurrences that describe parallel_workers_used and replace the old "1 if mixed"
wording with this precise tie-breaking description so the docs match the
selection behavior implemented in the code.

---

Nitpick comments:
In `@scripts/campaigns/aggregate_m5_pilot.py`:
- Around line 148-231: The _walltime_summary function currently returns and
manipulates untyped nested dicts (dict[str, Any]) which is error-prone; define a
Pydantic model (e.g., WalltimeSideMetrics and WalltimeSummary) to represent
mean_eval_wall_seconds, mean_gen_wall_seconds, total_run_wall_seconds,
parallel_workers_used, n_eval_rows and n_gen_rows, change _walltime_summary
signature to return WalltimeSummary instead of dict, replace local
eval_walls/gen_walls/workers with typed structures and convert computed values
into the Pydantic model before returning, and update any callers
(parse/reduce/write/render code paths that consume _walltime_summary) to accept
the WalltimeSummary type and serialize (.dict() or .json()) when writing output;
ensure all relevant functions have full type annotations referencing
WalltimeSummary so the schema is enforced across the pipeline.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd69840e-e1b6-4076-a3fb-71cb5eb4f610

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 348c3ca and 32010a5.

📒 Files selected for processing (2)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py
  • scripts/campaigns/aggregate_m5_pilot.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py

Comment thread scripts/campaigns/aggregate_m5_pilot.py
Comment thread scripts/campaigns/aggregate_m5_pilot.py
â€Ķrs_used` (PR 5a review)

Two PR 5a review findings applied; one nitpick skipped with reason.

**Finding 1 — N/A sentinel for missing walltime data.** The empty-rows
return path of `_walltime_summary` set `parallel_workers_used` to
int 0, contradicting the markdown renderer's intent ("N/A" via
`wt.get('parallel_workers_used', 'N/A')` — fallback only fires when
the key is missing, not when it's 0). Older runs without
instrumentation rendered "0" in summary.md / verdict.csv, indistinct
from a real 0-worker run (impossible — `parallel_workers >= 1` per
`EvolutionConfig` schema). Switch to the string sentinel "N/A" in:
- `_walltime_summary([])` empty-rows return path.
- `_walltime_summary([...])` defensive branch where every
  `parallel_workers` column was unparseable.
- `_write_walltime_summary_csv`'s fallback dict for rows lacking a
  `walltime` entry.

CSV `DictWriter` round-trips strings unchanged; markdown f-string
renders "N/A" verbatim. Updated the empty-rows test assertion +
added an end-to-end test that runs main() with a header-only
walltime.csv (older-run shape) and verifies `walltime_summary.csv`
+ summary.md both show "N/A" for `parallel_workers_used`.

**Finding 2 — docstring drift.** The `_walltime_summary` return-shape
docstring still said "1 if mixed", which was the pre-tie-break-fix
wording. Updated to reflect the precise tie-break rule (modal across
rows; on ties prefer 1 if among the tied else smallest tied; "N/A"
sentinel for no data).

**Skipped — Pydantic model nitpick.** Reviewer suggested replacing
the dict-with-`Any` returns with `WalltimeSummary` Pydantic models.
Real modernisation but: the dict surface is local to this script +
test fixture; pydantic adoption here is inconsistent with the rest
of the script's idioms (other returns are also untyped dicts); the
refactor would be 200+ LoC for cosmetic typing benefit, not
contract-breaking. Defer to a future cross-script typing refactor.

32/32 aggregator tests pass; pre-commit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

Actionable comments posted: 2

ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/campaigns/aggregate_m5_pilot.py`:
- Around line 194-197: The code currently appends 1 on ValueError/TypeError when
parsing row.get("parallel_workers", 1); change this to only append to the
workers list when int(row["parallel_workers"]) succeeds (i.e., try to read raw =
row.get("parallel_workers") and if raw is not None/empty attempt int(raw) and
append the parsed int), and on exception do not append anything (treat as
missing). Also update the downstream modal/aggregate logic that computes the
modal worker value to return "N/A" when workers is empty. Apply the same change
to the other identical block around lines 219-223 so both parsing sites (the
workers list population for parallel_workers) follow this behavior.
- Around line 148-163: Replace the untyped dict schema returned by
_walltime_summary with a Pydantic model: define a BaseModel (e.g.,
WalltimeSummary) with typed fields mean_eval_wall_seconds: dict[str, float],
mean_gen_wall_seconds: dict[str, float], total_run_wall_seconds: float,
parallel_workers_used: int | Literal["N/A"], n_eval_rows: int, n_gen_rows: int
(or use explicit prey/predator submodel if preferred), update _walltime_summary
signature to return that model (or the model instance .dict() only where
strictly needed), change internal construction to instantiate WalltimeSummary
instead of building raw dicts, and update the other usages mentioned (the blocks
around lines ~224-243 and ~559-615) to accept/consume the typed model rather
than dict[str, Any], adjusting any indexing like ["prey"] to attribute access or
typed dict access as appropriate.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba2d6056-0178-4433-af32-2fa98b555aa3

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 32010a5 and d653d6f.

📒 Files selected for processing (2)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py
  • scripts/campaigns/aggregate_m5_pilot.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py

Comment on lines +148 to +163
def _walltime_summary(walltime_rows: list[dict[str, Any]]) -> dict[str, Any]:
"""Reduce walltime CSV rows to a per-seed summary dict.

Returns
-------
`{
"mean_eval_wall_seconds": {"prey": float, "predator": float},
"mean_gen_wall_seconds": {"prey": float, "predator": float},
"total_run_wall_seconds": float,
"parallel_workers_used": int | "N/A" (modal value across rows; on
ties, prefer 1 if 1 is among the tied
values, else the smallest tied value;
"N/A" sentinel when no walltime data),
"n_eval_rows": int,
"n_gen_rows": int,
}`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠ïļ Refactor suggestion | 🟠 Major | 🏗ïļ Heavy lift

Replace wall-time nested dicts with a typed model.

The new wall-time flow is still string-keyed (dict[str, Any]), which weakens schema safety and pushes contract errors to runtime. A dedicated model for wall-time summary would make this path safer and clearer.

As per coding guidelines, "Use Pydantic BaseModel for data structures" and "Use comprehensive type annotations in all code".

Also applies to: 224-243, 559-615

ðŸĪ– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/campaigns/aggregate_m5_pilot.py` around lines 148 - 163, Replace the
untyped dict schema returned by _walltime_summary with a Pydantic model: define
a BaseModel (e.g., WalltimeSummary) with typed fields mean_eval_wall_seconds:
dict[str, float], mean_gen_wall_seconds: dict[str, float],
total_run_wall_seconds: float, parallel_workers_used: int | Literal["N/A"],
n_eval_rows: int, n_gen_rows: int (or use explicit prey/predator submodel if
preferred), update _walltime_summary signature to return that model (or the
model instance .dict() only where strictly needed), change internal construction
to instantiate WalltimeSummary instead of building raw dicts, and update the
other usages mentioned (the blocks around lines ~224-243 and ~559-615) to
accept/consume the typed model rather than dict[str, Any], adjusting any
indexing like ["prey"] to attribute access or typed dict access as appropriate.

Comment thread scripts/campaigns/aggregate_m5_pilot.py Outdated
â€Ķabricating 1 (PR 5a review)

PR 5a review finding: `_walltime_summary` previously appended `1` to
the workers list whenever `parallel_workers` was missing
(`row.get("parallel_workers", 1)`) or unparseable (`except: append(1)`).
That polluted the modal counter and masked truly-missing
instrumentation — a row without a `parallel_workers` column would
look like a sequential run.

Fix: read `raw = row.get("parallel_workers")`; skip when None / empty
string; otherwise attempt `int(raw)` inside a `contextlib.suppress`
and append on success only. When every row's column is
missing/malformed, `workers` ends up empty and the existing
"N/A" sentinel branch fires (per the prior review's tightening).

New test covers the missing / empty-string / unparseable cases all
together: 3 rows with valid wall_seconds but no parseable
parallel_workers → mean_eval_wall_seconds populates correctly,
parallel_workers_used returns "N/A".

**Skipped (Pydantic model nitpick).** Reviewer raised this for the
second time. Same rationale as before: dict-with-Any is consistent
with the rest of the script (`# pragma: no cover` glue layer); 200+
LoC refactor for cosmetic typing benefit; risks breaking working
code. Defer to a future cross-script typing refactor.

33/33 aggregator tests pass; pre-commit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

Actionable comments posted: 1

ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py`:
- Around line 477-497: The test
test_main_walltime_summary_renders_na_without_walltime_csv is intended to
exercise the "walltime.csv missing" path but the helper _build_synthetic_session
always creates a walltime.csv; after calling _build_synthetic_session in that
test, remove the created walltime.csv (e.g., delete seed42_session /
"walltime.csv") so the code exercises the true-missing-file branch;
alternatively, update _build_synthetic_session to not create the file when
walltime_rows is None so tests that pass walltime_rows=None get a genuinely
absent file.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96bcd44e-bae2-4d0d-ad7b-aba2e7124f4f

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between d653d6f and b1a7cf8.

📒 Files selected for processing (2)
  • packages/quantum-nematode/tests/quantumnematode_tests/evolution/test_aggregate_m5_pilot.py
  • scripts/campaigns/aggregate_m5_pilot.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/campaigns/aggregate_m5_pilot.py

Round 4 review: `_build_synthetic_session` always wrote walltime.csv
(header-only when `walltime_rows is None`), so
`test_main_walltime_summary_renders_na_without_walltime_csv` was
exercising the empty-data branch rather than the truly-missing-file
branch the docstring claimed. The outcome happened to match because
`_load_walltime_csv` returns `[]` for both cases.

Update the helper so `walltime_rows=None` skips creating the file
entirely (genuinely-missing case) and `walltime_rows=[]` writes a
header-only file (instrumented run that aborted before recording).
The existing test now exercises the missing-file branch and gains a
sanity-check assertion that the file is absent. Add a sibling test
for the header-only branch so coverage of both code paths is
explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

Caution

Docstrings generation - FAILED

An unexpected error occurred while creating a local commit: Not Found - https://docs.github.com/rest/git/refs#get-a-reference

@chrisjz
chrisjz merged commit 1482806 into main May 9, 2026
3 checks passed
@chrisjz
chrisjz deleted the feat/m5-coevolution-pr5a-pre-pilot-polish branch May 9, 2026 04:32
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