Repository navigation
refactor(prelude): read a warm-start prior into named values before PRELUDE acts on it - #1958
Conversation
…tle_warm_replay _settle_warm_replay held two settlements in one body: the drift rejection for a replay that did not reproduce its prior, and -- inside a 206-line branch -- every path a reproduced replay can take. Lift the second one out and name four jobs inside it: the checkout that would not promote, the patches that actually landed, the audit metadata a replayed stack entry carries, and the journal append. _settle_warm_replay: 390 -> 197 lines, cognitive 113 -> 48, cyclomatic 43 -> 25 _ReplayMeasurement replaces six loose throughput locals that the settlement threaded by hand; hot_tput, single_round_tput and tput were the same value by the time any of them was read. Observable effect: none -- pure refactor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code metrics gate: PASSEDJudged: the 4 files this change touches. Files it does not touch never fail it; anything found there is listed under Outside this change for information.
85 findings in files this change does not touchOutside this change (informational): 85
...and 60 more; the job log lists every row.
Thresholds and sources: |
…RELUDE acts on it Every unit in prelude.py now measures within the complexity ceiling. Two of them carried most of the weight: maybe_enqueue_warm_replay (497 lines, cyclomatic 48) derived a replay request and refused it at eleven gates in one body, and _prepare_warm_kernel_kb (269 lines, cyclomatic 33) staged, landed, settled and persisted the kernel half in another. The derivation moves to a sibling module. _warm_prior.py reads the records a prior arrives as -- its history, its kernel section, a donor's recommendation -- into values PRELUDE can act on: a launch overlay and where it came from, the kernel plan and the overlay it brings, the gain anchor, the checkouts an overlay was measured on. It reads no session state and writes none, so what remains in PreludePhase is the state the replay moves through and the refusals it publishes. Four mechanisms are gone rather than relocated: * the kernel unwind that four branches of the enqueue path spelled out separately is one _unwind_warm_kernel_half; * the staging loop that _preview_current_kernel_config and _prepare_warm_kernel_kb each carried is one _staged_warm_kernel_plan; * the donor field list that maybe_enqueue_warm_replay restated beside _WARM_REPLAY_DONOR_FIELDS is read from the constant; * warm_recipe_proven_items and inject_warm_recipe_history_into_ledger both walked recipe -> attrs -> exact_history by hand; both now ask warm_recipe_history_rows. _WarmReplaySettlement carries the four values every branch of a settlement threaded by hand (the result, the task, the outcome it writes and the recorder it rules on), which is what leaves room under the parameter limit for the gates to be named. Observable effect: none -- pure refactor. No behaviour, interface, default, flag or artifact changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eplay units Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Updated: every function in The derivation half of @AMD-AGI/Hyperloom#1962 is stacked on this branch and rewrites |
… preparation does Two refusals on the enqueue path had no test of their own: which record a replay's kernel launch overlay is read from, and what a kernel preparation that failed with a patched tree still standing does about it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lude-warm-replay # Conflicts: # scripts/code_metrics_baseline.txt # src/hyperloom/orchestrator/phases/prelude.py
chaojhou
left a comment
There was a problem hiding this comment.
PR #1958 -- refactor(prelude): read a warm-start prior into named values before PRELUDE acts on it
What it does: PR #1958 splits the ten over-ceiling functions in phases/prelude.py into named helpers. The warm-prior parsing moves into a new sibling module, phases/_warm_prior.py, and the 15 prelude baseline entries are dropped. Route: extract-function refactor plus four de-duplications (kernel unwind, staging loop, donor field list, history-row walk). Behaviour is equivalent on every path I drove with base-vs-head differential scripts, except one narrow failure path in _settle_warm_replay, which now persists and logs error_class even when the rollback itself fails.
Blocking issues: 1
- [settle-failed-error-class-before-rollback] A failed replay whose rollback also fails now persists error_class and logs 'warm-replay failed', contradicting 'Observable effect: None' [verified]
Problem: In base, the status!=succeeded branch ran _require_combined_warm_rollback first and only stamped outcome['error_class'] and logged 'warm-replay failed (...)' once the rollback succeeded. Head stamps error_class and logs at lines 1963-1964 before _fail_warm_replay runs the rollback. When the rollback fails, the rollback_failed outcome that _require_combined_warm_rollback persists now carries error_class, and the INFO line is emitted. src/hyperloom/orchestrator/phases/prelude.py:1963
Impact: Several operator-visible artifacts change: state.warm_replay_outcome gains error_class, and the warm-replay breakdown event verdict.error_class (warm_replay_event._verdict reads settled['error_class']) goes from '' to 'Boom'. A new INFO log line also appears. The PR states no observable change. The new behaviour is arguably more informative, but it is undisclosed in a PR billed as a pure refactor.
Action: Either restore the base order: run the rollback first, then stamp error_class and log (e.g. check _require_combined_warm_rollback in the branch before setting error_class), or state the change in the PR's Observable effect section.
Checked: maybe_enqueue_warm_replay (+ _read_replayable_current_recipe, _prepared_warm_kernel_section, _refuse_dirty_kernel_prepar (yes); _prepare_warm_kernel_kb (+ _staged_warm_kernel_plan, _settle_empty_warm_kernel_plan, _record_warm_kernel_progress, _appl (yes); _settle_warm_replay (+ _warm_replay_measurement, _record_tput_gate, _record_replay_measurement, _fail_warm_replay, _judg (no); _judge_warm_replay_accuracy (+ _read_replay_accuracy, _admit_unjudged_replay_accuracy, _record_accuracy_gate) (yes); _resolve_promoted_recipe_checkout / rollback_combined_warm (+ _replayed_patch_trees, _promotable_tree, _restore_replayed (yes); _merge_named_current_recipe_configs -> _warm_prior.merge_named_current_recipe_configs (yes); _warm_kernel_extra_envs (yes); inject_warm_recipe_history_into_ledger / warm_recipe_proven_items (+ warm_recipe_history_rows, warm_recipe_rejection, _r (yes); _collect_warm_kernel_plan (+ warm_kernel_plan_entry) (yes); _preview_current_kernel_config / _read_current_recipe_replay (yes) | Conflict with main none | Base: ef93838 | Head: 2b05075
…lude-warm-replay # Conflicts: # scripts/code_metrics_baseline.txt
|
Requesting changes: the split makes the complexity numbers pass, but grows production code by ~400 lines and changes one failure path despite "Observable effect: None". Production code: +1780/−1383 (net +397), plus 13 new NamedTuples (base had 0) and Blocking
Simplify instead of relocate
Tests: the 6 new tests call private helpers, and Metrics: |
…orted, after its trees come back The extraction had hoisted the error class and the line that reports it above the rollback, so a settlement that ended as rollback_failed carried a class and printed a line the same settlement never did before. Both now sit behind the rollback guard, where the original kept them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…onical coercion ``common.coerce.to_float`` answers 0.0 for a non-finite value, so swapping it in here would read an ``inf`` throughput as no throughput at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tore errors and nothing else Drives promote_warm_replay with a rollback that refuses: the settled outcome is rollback_failed carrying the restore errors, it carries no error_class, and no "warm-replay failed" line is logged. An extraction that lifts the class and the log above the rollback passes every other warm-replay test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@ZhengGong-amd @chaojhou thanks — both blocking findings are fixed. Point by point. Fixed1. Rebase / baseline conflict. 2. Test added as asked — I also re-ran the check mechanically rather than by eye: the set and count of every 3. Lead with the deletions. Done — the description now opens with the four mechanisms this removes rather than relocates, and states the net line count with the reason. Where I disagree, with the reasonNet +398 production lines. Agreed this is the wrong direction, and I do not think it can reach net-zero without declining to split the two functions at all. The deletions pay for part of it; what they cannot pay for is that eleven refusal paths which were The NamedTuples. Eight of the thirteen carry 3 or more fields (
Explicit
Tests pinning the method layout. The entry point under test is unchanged:
|
PR #1958 -- refactor(prelude): read a warm-start prior into named values before PRELUDE acts on itSelf-review, run with What it does: brings the ten over-ceiling units in Blocking issues: none at this head. The one that fired is fixed and is described above: Four further candidates were raised and refuted:
SKIPPED: Step 7 independent reader -- no second agent was available, so the refutation pass was run by the same agent that wrote the findings. What it rests on is mechanical and reproducible from the Ran: line. Checked: |
PR #1958 -- Step 7 independent reader, runThe card at head The reader was aimed at the two defect classes this refactor effort has actually produced. A statement that ended up on the other side of the check protecting it. Base and head A shared-helper swap that changes the domain. Absent outright. The PR introduces zero The "Observable effect: None" claim was re-tested mechanically rather than read: the multiset Checked: |
|
Production code is still too large. The net growth is +398 lines (it was +397), and it needs to keep shrinking before this can merge. I classified every changed line in the touched production files with an AST pass (merge-base vs head
Functions go from 50 to 123, record types from 0 to 13, and total parameters from 80 to 215. Of the 90 new definitions, 47 are referenced exactly once in The logic barely changed (-114). The growth is the scaffolding around single-use units: their signatures, docstrings, fields and spacing (about +584 lines). That is the same control flow in more places, not a simpler one (AGENTS.md Simplify by removing a mechanism). The parameter-count and tuple-return limits in Cut next:
Target: production net ≤ 0, or name each remaining new type in the description with what it removes. |
…lude-warm-replay # Conflicts: # scripts/code_metrics_baseline.txt
…lude-warm-replay # Conflicts: # scripts/code_metrics_baseline.txt
Review asked why these PRs add so many docstrings. The files already document 96-98% of their functions, so the growth is the function count the complexity ceiling forced, not a higher documentation rate. Every new helper keeps a docstring; the Args:/Returns: blocks on them go. Mechanical, via collapse_docstrings.py --added-vs origin/main: only functions this branch adds are touched, pre-existing functions, module and class docstrings are left alone. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…pse improved Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lude-warm-replay Only conflict was scripts/code_metrics_baseline.txt: took the lower value per entry, kept this branch's retirement of the PreludePhase warm-replay entries, and took main's lowered dispatch/parser/runner numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… sibling module Rework the warm-replay refactor delete-first. `phases/_warm_prior.py` is gone and its contents are back in `prelude.py`; every NamedTuple the split introduced is gone with it, and the functions that were under every repo threshold are back at their merge-base bodies. What this removes rather than relocates: - the `recipe -> attrs -> exact_history` walk, spelled out twice; - the primary-tree fallback, spelled out twice; - the kernel-half unwind and its pending restatement, spelled out four times; - the preparation rollback and its `ROLLED_BACK` marking, spelled out twice; - the `deferred` / `errors` counters, which restate the plan's own decisions; - the three local copies of `try: float(x) except: 0.0`, now `common.coerce.to_float`; - the `order` list beside `pairs`, the `prepared`/`decision_params` duplicate reads, the `current_remote` branch on a call whose default already matched, and a dozen guards whose producers are typed. Observable effects: - `to_float` rejects `bool` and non-finite values where the old inline coercions did not: a replay reporting `inf` or `True` for `output_throughput`, `warmup_round_tput`, `baseline_tput_anchor`, a warm-recipe `confidence` or a session `gain_pct` now reads as `0.0`. All five are benchmark readings where a non-finite value is not a measurement. - `_judge_warm_replay_accuracy` no longer swallows a non-numeric `shared_state.baseline_accuracy`; the field is typed `float`. Nothing else changes: the string constants and the `log` calls are identical to the merge base, and no outcome, pending or recorder key lost a writer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t seam Restore the no-argument preparation call a local prior takes, and name the uplift the replay gates grade on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…names The thirty-one one-line docstrings on the warm-replay helpers repeat the name above them and nothing else. Delete them and lower the module-lines baseline they inflated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolve the scripts/code_metrics_baseline.txt ratchet by keeping the lower value on each side. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chaojhou
left a comment
There was a problem hiding this comment.
PR #1958 -- refactor(prelude): read a warm-start prior into named values before PRELUDE acts on it
What it does: Splits the warm-replay lifecycle in phases/prelude.py (maybe_enqueue_warm_replay, _settle_warm_replay, _prepare_warm_kernel_kb, _judge_warm_replay_accuracy) into named stages and shares the kernel-rollback settle, patch-tree walk, history-row walk and donor field list that were previously written out two to four times; prelude.py shrinks by 163 lines. The stated behaviour changes are deliberate: warm-start numbers are read through hyperloom.common.coerce.to_float, so bool, NaN and inf values and invalid input take the fallback (a NaN replay throughput is now rejected by the throughput gate), and a negative warm-up throughput is kept in the audit-only cold_tput.
Blocking issues: none
SKIPPED: local pytest -- test modules import fcntl and do not collect on this Windows host; relied on CI test shards (py3.10/3.11, 6/6 each) green at the head.
Checked: prelude.py base vs head, unit by unit, for maybe_enqueue_warm_replay (+_read_replayable_current_recipe, _refused_dirty_warm_kernel, _warm_kernel_overlay, _enqueue_warm_replay, _warm_replay_launch_config, _replayable_patch_plan, _combined_warm_launch, _workload_compatible_launch, _warm_replay_task_params, _open_warm_replay_task, _warm_expected_gain); _settle_warm_replay (+_grade_warm_replay, _record_replay_measurement, _rule_on_warm_replay_keep, _adopt_reproduced_warm_replay, _push_warm_replay_onto_stack, _warm_replay_stack_entry, _warm_replay_candidate, _journal_warm_replay); _prepare_warm_kernel_kb (+_apply_warm_kernel_plan, _land_warm_kernel_entry, _settle_warm_kernel_preparation, _unwind_prepared_warm_kernel, _warm_kernel_persist_failure); _judge_warm_replay_accuracy; rollback_combined_warm / _resolve_promoted_recipe_checkout (+_warm_patch_trees); _collect_warm_kernel_plan; _warm_kernel_extra_envs; _merge_named_current_recipe_configs; inject_warm_recipe_history_into_ledger / warm_recipe_proven_items (+_warm_recipe_history_rows); agent_kb.prior_member/read*; shared_state field types; common/coerce.to_float; warm_replay_event.record_measurement | Ran: AST function-set diff base vs head (0 removed, 12 changed, 32 added); per-unit source diff; grep of external callers (loop/writeback.py:2105) and cold_tput/expected_gain_pct readers | Base: bb2da63 | Head: ac511a0
LGTM
What this does
Refactors warm replay inside
orchestrator/phases/prelude.py: kernel preparation and rollback, replay configuration and enqueueing, throughput/accuracy grading, and promotion are separate named stages. The history-row traversal, donor-field list, combined rollback, and kernel staging paths are shared by their callers. No module is added or removed.Relative to main
bb2da63332dcbc46bdf10702c516ef43eb80a016,prelude.pychanges by +843/-1006 (163 fewer production lines). Including 119 added test lines and the metrics baseline, the complete PR is 55 lines smaller. AST-counted docstring source spans remain 138 lines on each side.The existing
size-exceptioncovers 1849 changed production lines, counting additions plus deletions. Enqueue, rollback, grading and promotion exchange the same pending replay record, so the extracted stages and their callers change together. The remaining over-threshold plan/config readers retain their single parsing responsibilities and lower their recorded debt; moving them into sibling modules would not remove that responsibility or simplify the replay lifecycle.Observable effect
The normal replay contract is preserved: a replay must pass the throughput and applicable quality/accuracy gates before promotion; a failed required replay restores both recipe and kernel changes. On a failed replay, combined rollback still runs before the failure's
error_classis stamped or thewarm-replay failedlog is emitted. If rollback fails, its failure outcome is preserved without adding that replay error field or log.Numeric parsing now uses
hyperloom.common.coerce.to_floatfor replay confidence, expected gains, throughput and audit measurements. Invalid, boolean and non-finite inputs use the call site's fallback rather than being admitted as numbers or propagating a float-conversion exception. For example, non-finite replay throughput falls back to zero and is rejected by the throughput gate; an invalid donor expected gain falls back to zero. A negative warm-up throughput is now retained in the audit-onlycold_tputfield; main previously recordedNonefor a non-positive reading. The replay is still graded on its hot throughput.Complexity and validation
Measured with ruff 0.16.2 and complexipy 8.0.1 against the main commit above; lines are physical spans from
defthrough the last statement:maybe_enqueue_warm_replay_settle_warm_replayThe baseline diff removes 11 entries, lowers 5, and adds none. Remaining recorded debt includes cognitive complexity for
_collect_warm_kernel_plan42→33,_warm_kernel_extra_envs42→34, and_merge_named_current_recipe_configs39→32;_judge_warm_replay_accuracyfunction-lines 123→92; module-lines 2640→2529. This does not claim every helper is below every review threshold.Validation: 139 tests passed in
test_prelude_warm_kernel_kb.pyandtest_warm_replay.pyunder Linux/Python 3.10. Ruff lint and format checks passed. The head already contains the main commit named above.