Repository navigation
refactor(orchestrator): pass phase, kernel, knowledge and loop inputs as typed objects instead of parallel keywords - #1977
Conversation
Code metrics gate: PASSED with waived findings (
|
| Dimension | Limit | New | Worse | Baseline out of date | Baseline entries | vs base |
|---|---|---|---|---|---|---|
| Cyclomatic complexity | fails > 20 | 0 | 0 | 0 | 69 | +0 |
| Cognitive complexity | fails > 30 | 0 | 0 | 0 | 275 | +0 |
| Function length (lines) | fails > 80 | 0 | 11 | 0 | 414 | -2 |
| Nested block depth | fails > 5 | 0 | 0 | 0 | 26 | +0 |
| Parameter count | fails > 5 | 0 | 0 | 0 | 95 | -70 |
| Module length (lines) | warns > 800, fails > 1200 | 0 | 10 | 0 | 51 | +0 |
| Duplicated lines per file | fails > 0 | 0 | 0 | 0 | 97 | -1 |
| 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 |
Worse than the baseline (waived by the baseline-raise label): 21
| Unit | Dimension | Baseline | Now |
|---|---|---|---|
src/hyperloom/inference_optimizer/cli/__init__.py:2483 _run_optimize |
Function length (lines) | 273 | 279 |
src/hyperloom/orchestrator/actions/executors/integrate_patch.py:3249 IntegratePatchExecutor._enablement_keep_records |
Function length (lines) | 211 | 213 |
src/hyperloom/orchestrator/actions/executors/integrate_patch.py:4240 IntegratePatchExecutor._maybe_write_framework_kb_record |
Function length (lines) | 114 | 117 |
src/hyperloom/orchestrator/enablement/build.py:386 EnablementBuild._enqueue_build_launch_probe |
Function length (lines) | 84 | 86 |
src/hyperloom/orchestrator/enablement/lane.py:516 EnablementLane.maybe_rearm_enablement |
Function length (lines) | 104 | 108 |
src/hyperloom/orchestrator/kernel/conc_sweep.py:716 _sweep_one_arm_single_server |
Function length (lines) | 328 | 338 |
src/hyperloom/orchestrator/kernel/controller_patch_integration.py:243 integrate_controller_patches |
Function length (lines) | 257 | 259 |
src/hyperloom/orchestrator/knowledge/recipe_journal.py:745 RecipeJournalCollaborator._record_fact_per_variant |
Function length (lines) | 114 | 116 |
src/hyperloom/orchestrator/phases/framework.py:1250 FrameworkPhase._maybe_rearm_authored_lane |
Function length (lines) | 103 | 104 |
src/hyperloom/orchestrator/phases/framework.py:1962 FrameworkPhase._submit_framework_agent_candidate_for_review |
Function length (lines) | 97 | 98 |
src/hyperloom/orchestrator/phases/framework.py:2206 FrameworkPhase.maybe_reauthor_from_critic_feedback |
Function length (lines) | 131 | 133 |
src/hyperloom/inference_optimizer/cli/__init__.py:1 |
Module length (lines) | 2569 | 2815 |
src/hyperloom/orchestrator/actions/executors/integrate_patch.py:1 |
Module length (lines) | 4741 | 4748 |
src/hyperloom/orchestrator/kernel/conc_sweep.py:1 |
Module length (lines) | 1429 | 1450 |
src/hyperloom/orchestrator/knowledge/recipe_journal.py:1 |
Module length (lines) | 1785 | 1800 |
src/hyperloom/orchestrator/loop/coordinator.py:1 |
Module length (lines) | 1423 | 1442 |
src/hyperloom/orchestrator/loop/dispatcher.py:1 |
Module length (lines) | 1503 | 1557 |
src/hyperloom/orchestrator/phases/framework.py:1 |
Module length (lines) | 3139 | 3167 |
src/hyperloom/orchestrator/phases/machine_state.py:1 |
Module length (lines) | 2189 | 2198 |
src/hyperloom/orchestrator/specialists/runner.py:1 |
Module length (lines) | 1520 | 1525 |
src/hyperloom/orchestrator/specialists/subprocess_.py:1 |
Module length (lines) | 1747 | 1770 |
How to fix
- New or worse: split the unit (extract a function, a class, a module) until it is back under the limit, or no worse than its recorded value. The baseline cannot absorb a new violation: CI refuses any baseline entry added or raised relative to the base branch.
- Baseline out of date: something improved. Run
python scripts/code_metrics.py --update-baselineand commitscripts/code_metrics_baseline.txt; the command only lowers or removes entries. - Override: the
baseline-raisePR label waives new, worse and baseline-growth findings and the failing design checks (they stay listed here). Use it only with the reason in the PR description; it waives nothing else. - Tools for a local run:
pip install ruff==0.16.2 complexipy==8.0.1 vulture==2.16andnpm install -g jscpd@5.4.1(or pointCODE_METRICS_JSCPDat another jscpd command, such asnpx --yes jscpd@<version>).
Module length warning: over 800 lines, within the 1200-line failure limit (not failing): 4
| Module | Lines | Limit |
|---|---|---|
src/hyperloom/orchestrator/enablement/lane.py |
892 | warns > 800 |
src/hyperloom/orchestrator/kernel/attempt_summary.py |
1008 | warns > 800 |
src/hyperloom/orchestrator/phases/close.py |
1010 | warns > 800 |
src/hyperloom/orchestrator/specialists/dispatch.py |
1055 | warns > 800 |
Already over at the base (backlog, not this change's): 11
| Unit | Dimension | Base | Now |
|---|---|---|---|
src/hyperloom/inference_optimizer/cli/__init__.py:1814 _reexport_session_environment |
Function length (lines) | 111 | 111 |
src/hyperloom/inference_optimizer/cli/__init__.py:2376 _resolve_agent_lineup |
Function length (lines) | 89 | 89 |
src/hyperloom/inference_optimizer/cli/__init__.py:2271 _resolve_run_topology |
Function length (lines) | 92 | 92 |
src/hyperloom/inference_optimizer/cli/__init__.py:1927 _resume_session |
Function length (lines) | 170 | 170 |
src/hyperloom/inference_optimizer/cli/__init__.py:2099 _seed_fresh_session |
Function length (lines) | 170 | 170 |
src/hyperloom/orchestrator/enablement/lane.py:91 EnablementLane._maybe_enqueue_enablement_specialist |
Function length (lines) | 94 | 94 |
src/hyperloom/orchestrator/kernel/conc_sweep.py:1187 run_conc_sweep |
Function length (lines) | 251 | 249 |
src/hyperloom/orchestrator/knowledge/recipe_journal.py:143 _record_config_attempts |
Function length (lines) | 142 | 142 |
src/hyperloom/orchestrator/prompts/prompt_builder.py:616 _section_decision_framework |
Function length (lines) | 104 | 104 |
src/hyperloom/orchestrator/specialists/runner.py:559 SpecialistRunner._prepare |
Function length (lines) | 199 | 199 |
src/hyperloom/orchestrator/prompts/prompt_builder.py:1 |
Module length (lines) | 1217 | 1212 |
70 findings in files this change does not touch
Outside this change (informational): 70
| Unit | Dimension | Baseline | Now |
|---|---|---|---|
src/hyperloom/orchestrator/enablement/recipe/projections.py project_launch_evidence |
Cognitive complexity | absent | 36 |
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/hyperloom_kb/schema.py FieldDeclaration.__post_init__ |
Cognitive complexity | absent | 32 |
src/kernelforge/cli/forge_loop_command.py _prepare_measurement_driver |
Cognitive complexity | absent | 36 |
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start |
Cognitive complexity | absent | 40 |
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/multi_node/cli.py _restart_multinode |
Function length (lines) | absent | 107 |
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/cli/forge_loop_command.py _RunReporter.build_result |
Function length (lines) | absent | 91 |
src/kernelforge/cli/forge_loop_command.py _prepare_measurement_driver |
Function length (lines) | absent | 136 |
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start |
Function length (lines) | absent | 149 |
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 | 184 |
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._run_supervisor_review |
Function length (lines) | absent | 88 |
src/kernelforge/loop/runner.py IterationLoop._source_attempt |
Function length (lines) | absent | 173 |
src/hyperloom_kb/http_service.py |
Module length (lines) | absent | 1525 |
src/kernelforge/cli/forge_loop_command.py |
Module length (lines) | absent | 2063 |
src/hyperloom/orchestrator/actions/executors/_geak_sweep.py sweep_via_geak |
Cognitive complexity | 67 | 70 |
src/hyperloom/orchestrator/actions/executors/explore.py ExploreExecutor.__call__ |
Cognitive complexity | 369 | 374 |
...and 45 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.
…and close the lane from LaneEnd and RoundSettlement
…d fan it out as one PodPatchApply
…Server, and drop the unused num_prompts_factor parameter
… land a patch with one LandingContext
…utcome and drop the unused session_dir
…emote publish audit from one RemoteRecipeAudit
…Request, and drop the pass-through canonical_id_from_components
…the caller already holds
…gs and AdvisoryServices and drop the unused sub_agent_runner and bus_class
…r holds as one HeldLeases
… authoring retries from one record each
…tory rows, transitions and lifecycle marks from one record each
…xt, PromptCycle and PromptAssets
… drop the unused stderr_truncate and dedup_key parameters
…re and run a rebench from RebenchBenchmark and RebenchServer
…rvest and trace a turn from its RunnerContext
…d reap it from one ReapWatch
…r signature refactor
…does not import revalidation
96da298 to
0302478
Compare
…the dispatcher takes
PR #1977 -- refactor(orchestrator): pass phase, kernel, knowledge and loop inputs as typed objects instead of parallel keywordsWhat it does: Replaces the long keyword lists of 70 Blocking issues: none deferred: none Step 7 independent reader: a second reader re-ran the ScriptedSpecialist pin test against the pre-fix stand-in (fails) and the fixed head (passes), and re-swept the tree for other stand-ins mirroring a changed signature. Checked: new commit f2cb120 (rehearsal/specialist.py:101, test_scripted_backends.py, test_bringup_round_scenario.py); earlier finding re-verified fixed (signatures of ScriptedSpecialist.run and SpecialistSubprocessDispatcher.run both LGTM |
chaojhou
left a comment
There was a problem hiding this comment.
PR #1977 -- refactor(orchestrator): pass phase, kernel, knowledge and loop inputs as typed objects instead of parallel keywords
What it does: brings the rest of src/hyperloom/orchestrator (loop, phases, kernel, knowledge, enablement, bringup, specialists, prompts, scoring, rehearsal) under the parameter-count limit. 70 functions now take the record their values belong to (CoordinatorSettings / RunOptions, PhaseFlags, KernelPatchRequest, FactRecord, SpecialistSpawn and others), existing types are reused where they are the same concept (CriticIteration, PhaseBudget, KBSelectionProfile, ServingTarget), six parameters nobody passed or read and the canonical_id_from_components pass-through are deleted, and the rehearsal ScriptedSpecialist.run is pinned to the dispatcher's signature. Prompts, persisted state, KB and journal rows and CLI argv are unchanged.
Blocking issues: none
Non-blocking:
- Records that only exist to pass the gate:
PhaseFlagsis built from the same three getters in three places in machine.py and once in close.py, and aphase_flags()query beside that state would make it one decision.rehearsal/launch.py_LaunchRequestrepeatsRecordedLaunch's fields,LaneEndmostly repeatsLaneTerminal, andScorerCallKeyscarries three ofLLMCallSite's join keys and is copied into one field by field. - Parameters kept only for tests:
GenerationScan.attempts = 2andRecipeFiltersare set by no production caller, the same ground on which this PR deletes six other parameters._SweepRun.num_prompts_factornow always holdsDEFAULT_NUM_PROMPTS_FACTOR. - The new records that callers in other packages build (
CoordinatorSettings/AdvisoryServices/RunOptions,SpecialistSpawn, andRunnerLinks/BootStreams/RecipeFilters) are not in their modules'__all__, whileFrameworkOutcome,ConcCurveOptionsand the rebench records are. - Positional constructions of same-typed neighbours:
HistoryEntry(from_phase, to_phase, reason, ...)at machine_state.py:1983 and theResultsLocationunpack in attempt_summary.py:756; keywords would keep a later field reorder from swapping them silently. NamedTuple and frozen dataclass records are mixed within one package. - Docstrings that still name a parameter now inside a record:
_record_enablement_round(stop_reason,attempt),build_execution_row(outcome),_reap_loop(progress_cb),apply_kernel_patch(snapshot_dir/patch_path/target_file);SpecialistSpawn.worktree_basedescribes the diff base, which isworktree_base_commit. - The breaking-changes line could name
Coordinator(sub_agent_runner=, bus_class=)and the re-exportedMockRowScanBackend(dedup_key=)besidecanonical_id_from_components.
Checked: every hunk of the 51 production files in six module groups for field-to-keyword mapping, defaults, eager-vs-lazy reads, frozen records holding caller-mutated objects, positional order, and call sites including monkeypatch fakes and getattr dispatch; callers of the six deleted parameters and canonical_id_from_components across src, scripts, examples, docs and SKILL.md; the PhaseBudget and GenerationScan moves (no cycle; import-linter passes); description figures (function-args 165 -> 95 and the per-function and per-module line deltas match the base and head files) | Ran: base vs head on 192+ orchestration prompt variants (identical hash), Coordinator.run in three stop scenarios, 275 machine_state decision and history cases, apply_kernel_patch (dry-run, apply, shell rebuild, missing patch), conc-sweep plans, KB writeback / RecipeKB put-search-get / journal and audit rows, ladder classify and setup-ledger rows (all identical with clocks normalised); an AST sweep of every call to a changed signature against inspect.signature (0 bad keywords); git merge-tree against origin/main and the open #1957-#1965 (all clean); CI green at head. Not run: pytest (not installed here) | Base: a40eec4 | Head: f2cb120
LGTM
…ignature, before and after main's #1977 Main's #1977 (a452748) takes the scope, metric, value and objective as one KBSelectionProfile. PR #1869 tests the merge with main, where the write-back and CLOSE tests failed with a TypeError. Drop the old keywords once main is merged into multibranch-dev past a452748. Co-authored-by: Cursor <cursoragent@cursor.com>
…ain's #1977 expects Co-Authored-By: Claude <noreply@anthropic.com>
Description: what and why
Brings the rest of
src/hyperloom/orchestrator(outsideactions/,state/,trace_analysis/, which refactor(orchestrator): pass actions, state and trace-analysis inputs as typed objects instead of parallel keywords #1972 covered) under the parameter-count limit (function-args> 5). Many of these functions were helpers extracted from large phase, kernel and knowledge functions that took the caller's locals, or took the fields of one record, request or launch as separate keywords. Each now takes the object the values belong to:CoordinatorSettings,AdvisoryServices,RunOptions,HeldLeases,RunnerLinks.CloseArtifact,ProposalStep,AuthoredAttempt,ProgressStamp,AuthoringRetry,PhaseFlags,HistoryEntry,PhaseClock,LifecycleMark.KernelPatchRequest,PodPatchApply,ResultsLocation,ConcCurveOptions,MergeLabels,ConflictQuery,LandingContextand private rung / arm records for the conc sweep.FrameworkOutcome,FactRecord,RemoteRecipeAudit,RecipeFilters;write_if_bettertakes theKBSelectionProfileits caller already holds.GenerationScanlives in its ownenablement/generation_scan.pysobuild.pydoes not importrevalidation.py):LaneEnd,RoundSettlement,KeepEvidence,SetupExecution,GenerationScan,ProbeTarget,BootStreams,ReconcileStores/ReconcileLinks/ReconcileLimits,ClassifiedFailure,RebenchBenchmark,RebenchServer,RunOutcome,RunHarvest,SpecialistSpawn,ReapWatch,SessionContext,PromptCycle,PromptAssets,ScorerCallKeys.ScriptedSpecialist.runtakes the sameSpecialistSpawnas the dispatcher it replaces, and a test pins the two signatures together.CriticIterationincritic_agent,PhaseBudgetin the predictor (its definition moves frompredictor/pump.pytopredictor/payload.py, the module that takes it, so the two do not import each other),KBSelectionProfilein the remote recipe store,ServingTargetfor the setting script.canonical_id_from_components(a pass-through torecipe_canonical_idwith one internal caller) is removed (evidence below).No
**kwargs, untyped dict, compatibility shim or new fallback branch is added; every call site is updated. Coordinator collaborator properties,ACTION_CATALOGUEandKERNEL_REQUEST_HANDLERSentries keep their names and call shapes.Scope: 81 functions over the limit outside the ten orchestrator files other open PRs are restructuring (none of which is touched); 70 are done. 11 keep their signatures:
check_server_argv,integrate_controller_patches,_geak_revalidation_decision,PatchKB.stage_provenance,LocalRecipeStore.put_recipe,record_lifecycle_eventandsnapshot_source_layerbecause a caller lives in one of those ten files;ScriptedLaunchBackend.runbecause it stands in forrun_with_session_kill, which those files call by keyword;KBStoreClient.put_knowledge/put_file/put_dirbecause_vendor/kb_store_client.pyis a byte-identical upstream copy.Gate count of functions over the parameter limit (
function-argsentries inscripts/code_metrics_baseline.txt): 165 on main -> 95 (-70); the cyclomatic-complexity entries and the broad-exceptcount are unchanged.Linked issue(s): none.
Tests: added/updated? commands run?
run_task_registered,_finalizeand_reap_loopdirectly are updated to the new signatures.machine_statedecisions and rows;kb_writeback, setup-ledger and remote-recipe audit rows; the setting script; the predictor payload; recipe-KB search and audit events;apply_kernel_patchresults and manifests; the attempt summary and scorer traces.python:3.10-slimandpython:3.11-slim.PYTHONPATH=src(never the full suite): every test file underorchestrator/{bringup,enablement,loop,phases,rehearsal,kernel,actions/executors}, 259inference_optimizertest files that import a changed module, and 3kernelforgetest files. All pass except 4 TraceLens-dependent tests that fail identically on the base here (TraceLens is not installed locally).inspect.signature(...).bindcontract tests and monkeypatch fakes) is clean.ruff check/ruff format --checkon the changed files;python scripts/code_metrics.py --base-ref origin/main: parameter count -70, duplication and design checks 0; the remaining findings are the length items below.Size/complexity triggers crossed: split, or why the shape is right:
.pyfiles). One concern (parameter count of the remaining orchestrator modules and their call sites). Each commit touches at most 5 production files and is grouped by module.baseline-raise: 21 length findings, each in a function or module already over its limit at the base. The lines this PR adds come from wrapping keyword arguments into a record constructor at existing call sites (an opening and closing line per record) and, for modules, from the record types defined in the module that owns them. Lines added relative to the base: functionscli/__init__.py::_run_optimize+6,integrate_patch.py::_enablement_keep_records+2 and_maybe_write_framework_kb_record+3,enablement/build.py::_enqueue_build_launch_probe+2,enablement/lane.py::maybe_rearm_enablement+4,conc_sweep.py::_sweep_one_arm_single_server+10,controller_patch_integration.py::integrate_controller_patches+2,recipe_journal.py::_record_fact_per_variant+2,framework.py::_maybe_rearm_authored_lane+1,_submit_framework_agent_candidate_for_review+1,maybe_reauthor_from_critic_feedback+2; modulescli/__init__.py+10,integrate_patch.py+7,conc_sweep.py+21,recipe_journal.py+13,loop/coordinator.py+19,loop/dispatcher.py+7,phases/framework.py+23,phases/machine_state.py+4,specialists/runner.py+5,specialists/subprocess_.py+23. The alternatives are formatting tricks, which this PR does not use.If this simplifies or refactors: which mechanism was removed, which contract was preserved, and the tests that show it:
Removed: the parallel keyword lists and the
canonical_id_from_componentspass-through. Preserved: persisted state, KB, journal and ledger rows, prompts, and the name-resolved wiring, shown by the base-vs-head comparisons above.Deleted parameters (checked across src, scripts and tests): never passed:
Coordinator(sub_agent_runner=, bus_class=),run_conc_sweep(num_prompts_factor=),invoke_runtime_cli(stderr_truncate=),MockRowScanBackend(dedup_key=); never read:write_framework_record(session_dir=),LocalRecipeStore.search(prefer=)(the body diddel prefer).Observable effect: what an operator will now see, and what the old behaviour cost them (if none, why the change is unobservable):
None: no output file,
state.jsonfield, KB record, prompt, CLI flag or log line changes.Breaking changes: yes, for Python callers only: the refactored functions take their typed objects, and
canonical_id_from_componentsis no longer exported fromhyperloom.orchestrator.knowledge.recipe_kb(userecipe_canonical_id). Every in-tree caller is updated.PR addresses single concern: yes (parameter count of the remaining orchestrator modules).
Root cause is upstream (Magpie/TraceLens/GEAK/IntelliKit/AgentKernelArena), ticket filed: n/a.