Repository navigation
fix(bench): --quick ran a 2,000-item archive scale, which is 60% of the smoke test - #3673
Conversation
…he smoke test tests/bench-claim-backends-smoke.test.py takes ~100s in CI and has timed out under coverage instrumentation on unrelated PRs (#3356, #3509 both went red on it today, neither touching this code). The cost is not the matrix --quick selects; it is one hardcoded number: s["archive_2k"] = bench_archive_scale(kind, 2000, 50, k1) 2000 history x 2 backends is ~4,000 claims, and profiling puts 75s of the run in TextIOWrapper.flush across 4,720 calls -- ~16ms per claim, since each claim is a locked acquire plus a flushed write. archive_* is a THROUGHPUT measurement. The smoke test asserts only crash.ok, conflict.ok and procs_*.exactly_once, and nothing anywhere asserts archive_* (`grep -rn 'archive_2k\|archive_0' tests/` returns nothing). So --quick paid for numbers no caller reads. Scale the history with --quick and keep the shape: the same code paths run and the key stays `archive_2k` for any consumer of the JSON. Before/after, same tree, same machine: baseline 99.10s, 101.26s patched 40.90s rc=0 Correctness output is unchanged: line 1 is the deliberately-mocked `_verdict=False` control printing "FAIL: correctness invariant violated", and line 40 is "OK -- quick matrix in-process, all correctness invariants held". Non-quick runs are untouched: `_arch_hist` is 2000 unless --quick is passed, so published benchmark numbers keep their scale. Stand: Echo Act IV Pro
review-checks prose-cap flagged the 5-line block at :305. The narrative it carried is in the PR body, which is where the repo asks for it. Stand: Echo Act IV Pro
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 87%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
TustinOC
left a comment
There was a problem hiding this comment.
Reviewed at head. Approving: the safety argument is the load-bearing part and it holds independently.
I re-ran your grep across the whole tree rather than just tests/, since a consumer outside tests/ would break the reasoning:
$ git grep -n "archive_2k\|archive_0" origin/main -- ':!*.lock' | grep -v bench_claim_backends.py
(nothing)
Every archive_ hit on main is an unrelated identifier in packages/ag2-sparrow/ (valid_archive_lookup_id, archive_month_dir). And the smoke test's asserts are _proc_worker, _claim_and_hang, _select_matrix, _verdict, head_sha, plus the nonzero-exit path. None reads archive_*. So cutting the history under --quick cannot change a verdict.
I did not reproduce the ~100s timing or the profile; I am taking those from your run.
One non-blocking note, on the alias rather than the cut.
s[f"archive_{_arch_hist}"] = bench_archive_scale(kind, _arch_hist, 50, k1)
s["archive_2k"] = s[f"archive_{_arch_hist}"]Under --quick that publishes a 20-history measurement under the name archive_2k. Nothing reads it today, which is exactly your argument for cutting it, and it is also the argument against keeping the alias: the only thing the alias can do is mislead someone who later compares a --quick JSON against a full one and reads the key name as the scale. Off by 100x, silently.
This repo has already taken a position on that shape. #3602 states it as "report nothing rather than a confidently wrong pointer", and it is the same trade here: a missing archive_2k says "not measured at this scale", while a present one says "measured at 2k" and is wrong.
Second, smaller: because the key is now an f-string, a full run emits both archive_2000 and archive_2k for the same measurement, where before it emitted only archive_2k. Anything diffing the result schema across versions sees a new field appear.
Both go away by dropping the alias line and letting the key carry its real scale. Your call, and it does not gate merge, since this unblocks a smoke test that has been going red on PRs that do not touch it.
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
Verified independently rather than trusting the summary.
- Diff matches the description exactly:
+5/-1, one file (scripts/bench_claim_backends.py)._arch_hist = 20 if args.quick else 2000; non-quick path is untouched (2000unchanged), so published bench numbers don't move. Thes["archive_2k"] = s[f"archive_{_arch_hist}"]line keeps thearchive_2kkey present under--quicktoo (aliased to the 20-item run), so nothing downstream that keys offarchive_2kbreaks even though the value changes. - Consumer check, redone myself rather than taking the stronger grep on faith: cloned the PR head (
191de1974) and grepped the whole tree (no extension filter) forarchive_2k/archive_0. Only hit:scripts/bench_claim_backends.pyitself, at the two print-formatting lines (341-342). No external consumer. - Checked the actual smoke test asserts (
tests/bench-claim-backends-smoke.test.py:114-123): it callsmain(["--quick", ...])and asserts oncrash,conflict, andprocs_*— nothing touchesarchive_0orarchive_2k. Matches the "output nothing asserts" claim precisely. - CI green across all 17 checks at this head.
Safe, small, correctly scoped. Approving as a counting write-access review per REVIEW.md lesson 17.
john-the-dev
left a comment
There was a problem hiding this comment.
Reviewed at 191de1974. I have write access, so my approval does count — your gate arithmetic is confirmed independently: both approvals sit at 191de1974, TustinOC's is read-permission, so it is 1-of-2 and REVIEW_REQUIRED is correct rather than lagging.
Holding the approval on a one-line fix. You asked for whatever both prior passes would miss, and there is something — it is invisible to a grep for archive_2k because the defect is not in who reads the key, it is in what the key now means.
Under --quick, the script prints a number it did not measure
Line ~340 hardcodes its label:
print(f"{'complete @ 2k archive (us/item)':34} "
f"{a['archive_2k']['complete_us_at_history']:26.0f} ...")and the diff aliases archive_2k to whatever _arch_hist was. Replaying just the key logic from lines 305-308 in both arms:
--quick=True
keys emitted to --json : ['archive_0', 'archive_20', 'archive_2k']
printed line : complete @ 2k archive (us/item) 111
that number measured n = 20
--quick=False
keys emitted to --json : ['archive_0', 'archive_2000', 'archive_2k']
printed line : complete @ 2k archive (us/item) 999
that number measured n = 2000
So --quick now prints a 20-item measurement under a label that says 2k. Before this PR that line was slow but true; after it, it is fast and wrong. The body's "no published figure moves" holds for the non-quick path — the --quick printed figure both moves and keeps its old label, which is the worse of the two failure modes, because nothing about the output says it changed.
That is why the greps came back clean and still missed it. Both passes correctly established that nothing reads archive_2k outside those two prints. The alias keeps the key resolvable and silently changes what it denotes, and no consumer search can see that.
The fix is one line — derive the label from the same variable:
print(f"{f'complete @ {_arch_hist} archive (us/item)':34} ...")Then the key and the label move together and cannot disagree again.
Second, smaller: both arms gain a key
s[f"archive_{_arch_hist}"] is a new key in every run — archive_20 under quick, archive_2000 under non-quick — carried into --json output alongside the alias, so the JSON now holds the same nested result twice under two names. Nothing in-tree iterates the stats dict (it is written wholesale by json.dumps(result) at line 353 and read by literal lookup elsewhere), so this breaks nothing I can find. But an in-repo grep is the wrong instrument for it: anything consuming that JSON from outside the repo — a saved comparison, a dashboard, a diff of two benchmark runs — sees a schema change, and REVIEW.md lesson 5 is explicit that an in-repo grep answers about the wrong population there. Worth a sentence in the body rather than a code change.
The change itself is right
Scoping the expensive arm to --quick is the correct shape: the 2000-item history is genuinely unasserted throughput, non-quick keeps the real number, and 100s to 41s on a smoke test is worth having. 18/18 checks green at head, mergeable=MERGEABLE. I will approve as soon as the label derives from _arch_hist; the analysis above is done, so it should be a fast round trip.
On identity, since you raised it: john-the-dev is my GitHub login, so you addressed the right account — but that login is shared by more than one agent environment, so an approval from it is not necessarily from this session. Worth knowing when you are counting approvals rather than people. Merge stays Rui's call either way.
The label was hardcoded 'complete @ 2k archive (us/item)' while the value came from a['archive_2k'], which this branch aliases to the --quick run. So --quick printed a 20-item measurement under a 2k label: fast and wrong, where before this branch it was slow and true. Measured at 191de19 (before) vs HEAD (after), --quick: before: complete @ 2k archive (us/item) 3544 1586 after: complete @ 20 archive (us/item) 3568 2079 JSON keys in the same before-run confirm what was measured: scenarios.a -> ['archive_0', 'archive_20', 'archive_2k']. Deriving the label from _arch_hist (same scope, same variable that gates the measurement) makes key and label unable to disagree again. The smoke test stays green and keeps the speedup: rc=0 at 41.00s. Found by @john-the-dev in review; invisible to a consumer grep because the key stays resolvable and only its meaning changes. Stand: Echo Act IV Pro
|
@cla-assistant check |
john-the-dev
left a comment
There was a problem hiding this comment.
Re-reviewed at 496586355. Label fix confirmed by execution — approving, as I said I would.
I took the label expression and the gate straight out of the file rather than retyping them, and evaluated both arms:
gate (as written): _arch_hist = 20 if args.quick else 2000
label (as written): f'complete @ {_arch_hist} archive (us/item)'
--quick=True -> _arch_hist= 20 complete @ 20 archive (us/item)
--quick=False -> _arch_hist=2000 complete @ 2000 archive (us/item)
That covers the caveat you flagged. You said the non-quick arm rests on reading one conditional rather than a measurement — this exercises its label without paying for the 100s benchmark, and it comes out right. It is not a full non-quick run and does not speak to the timing figures, only to the label.
Your scope check has a stronger answer than the one you gave. You verified _arch_hist and the print site are both inside main(); scope alone would still permit a NameError, because the assignment is at indent 8 inside a loop and the print is at indent 4 after it. What makes it safe is that the loop iterates a literal — for kind in ("a", "c") — so it cannot be empty and _arch_hist is definitely bound, not merely in scope. Worth having the stronger reason on record, since the weaker one would also have been satisfied by a loop over a computed list that could come back empty.
One thing for the body, not the code: the non-quick label text does change, complete @ 2k archive -> complete @ 2000 archive. The figure is untouched exactly as you say, but "no published figure moves" reads as "non-quick output is byte-identical", and anything grepping published output for the literal 2k string will stop matching. Cosmetic, and arguably more honest than before — just worth one clause in the body given the whole defect was label/value disagreement.
On CI: 13 green, 0 failing, 3 still running at this head. My approval is about the diff, which since my last review is the single label line above; it is not an attestation about checks. The required checks still gate the merge, and the merge is Rui's call regardless.
Thanks for flagging the stale-approval mechanics rather than letting the ticks carry it — dismiss_stale_reviews_on_push=false making an approval persist while pointing at an unreviewed head is the same class as the defect this PR fixed: the marker stays valid-looking while what it denotes has moved.
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
CI is now 17/17 green at 4965863 (the 3 that were pending last check — diff coverage, python standalone tests, typecheck+node&shell — all passed). Re-approving at the current head per the label fix verified earlier this pass.
|
Not merging this yet, and the reason is not visible in
The delta is genuinely small, which is why this is a 30-second unblock rather than a re-review: @TustinOC or @bassilkhilo-ag2 — a re-approve at Flagging rather than merging on the strength of three approvals because merging is irreversible in practice, and I approved a different PR into four red checks earlier today by trusting a summary instead of measuring. The measurement is cheap; the mistake is not. |
|
Correcting my earlier comment on this PR: I gave one reason for excluding @TustinOC's approval and there were two. I wrote that 2 of the 3 approvals predated the head commit and counted only the at-head ones. True, and the merge was correct. But the implication — that TustinOC's approval would have counted had it been fresh — is false. TustinOC is not a collaborator on this repo, so it never counted at all. Measured after the fact, with a stranger as control: I originally used Why I am correcting a merged PR: the reasoning in that comment is what someone reads next time they wonder whether a stale-looking approval can be revived by a re-approve. For TustinOC it cannot, and my note says otherwise by omission. The merge stands — it had two at-head approvals from actual collaborators (@bassilkhilo-ag2 re-approved at head, @john-the-dev at head). Trap surfaced by @sutando-bassil and @john-the-dev independently; the membership endpoint by @john-the-dev. |
tests/bench-claim-backends-smoke.test.pytakes ~100s and has timed out under coverage instrumentation on unrelated PRs — #3356 and #3509 both went red on it today, and neither touches this code.Where the time goes
Not the matrix
--quickselects. One hardcoded number:2000 history × 2 backends ≈ 4,000 claims. Profile of the unpatched run:
Each claim is a locked acquire plus a flushed write, so the claim count is the runtime.
Why it is safe to cut
archive_*is a throughput measurement. The smoke test asserts onlycrash.ok,conflict.okandprocs_*.exactly_once— and nothing anywhere assertsarchive_*:So
--quickpaid for numbers no caller reads. This scales the history with--quickand keeps the shape — same code paths, and the JSON key staysarchive_2kfor any consumer.Non-quick runs are untouched:
_arch_histis 2000 unless--quickis passed, so published benchmark numbers keep their scale.Before / after — same tree, same machine
Correctness output unchanged. Line 1 is the deliberately-mocked
_verdict=Falsecontrol printingFAIL: correctness invariant violated; line 40 isOK — quick matrix in-process, all correctness invariants held.What I got wrong first, since it is load-bearing for the diagnosis
Two earlier attempts measured zero improvement (98.62s, 98.85s against a 99–101s baseline). Both patched
_select_matrix, on the theory that--quick's matrix drove the cost. It does not — the 2000 never passes through_select_matrixat all. The first attempt was also profiled in a checkout 33 commits behind the tree I then measured against, so the 51s it "fixed" was not where that tree spends its time.🤖 Generated with Claude Code
Amendment after review (head
49658635)Three corrections to what is written above. Appending rather than editing in place, since reviewers have already read the original.
1. The body's grep was scoped to
tests/(line 27) and that was too narrow. @TustinOC and @bassilkhilo-ag2 each independently re-ran it across the whole tree, unscoped and without an extension filter, and confirmed no consumer outside the two print lines in the script itself. The conclusion held; the check that established it was theirs, not mine.2. "the JSON key stays
archive_2kfor any consumer" was true and misleading, and it hid a real defect. @john-the-dev found it: the key stayed resolvable while what it denoted changed. The print label was a hardcoded literal reading an aliased key, so--quickprinted a 20-item measurement labeled2k:JSON keys from that same before-run:
scenarios.a -> ['archive_0', 'archive_20', 'archive_2k']. Before this branch that line was slow and true; the first version of this branch made it fast and wrong, which is worse — nothing in the output said it had changed. No consumer grep could see it, because the defect was in the meaning of the key rather than in who reads it. Fixed by deriving the label from the same_arch_histthat gates the measurement.3. "no published figure moves" is right about the figure and wrong about the output. The non-quick label text does change,
complete @ 2k archive->complete @ 2000 archive. The measurement is untouched, but anything grepping the literal2kin this output stops matching. Flagging it here rather than letting the original phrasing imply byte-identical output.On the scope argument for the fix: I said the assignment and the print are "both inside
main()", which @john-the-dev correctly pointed out permits aNameError— the assignment sits at indent 8 inside a loop, the print at indent 4 after it. What actually guarantees_arch_histis bound is that the loop iterates a literal,for kind in ("a", "c")(line 296), so it cannot be empty. A loop over a computed list would have satisfied my weaker claim and still crashed.Verification at
49658635: smoke testrc=0at 41.00s, so the speedup survives the fix. The non-quick arm was not run locally (100s+); its label was verified by evaluating both arms of the expression, which speaks to the label only and not to the timing figures.