From 89a2734bfff279bfa93c59588d93f9b8e6c16698 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 13 Mar 2026 05:21:05 +0000 Subject: [PATCH] fix: address review findings for diverse seed population (#638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - evolve.sh: fix fail-in-subshell bug — run seed-gen-cli as a direct command so its exit code is checked by the parent shell and fail() aborts the script correctly; redirect stderr to log file instead of discarding it with 2>/dev/null - seed-generator.ts: reorder enumerateVariants() to put STAKED_THRESHOLDS outermost (192 entries/block) so that selectVariants(6) with stride=192 covers all 6 staked% thresholds; remove false doc claim about "first variant is current seed config"; add comments explaining CI=0n is intentional in all presets - seed-gen-cli.ts: emit a stderr diagnostic when count exceeds the 1152-variant cap so the cap is visible rather than silently producing fewer files than requested - test: strengthen n=6 test to assert all STAKED_THRESHOLDS values are represented in the selected variants Co-Authored-By: Claude Sonnet 4.6 --- tools/push3-evolution/evolve.sh | 11 +++++- tools/push3-evolution/seed-gen-cli.ts | 8 ++++ tools/push3-evolution/seed-generator.ts | 37 +++++++++++++------ .../test/seed-generator.test.ts | 10 ++++- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/tools/push3-evolution/evolve.sh b/tools/push3-evolution/evolve.sh index 2870f19..3d2ea4e 100755 --- a/tools/push3-evolution/evolve.sh +++ b/tools/push3-evolution/evolve.sh @@ -279,15 +279,22 @@ if [ "$DIVERSE_SEEDS" = "true" ]; then # --- Diverse-seeds mode: use seed-gen-cli to produce parametric variants --- # Generate up to POPULATION variants; any shortfall is filled by mutating the seed. SEED_VARIANTS_DIR="$WORK_DIR/seed_variants" + SEED_VARIANTS_LIST="$WORK_DIR/seed_variants_list.txt" VARIANT_IDX=0 + # Run seed-gen-cli as a direct command (not inside <(...)) so its exit code is + # checked by the parent shell and fail() aborts the entire script on error. + # Stderr goes to the log file for diagnostics rather than being discarded. + run_seed_gen_cli --count "$POPULATION" --output-dir "$SEED_VARIANTS_DIR" \ + > "$SEED_VARIANTS_LIST" 2>>"$LOG" \ + || fail "seed-gen-cli.ts failed to generate variants" + while IFS= read -r VARIANT_FILE && [ "$VARIANT_IDX" -lt "$POPULATION" ]; do CAND_FILE="$GEN_DIR/candidate_$(printf '%03d' $VARIANT_IDX).push3" cp "$VARIANT_FILE" "$CAND_FILE" printf '0\n' > "${CAND_FILE%.push3}.ops" VARIANT_IDX=$((VARIANT_IDX + 1)) - done < <(run_seed_gen_cli --count "$POPULATION" --output-dir "$SEED_VARIANTS_DIR" 2>/dev/null \ - || fail "seed-gen-cli.ts failed to generate variants") + done < "$SEED_VARIANTS_LIST" # Fill any remaining slots with mutations of the seed (fallback) while [ "$VARIANT_IDX" -lt "$POPULATION" ]; do diff --git a/tools/push3-evolution/seed-gen-cli.ts b/tools/push3-evolution/seed-gen-cli.ts index 362b069..f785d7a 100644 --- a/tools/push3-evolution/seed-gen-cli.ts +++ b/tools/push3-evolution/seed-gen-cli.ts @@ -54,6 +54,14 @@ if (!outputDir) { mkdirSync(outputDir, { recursive: true }); const variants = selectVariants(count); +if (variants.length < count) { + process.stderr.write( + `[seed-gen] Note: --count ${count} exceeds the 1152-variant parameter space;` + + ` generating ${variants.length} variants. The remaining ${count - variants.length}` + + ` slots in evolve.sh will be filled by mutating the seed.\n`, + ); +} + for (let i = 0; i < variants.length; i++) { const text = generateSeedVariant(variants[i]!); const filename = `variant_${String(i).padStart(3, '0')}.push3`; diff --git a/tools/push3-evolution/seed-generator.ts b/tools/push3-evolution/seed-generator.ts index 1d599c5..2bff72f 100644 --- a/tools/push3-evolution/seed-generator.ts +++ b/tools/push3-evolution/seed-generator.ts @@ -41,7 +41,13 @@ export const STAKED_THRESHOLDS: readonly number[] = [80, 85, 88, 91, 94, 97]; /** Penalty thresholds: penalty < N → bull; else → bear. */ export const PENALTY_THRESHOLDS: readonly number[] = [30, 50, 70, 100]; -/** Bull output parameter variants (higher confidence / tighter ranges). */ +/** + * Bull output parameter variants (higher confidence / tighter ranges). + * + * capitalInefficiency is intentionally fixed at 0 across all variants — the + * current seed (optimizer_v3.push3) uses CI=0 in all branches, and varying it + * is left to the mutation operators once evolution starts. + */ export const BULL_VARIANTS: readonly BullBearParams[] = [ // Aggressive bull (current seed): maximum confidence, tight anchor { capitalInefficiency: 0n, anchorShare: ONE_E18, anchorWidth: 20, discoveryDepth: ONE_E18 }, @@ -53,7 +59,11 @@ export const BULL_VARIANTS: readonly BullBearParams[] = [ { capitalInefficiency: 0n, anchorShare: 700_000_000_000_000_000n, anchorWidth: 50, discoveryDepth: 700_000_000_000_000_000n }, ]; -/** Bear output parameter variants (defensive / wide ranges). */ +/** + * Bear output parameter variants (defensive / wide ranges). + * + * capitalInefficiency is intentionally fixed at 0 for the same reason as BULL_VARIANTS. + */ export const BEAR_VARIANTS: readonly BullBearParams[] = [ // Standard bear (current seed) { capitalInefficiency: 0n, anchorShare: 300_000_000_000_000_000n, anchorWidth: 100, discoveryDepth: 300_000_000_000_000_000n }, @@ -249,17 +259,21 @@ export function generateSeedVariant(params: SeedVariantParams): string { /** * Enumerate all parameter combinations in a deterministic order. * + * STAKED_THRESHOLDS is the outermost loop so that even-stride sampling in + * selectVariants(n) naturally covers all staked% values: each block of + * (4 penalty × 4 bull × 4 bear × 3 tax) = 192 entries maps to one threshold. + * * Total = |STAKED_THRESHOLDS| × |PENALTY_THRESHOLDS| × |BULL_VARIANTS| * × |BEAR_VARIANTS| × |TAX_DISTRIBUTIONS| * = 6 × 4 × 4 × 4 × 3 = 1152 */ export function enumerateVariants(): SeedVariantParams[] { const result: SeedVariantParams[] = []; - for (const taxDistribution of TAX_DISTRIBUTIONS) { - for (const stakedThreshold of STAKED_THRESHOLDS) { - for (const penaltyThreshold of PENALTY_THRESHOLDS) { - for (const bull of BULL_VARIANTS) { - for (const bear of BEAR_VARIANTS) { + for (const stakedThreshold of STAKED_THRESHOLDS) { + for (const penaltyThreshold of PENALTY_THRESHOLDS) { + for (const bull of BULL_VARIANTS) { + for (const bear of BEAR_VARIANTS) { + for (const taxDistribution of TAX_DISTRIBUTIONS) { result.push({ stakedThreshold, penaltyThreshold, @@ -278,11 +292,12 @@ export function enumerateVariants(): SeedVariantParams[] { /** * Select `n` diverse variants from the full parameter space. * - * When n ≤ total combinations (1152), samples evenly so each axis of variation - * is represented. When n > total, returns all combinations. + * When n ≤ total combinations (1152), samples evenly across the enumeration so + * each axis of variation is represented. Because STAKED_THRESHOLDS is the + * outermost loop, selectVariants(6) picks one representative per staked% + * threshold (stride = 1152/6 = 192, one block per threshold value). * - * The first variant is always the current seed configuration - * (stakedThreshold=91, penaltyThreshold=50, bull/bear=defaults, exponential). + * When n > total, returns all 1152 combinations. */ export function selectVariants(n: number): SeedVariantParams[] { if (n < 1) throw new RangeError('n must be at least 1'); diff --git a/tools/push3-evolution/test/seed-generator.test.ts b/tools/push3-evolution/test/seed-generator.test.ts index 4538175..f08209e 100644 --- a/tools/push3-evolution/test/seed-generator.test.ts +++ b/tools/push3-evolution/test/seed-generator.test.ts @@ -243,10 +243,16 @@ describe('selectVariants', () => { }); it('n=6 covers all staked thresholds via even stride', () => { - // 1152 / 6 = stride of 192; the 6 selected entries span the full combination space + // enumerateVariants puts STAKED_THRESHOLDS outermost (192 entries per threshold). + // stride = 1152/6 = 192 → one representative per staked% value. const variants = selectVariants(6); expect(variants).toHaveLength(6); - // Each variant is valid + const stakedValues = new Set(variants.map(v => v.stakedThreshold)); + expect(stakedValues.size).toBe(STAKED_THRESHOLDS.length); + for (const st of STAKED_THRESHOLDS) { + expect(stakedValues.has(st)).toBe(true); + } + // Each representative is a valid Push3 program for (const v of variants) { expect(parseAndValidate(generateSeedVariant(v))).toBe(true); }