Skip to content

refactor(explore): let the explore round own the stack it builds and the ledger it writes - #1965

Merged
chaojhou merged 50 commits into
mainfrom
feature/chenyi/cc-explore
Oct 22, 2026
Merged

chaojhou merged 50 commits into
mainfrom
feature/chenyi/cc-explore

Conversation

@chennyiiis

@chennyiiis chennyiiis commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What this does

Moves the explore round out of the long ExploreExecutor.__call__ body into _ExploreRound, in the same explore.py module. The round owns the accumulating launch stack, its budget, the measured fingerprints and the result ledgers. _record centralizes the common variant-row fields and projects them through _REJECTED_FIELDS and _LOSER_FIELDS; rejected and losers remain separate lists.

The production diff against main is confined to explore.py: +1061/-1269, net -208 lines. The file is 1834 lines and ExploreExecutor.__call__ spans 16 lines. The other changed files are test_explore_executor.py and scripts/code_metrics_baseline.txt.

The existing per-turn restatement and deduplication behavior is preserved: _drop_leading_noops removes only leading restatements; _restates_stack evaluates later variants against the stack after prior KEEPs; launched_fps contains variants actually launched. Authored framework levers are computed once and passed as a list to _lever_seeded_payload, so they are measured and attributed even when the LLM proposes no grid.

Observable effect

The persisted explore_search_update.rejected rows gain stage and workspace for decision-stage failures/rejections, and error_class and server_log_path for warmup failures. The losers field keeps its existing projection. Consumers that inspect the persisted rejected rows can now locate the failing stage and its artifacts.

The internal helper filter_baseline_noop_variants takes observed=(config, env) in place of separate observed-config and observed-env keywords; its test callers are updated. Grid helpers remain in explore.py. No module is added and the private _MULTI_VALUE_FLAGS name remains unchanged.

The eval opt-out removed on main stays removed. A warm decision round still uses the accuracy result collected by its warmup, while a cold round retains its own eval.

Validation and size

The branch contains main at bb2da63332dcbc46bdf10702c516ef43eb80a016. The explore executor tests pass on Linux/Python 3.10. The lever regression cases drive the executor and the real grid runner on the forced-cold path, stubbing lifecycle eligibility and the benchmark subprocess and disabling grid warmup. They assert the seeded variant order, successful result and measured per-lever attribution both with and without an LLM proposal. Ruff check and format check pass.

size-exception: this in-place rewrite changes 2330 production Python lines even though its net reduction is 208. The methods share one round's evolving stack and settlement order; this PR keeps the round refactor together so those invariants can be reviewed and tested as one change. Existing module-size debt stays recorded; the baseline is tightened, not raised.

The round record it compacts at the end, the seed grid a round with no LLM
variants falls back to, the lever variants it puts in front of that grid, and
the row one tested variant contributes.

__call__: 1179 -> 1030 lines, cognitive 369 -> 304, cyclomatic 76 -> 61

vulture caught a real defect in the first cut of _seed_lever_variants: the
lifted body kept its old indent and ended up under the early return, so lever
seeding would have silently stopped happening. No test covered it.

Observable effect: none -- pure refactor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chennyiiis
chennyiiis requested a review from a team as a code owner October 17, 2026 08:50
@chennyiiis chennyiiis added the baseline-raise Waives new, worse and baseline-growth code-metrics findings; reason required in the PR description label Oct 17, 2026
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Code metrics gate: PASSED

Judged: the 3 files this change touches. Files it does not touch never fail it; anything found there is listed under Outside this change for information.

Dimension Limit New Worse Baseline out of date Baseline entries vs base
Cyclomatic complexity fails > 20 0 0 0 16 -1
Cognitive complexity fails > 30 0 0 0 98 -1
Function length (lines) fails > 80 0 0 0 236 -2
Nested block depth fails > 5 0 0 0 8 -1
Parameter count fails > 5 0 0 0 87 -1
Module length (lines) warns > 800, fails > 1200 0 0 0 47 +0
Duplicated lines per file fails > 0 0 0 0 95 +0
Dead code (vulture) fails > 0 0 0 0 2 +0
Check Findings Effect
Comments 0
English only 0
Production code imports test code 0
Repeated literals 0
A1 Private name of another package 0
A8 Flag argument 0
A9 Tuple return 0
A10 Raw vocabulary comparison 0
A11 Hidden global write 0
A12 Pure forwarder 0
A12 Single-implementation abstraction 0
71 findings in files this change does not touch

Outside this change (informational): 71

Unit Dimension Baseline Now
src/hyperloom/orchestrator/state/shared_state.py resolve_graded_comparison Cognitive complexity absent 34
src/hyperloom_kb/collect/mapping.py _experience Cognitive complexity absent 31
src/kernelforge/cli/forge_loop_command.py forge_loop Cognitive complexity absent 31
src/kernelforge/loop/runner.py IterationLoop._collect_session_candidate Cognitive complexity absent 44
src/kernelforge/loop/runner.py IterationLoop._rule_on_attempt Cognitive complexity absent 38
src/hyperloom/inference_optimizer/cli/__init__.py _reexport_session_environment Function length (lines) absent 107
src/hyperloom/inference_optimizer/cli/__init__.py _resolve_agent_lineup Function length (lines) absent 89
src/hyperloom/inference_optimizer/cli/__init__.py _resolve_run_topology Function length (lines) absent 92
src/hyperloom/inference_optimizer/multi_node/scripts/launch_multinode.py _build_parser Function length (lines) absent 84
src/hyperloom/inference_optimizer/multi_node/scripts/launch_multinode.py _spawn_rank_actors Function length (lines) absent 84
src/hyperloom_kb/sync.py GlobalSync.pull Function length (lines) absent 84
src/kernelforge/loop/runner.py IterationLoop._close_iteration Function length (lines) absent 117
src/kernelforge/loop/runner.py IterationLoop._collect_session_candidate Function length (lines) absent 185
src/kernelforge/loop/runner.py IterationLoop._implementer_history Function length (lines) absent 112
src/kernelforge/loop/runner.py IterationLoop._rule_on_attempt Function length (lines) absent 102
src/kernelforge/loop/runner.py IterationLoop._source_attempt Function length (lines) absent 175
src/hyperloom_kb/http_service.py Module length (lines) absent 1525
src/kernelforge/cli/forge_loop_command.py Module length (lines) absent 2003
src/hyperloom/orchestrator/actions/executors/_kv_metrics.py port_from_server_log Nested block depth absent 6
src/hyperloom/orchestrator/actions/executors/_grid_runner.py run_grid Cognitive complexity 177 180
src/hyperloom/orchestrator/specialists/dispatch.py SpecialistDispatchCollaborator.warm_specialist_params Cognitive complexity 93 94
src/hyperloom/orchestrator/actions/executors/_grid_runner.py run_grid Cyclomatic complexity 55 56
src/hyperloom/agents/quantization/driver/retry.py quantize_via_prompt Function length (lines) 102 103
src/hyperloom/inference_optimizer/breakdown/exporter.py build Function length (lines) 132 136
src/hyperloom/inference_optimizer/cli/__init__.py _run_optimize Function length (lines) 273 279

...and 46 more; the job log lists every row.

Verdict computed by the gate scripts in /home/runner/work/_temp/code-metrics-gate, not by this tree's copy.

Thresholds and sources: [tool.hyperloom.code_metrics] in pyproject.toml. Tools: complexipy 8.0.1, jscpd 5.4.1, ruff 0.16.2, vulture 2.16.

…f the grid

This is the path vulture found broken in the first cut of _seed_lever_variants
and no test covered. It now has three: the fresh levers go first, one the grid
already names is not seeded twice, and a round with no levers keeps its grid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chennyiiis chennyiiis closed this Oct 19, 2026
@chennyiiis chennyiiis reopened this Oct 19, 2026
chennyiiis and others added 3 commits October 19, 2026 12:07
The push that merged main fired only Secret Scan on this SHA; the rest of the
workflows stayed on the previous one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lore

# Conflicts:
#	scripts/code_metrics_baseline.txt
Test and others added 2 commits October 19, 2026 16:56
…er it writes

ExploreExecutor.__call__ was one 1000-line method: a 561-line try whose loop read
45 names from the enclosing scope and wrote 5 back, with bare break/continue
steering it. The round's state is now three records -- the stack each variant
launches on, the ledger of what it decided, the budget it spends -- and the
executor's methods operate on them, so the loop reads as a driver: admit the
variant, stage it, measure it, record the verdict, tear down its server. A
variant's rounds end in a named _RoundStep the driver dispatches on instead of a
break the reader has to trace.

The variant grid moves to _variant_grid.py beside it: where a round's variants
come from and which of them the launch already satisfies is a job of its own,
and explore.py was past the module-length ceiling with the round machinery in it.
filter_baseline_noop_variants takes the materialization inputs the way its
sibling filter does, which is what brings it back under the function-length
limit.

No observable change: internal structure only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lore

# Conflicts:
#	scripts/code_metrics_baseline.txt
#	src/hyperloom/inference_optimizer/tests/test_explore_executor.py
#	src/hyperloom/orchestrator/actions/executors/explore.py
@chennyiiis chennyiiis changed the title refactor(explore): name four jobs ExploreExecutor.__call__ ran inline refactor(explore): let the explore round own the stack it builds and the ledger it writes Oct 19, 2026
Comment thread src/hyperloom/orchestrator/actions/executors/_variant_grid.py Fixed
@chennyiiis chennyiiis added size-exception Waives the PR diff budget; added by a maintainer with a reason in the PR description and removed baseline-raise Waives new, worse and baseline-growth code-metrics findings; reason required in the PR description labels Oct 19, 2026
Test and others added 2 commits October 19, 2026 18:44
…f their owner

The baseline-noop filter asks whether a variant names a flag whose ordering a
merged config cannot preserve. That question belongs to the module that knows
the flags, so the tuple is `MULTI_VALUE_FLAGS` rather than a private name an
orchestrator module reaches into.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chaojhou chaojhou left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR #1965 -- refactor(explore): let the explore round own the stack it builds and the ledger it writes

What it does: PR #1965 moves the round state of ExploreExecutor.call (CC 76 -> 2) into _ExploreRound / _VariantStack / _RoundLedger / _RoundBudget plus named helpers, moves the variant-grid code (payload, levers, seed grid, noop and context-copy filters) into a new executors/_variant_grid.py, and renames grid_server_args._MULTI_VALUE_FLAGS to MULTI_VALUE_FLAGS. At its merge base it behaves the same as before, which a 26-scenario differential script confirmed. It is still blocked: the branch conflicts semantically with main, which has since rewritten the same explore code in #1918 and #1984.

Blocking issues: 1

  1. [conflict-explore-1918-1984] CONFLICTING with main: a naive resolution reverts #1918 (per-turn restatement/dedup) and #1984 (eval opt-out removal) [verified]
    Problem: The PR rewrote explore.py from merge base ef93838, but main has since changed the same logic. a8c4aac/#1918 now judges baseline-noop restatements and in-round duplicates at each variant's turn, against the stack as KEEPs have moved it. f7f64ac/#1984 removed the explore eval opt-out. At head, _runnable_variants (explore.py:1326) and _one_variant_per_fingerprint (1362) still run the old round-open noop filter and dedup, answered_its_grid (1115) still counts only ALREADY_MEASURED, and _explore_eval_disabled (154), _Workload.eval_disabled and the warmup_gv eval-off logic (1478-1500) are still there. src/hyperloom/orchestrator/actions/executors/explore.py:1326
    Impact: #1918 is silently reverted: a variant that restates the stack only after a KEEP is launched and measured again, a duplicate whose twin was only skipped is dropped instead of run, and a round answered by restatements reports failed. #1984 is reverted too: params.disable_run_eval turns accuracy eval off for explore again. mergeable=CONFLICTING blocks on its own.
    Action: Rebase onto origin/main. Port #1918's _launched_stack/_restates_stack (observed_launch_after), launched_fps dedup, leading-noop trimming and judged_without_measuring into _ExploreRound and _measure_the_grid. Drop _explore_eval_disabled, _Workload.eval_disabled, the RUN_EVAL override and warmup_gv as #1984 did. Regenerate code_metrics_baseline.txt. Then re-check equivalence against the new main.

Checked: ExploreExecutor.call (base explore.py:801-1987) vs head call/_open_round/_materialize_workload/_proposed_grid/_r (yes); filter_baseline_noop_variants (moved to _variant_grid.py:416, kwargs folded into **build via _materialized_launch) (yes); moved grid helpers (_action_reasoning, _grid_variants_from_payload, framework_lever_grid, _framework_lever_attributions, (yes); grid_server_args._MULTI_VALUE_FLAGS -> MULTI_VALUE_FLAGS (yes); consumers / monkeypatch targets of explore (yes) | Conflict with main semantic | Base: ef93838 | Head: 9029126

@ZhengGong-amd

Copy link
Copy Markdown
Collaborator

Requesting changes: behaviour is equivalent at the merge-base, but the branch conflicts with main in a way that would silently revert #1918 and #1984, and the refactor grows production code instead of shrinking it.

Production code: +2064/−1616 (net +448, +22%) across explore.py, _variant_grid.py and grid_server_args.py, plus 21 new types (16 NamedTuples, _ExploreRound and its 4 parts, _RoundStep, _RoundRefusal), with no mechanism removed. A complexity refactor should shrink the code. Bring the net to zero or below by deleting the mechanisms listed below. Moving them into new types does not count.

Blocking

  1. Semantic conflict with main at src/hyperloom/orchestrator/actions/executors/explore.py:1326. If the conflict is resolved in favour of this PR, two main changes are silently reverted. The first is fix(explore): judge a baseline restatement against the stack it launches on #1918: head still runs the noop filter and dedup once when the round opens (_runnable_variants, _one_variant_per_fingerprint), while main judges each variant against the KEEP-advanced stack (judged_without_measuring). The second is Remove the accuracy eval opt-out #1984: head still carries the eval opt-out (_explore_eval_disabled:154, _Workload.eval_disabled:248, the eval-off branch in _stage_variant:1484-1500). Rebase onto main, port fix(explore): judge a baseline restatement against the stack it launches on #1918's per-variant restatement into the round, drop the eval opt-out, regenerate the baseline and rerun the equivalence diff.

Simplify instead of relocate

  • Delete _RoundStep (explore.py:222). budget.stop is not None already carries the same state.
  • Delete the single-use bags _RoundSetup, _RoundGrid, _GradingAnchor, _RoundLaunch and _KeepComposition, and merge _GradedVerdict/_AccuracyVerdict/_VariantVerdict into one record. Move the _stage_variant/_launch_round/_judge_variant staticmethods onto _ExploreRound so that one owner holds the round's behaviour.
  • Drop the double write to rejected/losers (explore.py:965-997, 1007-1038). Derive losers from rejected, since writeback.py:2911,2936 reads only its truthiness and its length.
  • Delete _ExploreRound.serving_lease/release_serving_lease. Acquire and release the lease in one try/finally inside _measure_the_grid.
  • Keep _variant_grid.py, but finish the move. Migrate loop/intent_router.py:868 and the 5 test files that still import grid helpers from explore. Update the patch target at test_delegate_denial_loop.py:559, and update the explore.py references in SKILL.md:1130 and references/frameworks.md:27.

Test and others added 6 commits October 20, 2026 11:01
…lore

Carries #1918 and #1984 into the rewritten round by hand rather than by
taking either side of the conflict.

#1918 judges a baseline restatement against the stack it launches on, and
the rewrite had restructured exactly that code. In the round's new shape:
the grid keeps only the restatements ahead of every other variant
(``_past_the_opening_restatements``), each later variant is judged at its
turn against the stack a KEEP has moved (``_ExploreRound.restates_the_stack``,
over ``observed_launch_after``), an in-round duplicate is one whose
fingerprint this round actually launched (``launched_already`` /
``record_launch``), and a round whose variants were all answered without
measuring succeeds.

#1984 removes the accuracy-eval opt-out: ``_explore_eval_disabled``, the
``disable_run_eval`` task param, the ``SharedState.eval_disabled`` read and
the forced ``RUN_EVAL=false`` warmup variant are gone; the decision round
still skips eval when a warmup took the score.

``_variant_control_fields``, the fingerprint stamping and the baseline-noop
skip record move to ``_variant_grid``, which owns the variant grid, keeping
``explore.py`` shorter than the base branch's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The grid helpers are imported from `_variant_grid` by every caller, so
`explore` is no longer a second route to them: `intent_router`, the five
test modules that still reached through it, and the two reference docs now
name the owner.

`_variant_grid` imports `_framework_switch_manifest` directly instead of
through the package, which drops the `explore -> executors -> explore`
module-level import cycle main has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes four mechanisms rather than moving them:

- the parallel `losers` list, a second row per refused variant carrying a
  subset of what `rejected` already holds. The result's `losers` key is that
  one list; refused rows now also carry `workspace` on the measured path;
- `_RoundStep`, which restated in an enum what `_RoundBudget.stop` already
  records when a run stops the round;
- `_ExploreRound.release_serving_lease`, with the lease now taken and closed
  by the one loop that uses it;
- `_RoundGrid` and `_GradingAnchor`, two single-use pairs the round's own
  fields already name.

`_stage_variant`, `_launch_round`, `_warm_the_server`, `_measure_variant`,
`_judge_variant` and the grid loop were static methods whose first argument
was the round; they are the round's methods, so the executor now opens a
round, asks it to measure its grid, and reads the result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tested, warmup-failure and refusal rows repeated the same six-key
identity header; they now share one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test and others added 15 commits October 21, 2026 11:37
Resolves the code_metrics_baseline.txt ratchet conflict with
resolve_baseline.py. main's #1782 additive r9700/gfx1201 docs in SKILL.md
and references/frameworks.md and its two new monkeypatch.setenv lines in
test_custom_framework.py all survive alongside our explore/_variant_grid
split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lever grid, observed launch, launch delta and multi-value-flag readers
were never over a gated threshold, so they keep their own bodies, their
docstrings and the module-qualified switch-manifest calls; grid_server_args
goes back untouched with its private multi-value flag table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lore

# Conflicts:
#	scripts/code_metrics_baseline.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ers it writes

ExploreExecutor.__call__ was a 1159-line method holding the round's inputs, its
accumulating stack, four closures over them and six ledger accumulators. The
round is now an object that owns those, and each ledger row is a projection of
the one row the round records per variant instead of a fourth restatement of it.

Deleted on the way: the second reading of the shared state, the hand-written
float/int parsing the common coercions already do, the duplicated materialization
inside filter_baseline_noop_variants (it now calls _materialized_launch, as the
speculative-decoding filter beside it already did), the per-call repetition of
the launch and stack keywords, the dead "not_keep" fallback (every non-KEEP path
sets a reason), and the "if runnable" guard around a grid that cannot be empty.

Observable: a rejected-ledger row now carries the same fields whichever round
produced it -- a warmup failure gains error_class and server_log_path, a decision
rejection gains stage and workspace. A non-finite or boolean baseline_runtime_sec,
baseline_warm_runtime_sec, base_tput or accuracy baseline now reads as missing
rather than raising or being counted as 1.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mapping

_framework_of handed back the benchmark envs where the round expected the
benchmark mapping, so the recipe's own server args and the agentx check both
read empty and a KEEP persisted only the variant's own flags.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…settles

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…'s fields

The loser view is the variant's fields plus the round's verdict, and the rejected
view is the loser view plus where the round stopped, so each field name is
written once. The winners-history row reads off the variant's row directly, and
one reader answers for the canonical fingerprint a variant was stamped with.

The warmup round now says whether it left a server to decide on, so its status
is read where it is known rather than again by the caller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@ZhengGong-amd ZhengGong-amd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR #1965 -- refactor(explore): let the explore round own the stack it builds and the ledger it writes

What it does: Rewrites ExploreExecutor.__call__ in place in explore.py. The round now runs in a new _ExploreRound class, and __call__ only builds the params and output root before delegating to it. The diff against main touches 3 files (explore.py, test_explore_executor.py, scripts/code_metrics_baseline.txt) and is net -208 production lines.

Blocking issues: 4

  1. [free:lever-seeding-dropped] Framework-rewrite lever variants are never seeded, so lever attribution stops happening [verified]
    Problem: _resolve_grid sets self.lever_payload = framework_lever_grid(self.ss) and then calls _lever_seeded_payload(payload, self.lever_payload). That call passes the already-built lever list where shared state is expected. _lever_seeded_payload calls framework_lever_grid(ss) a second time, and getattr(list, "authored_framework_levers", None) is None, so it always returns []. src/hyperloom/orchestrator/actions/executors/explore.py:1083-1084, :775-777, :252-256. The real writer is record_authored_framework_levers, called at src/hyperloom/orchestrator/loop/writeback.py:3000. On main, the seeding read extra["shared_state"] directly (explore.py:924 at base), so main does not have this bug. No existing test covers this path.
    Repro: run ExploreExecutor with a shared state that holds pending authored_framework_levers, stubbing only run_with_session_kill.

    • Empty params.grid: base runs 5 additive (or 4 leave-one-out) fwlever_* variants, succeeds and returns framework_lever_attributions. Head returns status=failed, error_class=empty_grid.
    • params.grid with one LLM variant: base runs the fwlever_* variants plus v_llm and attributes a lever. Head runs only v_llm and returns framework_lever_attributions=[].
      Impact: Rewrite levers written by writeback never get an attributed gain, so the loop cannot tell which lever paid off. A round that relies only on levers fails with empty_grid instead of running.
      Action: Change the call to _lever_seeded_payload(payload, self.ss), or change _lever_seeded_payload to take the precomputed self.lever_payload and drop its inner framework_lever_grid call. Then add an executor-level test that seeds authored_framework_levers and asserts both the fwlever_* variants and a non-empty framework_lever_attributions.
  2. [free:observable-effect-wrong] The Observable effect section names the wrong ledger and leaves out the fields that actually change [verified]
    Problem: The live body says "each refused variant's losers entry now carries the fields its rejected row carries" and "no flag, env var, default, exit code or persisted field moves". At head the facts are different:

    • losers is unchanged. It is still _ledger_view(row, _LOSER_FIELDS) (explore.py:837, :1600), and the recorded base-vs-head differential shows no difference in losers.
    • The persisted explore_search_update.rejected rows do gain fields (_REJECTED_FIELDS, explore.py:838-846, written at :1599). Decision-stage rows gain stage and workspace. Warmup-failure rows gain error_class and server_log_path (explore.py:1420-1421). The differential counted 71/71/22/22 occurrences.
    • filter_baseline_noop_variants takes a new keyword observed=(config, env) and passes the remaining arguments through as **build (explore.py:577-581). Outside explore.py, only tests call it.
      Impact: The release notes would tell operators that losers entries change and that no persisted field moves. In fact losers is unchanged and the persisted rejected rows gain four fields, so anyone parsing the ledger is told to look in the wrong place.
      Action: Rewrite Observable effect to say that losers is unchanged, list the four new rejected fields and which row kinds get each one, and state the filter_baseline_noop_variants signature change. Write the body against git diff origin/main...HEAD only; every name and number must be reproducible from that diff.
  3. [free:body-names-absent-code] The body describes an abandoned two-module design and lists deletions of code that never existed on main [verified]
    Problem: The live body names code that is missing from both the head and main:

    • "_variant_grid.py (671 lines)", "grid_server_args.MULTI_VALUE_FLAGS", "Every caller -- intent_router, five test modules, two reference docs -- now reads the grid helpers from _variant_grid" and the removed "explore -> executors -> explore ... import cycle". At head there is no _variant_grid.py, grid_server_args.py is untouched, and the flag set is still the private _MULTI_VALUE_FLAGS (grid_server_args.py:360, imported at explore.py:93).
    • Under "What is gone": "_RoundStep", "release_serving_lease", "_RoundGrid and _GradingAnchor". git grep for these names at origin/main (and at the merge-base 3a54819) returns nothing, so there was nothing to delete.
    • "the result's losers key is a view of it". At head, self.rejected and self.losers are still two separate lists, both appended in _record (explore.py:1599-1600).
    • The methods "_stage_variant, _launch_round, _warm_the_server, _measure_variant, _judge_variant" and the #1918 names "_past_the_opening_restatements", "_ExploreRound.restates_the_stack", "launched_already" do not exist at head. The actual methods include _drop_leading_noops (:1152), _restates_stack (:1226), _measure (:1341), _warmup (:1375) and _graded_verdict (:1490).
    • "_RoundSetup, _RoundLaunch and _KeepComposition ... are kept" and "_GradedVerdict and _AccuracyVerdict". None of these exist at head. The only classes are _RoundRefused, _ExploreRound and ExploreExecutor.
    • "the modules that imported a grid helper from explore now import it from _variant_grid, and test_geak_revalidation_dispatch patches ...". At head, the only test change is _noop_filter_kwargs in test_explore_executor.py, adapted to the new filter_baseline_noop_variants(observed=...) signature.
      Impact: The release notes would announce a new module, a newly public grid_server_args name, an import-cycle fix and the removal of four mechanisms. None of them ship, so operators and downstream readers would look for code that does not exist.
      Action: Replace "What is gone", "The new module", the #1918 paragraph and the test paragraph with the names the head actually contains (_ExploreRound and its methods, _ledger_view/_REJECTED_FIELDS/_LOSER_FIELDS, _drop_leading_noops, _restates_stack). Delete every claim about _variant_grid, MULTI_VALUE_FLAGS, the import cycle and the five migrated test modules. Write the body against git diff origin/main...HEAD only; every name and number must be reproducible from that diff.
  4. [free:body-numbers-wrong] The size numbers, and the size-exception reason built on them, do not match the head diff [verified]
    Problem: Several numbers in the live body disagree with the head:

    • "It is now eight lines" / "Function length (lines) ... 8". At head, ExploreExecutor.__call__ is 16 lines (explore.py:1805-1820).
    • "explore.py is 1798 lines" and "the two modules are 2469, a net +427". At head, explore.py is 1834 lines and is the only production module changed. git diff --numstat 3a548190f ad42c957f gives explore.py +1061/-1269, which is net -208.
    • "size-exception: the diff is 2469 production Python lines across two modules, of which 671 are the new module's". That premise is false, yet the size-exception label is still applied.
      Impact: The release notes would report a +427-line, two-module change with a size waiver. The real change is a -208-line, single-module rewrite, and the waiver label rests on a reason that no longer holds.
      Action: Restate the table and the line-count paragraph from the head diff (__call__ 16 lines, explore.py 1834 lines, net -208). Then either remove the size-exception label or give a reason that holds for a 3-file, net -208 diff. Write the body against git diff origin/main...HEAD only; every name and number must be reproducible from that diff.

Checked: src/hyperloom/orchestrator/actions/executors/explore.py (framework_lever_grid, _lever_seeded_payload, _ExploreRound._resolve_grid, _record, _warmup_failed, _LOSER_FIELDS, _REJECTED_FIELDS, filter_baseline_noop_variants, ExploreExecutor.__call__), src/hyperloom/orchestrator/loop/writeback.py:3000, src/hyperloom/inference_optimizer/grid_server_args.py (_MULTI_VALUE_FLAGS), src/hyperloom/inference_optimizer/tests/test_explore_executor.py, live PR body | Ran: base-vs-head executor differential for lever seeding (run_with_session_kill stubbed); recorded base-vs-merged ledger differential over 13 explore test files; trial merge with origin/main b772e94 (clean); code_metrics gate on merged tree (PASSED); ruff check/format; pytest over 13 explore test files (770 passed); git grep of body-named symbols at head and at origin/main; git diff --numstat | Base: 3a54819 | Head: ad42c95

Test and others added 9 commits October 21, 2026 20:08
…er list

``_lever_seeded_payload`` took a ``shared_state`` and re-derived the lever
grid from it, but the round had already computed that grid one line above and
handed it the list instead. ``framework_lever_grid`` read
``authored_framework_levers`` off a list, found nothing, and returned ``[]``,
so no ``fwlever_*`` variant was ever seeded: a round with only levers refused
as ``empty_grid``, and a round with LLM proposals reported no lever
attributions.

The round owns the list -- it needs it again to attribute the outcomes -- so
the helper now takes it rather than a second source to derive it from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lore

# Conflicts:
#	scripts/code_metrics_baseline.txt
Resolve scripts/code_metrics_baseline.txt by keeping main's entries and
applying this branch's removals and lowered values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lore

Only scripts/code_metrics_baseline.txt conflicted. Resolved with the
ratchet rule: whichever side records the lower value wins, an entry the
merge base had and a side dropped is retired, an entry absent from the
base is kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lore

Brings the branch up to origin/main; no path conflicted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lore

Brings the branch up to origin/main; no path conflicted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolve scripts/code_metrics_baseline.txt by keeping main's entries and
applying this branch's removals and lowered values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve scripts/code_metrics_baseline.txt by keeping main's entries and
applying this branch's removals and lowered values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chaojhou chaojhou left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR #1965 -- refactor(explore): let the explore round own the stack it builds and the ledger it writes

What it does: ExploreExecutor.__call__ ran a whole explore round inline (1124 lines, cyclomatic 76) with the stack, budget, grading anchor and ledgers held as closure locals. The PR moves that state and the per-variant steps onto a private _ExploreRound in the same explore.py, and builds the rejected and loser rows from one recorded row through _REJECTED_FIELDS / _LOSER_FIELDS. The only observable change, which the description states, is that persisted explore_search_update.rejected rows gain stage/workspace (decision stage) and error_class/server_log_path (warmup failures). filter_baseline_noop_variants now takes observed=(config, env).

Blocking issues: none

SKIPPED: V4 -- openprs.txt is PARTIAL for #1869/#1276/#1154/#1032 (only their first 100 files were compared).

Checked: explore.py at base vs head, unit by unit: __call__ (base 794-2030) against ExploreExecutor.__call__, _ExploreRound.__init__, _materialized_round_config, _anchor_the_params, _framework_of, _resolve_grid, _lever_seeded_payload, _seed_grid_payload, _shape_runnable, _drop_leading_noops, _open_schedule, _refused, _restates_stack, _already_measured, _out_of_budget, _refuses_context_copy, _staged, _measure, _warmup, _warmup_failed, _settle, _graded_verdict, _apply_accuracy_gate, _record, _keep, _per_variant_outcomes/_outcome_row, _result, _budget_error and _last_round_summary; also filter_baseline_noop_variants and filter_context_copy_drafts (via _materialized_launch). Readers of the rejected rows: phase_state._merge_rejected, prelude.py:271 and prompt_builder.py:798. The callers and monkeypatch targets of explore names were grepped across the tree. The independent reader re-verified the prev-card fix and the updated callers. | Ran: pytest test_explore_executor.py on head (142 passed) and on base (140 passed). The same 8 tests fail on both because of the local Windows environment. The new lever tests also pass when run on the base tree. git merge-tree origin/main <head> is clean. | Base: bb2da63 | Head: ebbb11d | Branch head ab678b2 merges main into the reviewed head: baseline file only, non-baseline diff identical to the reviewed head

LGTM

@chaojhou
chaojhou merged commit a4bdd38 into main Oct 22, 2026
40 checks passed
@chaojhou
chaojhou deleted the feature/chenyi/cc-explore branch October 22, 2026 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size-exception Waives the PR diff budget; added by a maintainer with a reason in the PR description

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants