From da90ad73b41825d8d2800b4a9e5bfc5465a56ac1 Mon Sep 17 00:00:00 2001 From: Ari Aye Date: Sun, 30 Aug 2026 18:41:47 -0700 Subject: [PATCH] fix: eliminate no-op knob mutations Two fixes to mutate_knob(): 1. Guided mutation no-op detection: when the reflector suggests a value that's already the current value (e.g. theory -> theory), skip it and fall through to random selection instead of wasting an eval slot. 2. Exclude synthetic _prompt_* knobs from random selection. These are created by PROMPT_MUTATE to persist prompts through compile() round-trips and should not be randomly selected by KNOB_MUTATE. Co-Authored-By: Claude Opus 4.6 (1M context) --- factory/outer_loop/mutations.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/factory/outer_loop/mutations.py b/factory/outer_loop/mutations.py index 5e8de8287..c6e237a34 100644 --- a/factory/outer_loop/mutations.py +++ b/factory/outer_loop/mutations.py @@ -724,8 +724,16 @@ def mutate_knob( new_val = float(new_val) except ValueError: pass - else: - knob_name = random.choice(knob_names) + # Skip no-op: guided value same as current + if str(new_val) == str(old_val): + new_val = None + guided_knob = None + if not guided_knob: + # Exclude synthetic _prompt_* knobs (handled by PROMPT_MUTATE) + real_knobs = [k for k in knob_names if not k.startswith("_prompt_")] + if not real_knobs: + return None + knob_name = random.choice(real_knobs) old_val = wf.knob_values[knob_name] bounds = wf.knob_bounds.get(knob_name, []) new_val = None