Skip to content

feat(custom-specialist): make the Coordinator own the custom specialist; add HYPERLOOM_CUSTOM_SPECIALIST_ONLY - #1876

Merged
xiaofei-zheng merged 10 commits into
mainfrom
feat/custom-specialist-coordinator-owned
Oct 15, 2026
Merged

xiaofei-zheng merged 10 commits into
mainfrom
feat/custom-specialist-coordinator-owned

Conversation

@lishuoshuo-amd

@lishuoshuo-amd lishuoshuo-amd commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator
  • Description: what and why
    The operator-defined custom_specialist could go unrun for a whole session. Three dispatch paths overlapped: Orchestration chose whether and when to delegate it; the Coordinator's at-least-once guarantee was held back by any queued task merely tagged custom_specialist (on a MiniMax-M3 AgentX run a serving task tagged that way waited 4.5 h for the GPU and kept the guarantee from ever firing); and a tag-only task flipped custom_specialist_dispatched for good. Once it did run, its proposals waited behind other explore grids, integrate_patch tasks and framework_agent_phase_done before anyone benched them, and a FRAMEWORK_AGENT plateau exit could leave before they ran.
    This PR makes the custom specialist Coordinator-owned:
    • Queued once per macro cycle by on_enter_framework, before Orchestration's first turn, in both modes, idempotency key custom-c{cycle}-{sha1(prompt)[:8]} (a resume with a new definition gets its own run). It enters router.handle_delegate directly, after the gate. A dispatch refused at admission (for example a patch-mode run that the phase budget cannot fit) is requested once per (cycle, definition), logged, and not retried within the cycle.
    • The gate denies, at intent ingress only, any delegate naming custom_specialist as domain or tag (specialist_custom_coordinator_owned); dispatch-time row replay does not deny it, so the Coordinator's own task runs.
    • dispatch_order gains an operator tier: the custom specialist and an explore marked operator_priority dispatch before the phase's dispatch-first kinds and the rest.
    • Custom proposals get their own explore grid (operator_priority), not gated by other explores or framework_agent_phase_done; the general bench leaves them out.
    • machine.py holds a FRAMEWORK_AGENT exit on optimize_no_more_leverage while operator work is pending; budget and terminal exits are never held (kept out of the frozen predicate inputs, whose v6 contract fixes FRAMEWORK pending_work to null).
    • New only mode, turned on by HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1 (no CLI flag). It is read in _run_optimize right after the operator's --extra-env pins are exported, so export and a pin both reach it; persisted in state.json, and on a resume it turns only mode on, never off; a non-boolean value or the env without the pair exits 2 before the session starts. Scoped to FRAMEWORK_AGENT:
      • Orchestration takes no reactor turn there (coordinator._tick_once): everything it could emit is denied, and its turn held the tick's dispatch back by 10-20 min on entry and again after the custom task finished. The ingress denial of its specialist/explore/integrate_patch stays as the backstop. The critic still runs and reviews the custom patch.
      • Discovery, local explore, scout, stalled-domain force, predictor and the general bench are skipped.
      • The custom task is dispatched with no_lease_extension, so its wall budget is the existing 4 h cap rather than its mode base (no Orchestration turn is left to extend it), still bounded by the phase and session ends. Admission keeps asking for the mode base only.
      • The phase hints skip_to_sweep once the custom task (or its refused admission), its grid and its patch reviews are done; the exit goes to KERNEL_AGENT when that phase is enabled, else SWEEP. Later phases run as usual.
  • Linked issue(s): none
  • Tests: added/updated? commands run?
    test_custom_specialist.py rewritten around the new contract (123 tests). Every new behaviour test was run on the unmodified code first and failed. These include:
    • test_the_coordinator_custom_task_passes_the_dispatch_time_gate_and_runs (both modes), which drives a real Coordinator from ensure_custom_specialist through the dispatcher to the executor;
    • test_a_refused_custom_dispatch_is_requested_once_per_cycle through the real router (five calls over two cycles give two denials; five before the fix);
    • test_framework_entry_enqueues_the_custom_specialist_before_any_other_work;
    • test_only_mode_reaches_the_launch_by_export_or_by_extra_env_pin and test_a_bad_custom_specialist_launch_exits_before_preflight, both through cli.main up to _preflight (a pinned 1 was dropped, and a pinned maybe or a pin without the pair reached _preflight, before the fix);
    • test_only_mode_framework_ticks_without_an_orchestration_turn;
    • test_only_mode_custom_specialist_runs_to_the_phase_end_without_an_extension, which also shows a 30-min session still admits it;
    • test_framework_holds_a_leverage_exit_for_queued_custom_work_but_not_a_budget_exit through advance_phase_if_needed, which fails when the hold in _next_phase is removed.
      Replaced tests: the guarantee-latch tests are replaced by test_one_dispatch_per_cycle_whatever_became_of_it, test_a_task_that_only_borrows_the_custom_tag_does_not_hold_the_dispatch_back, test_dispatch_enqueues_one_task_per_cycle_through_the_real_router and the refused-dispatch test above; test_spawning_a_tag_only_custom_task_marks_it_dispatched is removed with the field; the "offered when configured" prompt and gate tests are replaced by test_orchestration_prompt_never_offers_custom_specialist and test_gate_denies_every_custom_specialist_delegate.
      Ran, one file at a time: test_custom_specialist.py, test_auto_bench_proposals.py, test_phase_state_machine.py, test_optimize_phase_exit.py, test_predictor_pump.py, test_knowledge_plane.py, test_policy_gate.py, test_dispatcher_lane_priority.py, test_dispatcher_execution_shutdown.py, test_coordinator_sync_helpers_coverage_unit.py, test_specialist_concurrent_dispatch.py, test_gpu_research_lane_dispatch.py, test_session_time_budget.py; all pass. ruff check / ruff format --check clean; lint-imports 7 contracts kept; the code-metrics gate passes against the base.
      E2E, Qwen3-8B on MI355X (conc 64, ISL/OSL 1024, glm-5-3, --no-kernel), one session per mode at an earlier head:
    • Default mode: the custom task started in the first dispatch after FRAMEWORK entry, alongside what Orchestration delegated. Before the entry fix it started one second behind them.
    • Custom-only mode: the custom task dispatched first; Orchestration's specialist/explore delegates were denied 16 times; its two config proposals were benched as an operator_priority grid and its patch measured, then FRAMEWORK exited on skip_to_sweep 13 s after the patch finished, and SWEEP → CLOSE followed.
    • The same run showed the custom task waiting 20 min for Orchestration's first turn and surviving past its 60-min wall budget only through two Orchestration extend_lease calls. The no-turn and no_lease_extension changes address both; they are covered by the tests above, not yet by a rerun.
  • Size/complexity triggers crossed: 24 files. One concern (who dispatches and benches the operator's specialist, and how to run it alone). The file count is:
    • the removed Orchestration path (prompt builder, CLI plumbing, dispatcher flag, state field);
    • docs;
    • four small modules that keep every over-limit unit no longer than on main: cli/custom_specialist.py (the --custom-specialist-* pair's loading, resume and conflict rules, moved out of cli/__init__.py), policy/custom_specialist.py (the custom ingress rules and the custom-only scope), policy/denial.py (PolicyDenied, re-exported by gate.py, so the new policy module imports it without a cycle) and phases/proposal_bench.py (the operator-work bookkeeping, FrameworkPhase's base).
      Line counts against main: framework.py 3200 (main 3204), gate.py 1608 (1647), coordinator.py 1429 (1429), dispatcher.py 1550 (1550), cli/__init__.py 2850 (2907), parser.py 1191 (main 1185; the helper grouping the two custom flags), advance_phase_if_needed 149 (152); the --custom-specialist-* flags and SharedState fields moved into helpers. Baseline: four entries lowered by --update-baseline, none raised. The policy package does not import the phase machine (.importlinter unchanged).
  • If this simplifies or refactors: removed the Orchestration dispatch path for custom_specialist, the guarantee's three latches (custom_specialist_dispatched, the queued/running scan, the per-process cycle marker), tag-based dispatch marking and the --orch-prompt conflict. Preserved: the custom prompt/description rendering, research-mode fallback when patches cannot be authored, the CLI definition rules, persistence and resume replacement, and the general auto-bench for non-custom proposals (test_auto_bench_proposals.py unchanged and passing).
  • Observable effect: with --custom-specialist-* set, the custom specialist is queued on FRAMEWORK_AGENT entry every macro cycle and starts in the first dispatch, and its proposals are the next thing measured on the GPU after whatever is already running. An operator no longer sees sessions where it never ran or its proposals were never benched; when the budget cannot admit it, one refusal per cycle is logged instead of one per tick. With HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1 the phase runs nothing else and Orchestration takes no turn there. The custom task may run to the phase end (4 h cap). The phase hands over to KERNEL_AGENT when enabled, else SWEEP, when that work is done or refused.
  • Breaking changes: yes. Orchestration can no longer dispatch custom_specialist (domain or tag) and its prompt no longer lists it; --orch-prompt no longer conflicts with the custom flags; custom_specialist_dispatched is gone from state.json (old files load, the key is dropped).
  • PR addresses single concern: yes
  • Root cause is upstream: no

@lishuoshuo-amd
lishuoshuo-amd requested a review from a team as a code owner October 14, 2026 08:58
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Code metrics gate: PASSED

Judged: the 24 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 73 +0
Cognitive complexity fails > 30 0 0 0 289 +0
Function length (lines) fails > 80 0 0 0 453 +0
Nested block depth fails > 5 0 0 0 27 +0
Module length (lines) warns > 800, fails > 1200 0 0 0 53 +0
Duplicated lines per file fails > 0 0 0 0 120 +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 1 advisory, never fails
A10 Raw vocabulary comparison 0
A11 Hidden global write 0
A12 Pure forwarder 0
A12 Single-implementation abstraction 0

A9 Tuple return (advisory, never fails): 1

Where Problem
src/hyperloom/orchestrator/phases/machine.py:203 A9 tuple return: MachinePhase._next_phase returns a 3-element tuple (limit 2); return a dataclass or NamedTuple whose fields name the parts

Module length warning: over 800 lines, within the 1200-line failure limit (not failing): 3

Module Lines Limit
src/hyperloom/inference_optimizer/cli/bootstrap.py 824 warns > 800
src/hyperloom/inference_optimizer/cli/parser.py 1191 warns > 800
src/hyperloom/orchestrator/specialists/dispatch.py 1059 warns > 800

Already over at the base (backlog, not this change's): 16

Unit Dimension Base Now
src/hyperloom/orchestrator/state/shared_state.py:97 resolve_graded_comparison Cognitive complexity 34 34
src/hyperloom/inference_optimizer/cli/__init__.py:1840 _reexport_session_environment Function length (lines) 112 112
src/hyperloom/inference_optimizer/cli/__init__.py:2417 _resolve_agent_lineup Function length (lines) 89 89
src/hyperloom/inference_optimizer/cli/__init__.py:2312 _resolve_run_topology Function length (lines) 92 92
src/hyperloom/inference_optimizer/cli/__init__.py:1954 _resume_session Function length (lines) 171 171
src/hyperloom/inference_optimizer/cli/__init__.py:2127 _seed_fresh_session Function length (lines) 183 183
src/hyperloom/inference_optimizer/cli/__init__.py:2524 _run_optimize Function length (lines) 277 276
src/hyperloom/inference_optimizer/cli/bootstrap.py:433 _seed_shared_state Function length (lines) 157 156
src/hyperloom/inference_optimizer/cli/parser.py:176 _build_parser Function length (lines) 1034 1016
src/hyperloom/orchestrator/loop/dispatcher.py:801 DispatcherCollaborator.run_task_registered Function length (lines) 111 111
src/hyperloom/orchestrator/prompts/prompt_builder.py:562 _section_decision_framework Function length (lines) 104 104
src/hyperloom/inference_optimizer/cli/__init__.py:1 Module length (lines) 2907 2850
src/hyperloom/orchestrator/loop/dispatcher.py:1 Module length (lines) 1550 1550
src/hyperloom/orchestrator/phases/framework.py:1 Module length (lines) 3204 3200
src/hyperloom/orchestrator/prompts/prompt_builder.py:1 Module length (lines) 1226 1208
src/hyperloom/orchestrator/state/shared_state.py:1 Module length (lines) 2687 2687
76 findings in files this change does not touch

Outside this change (informational): 76

Unit Dimension Baseline Now
src/hyperloom/orchestrator/enablement/recipe/projections.py project_launch_evidence Cognitive complexity absent 36
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_kb/tests/test_cli.py Duplicated lines per file absent 18
src/hyperloom_kb/tests/test_global_identity_and_pull.py Duplicated lines per file absent 13
src/hyperloom_kb/tests/test_local_state.py Duplicated lines per file absent 18
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 90
src/hyperloom_kb/http_service.py RequestHandler._dispatch Function length (lines) absent 91
src/hyperloom_kb/sync.py GlobalSync.pull Function length (lines) absent 86
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 140
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start Function length (lines) absent 154
src/kernelforge/loop/knowledge_integration.py _measure_warmstart_field Function length (lines) absent 83
src/kernelforge/loop/runner.py IterationLoop._close_iteration Function length (lines) absent 135
src/kernelforge/loop/runner.py IterationLoop._collect_session_candidate Function length (lines) absent 187
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 104
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 174

...and 51 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.

Comment thread src/hyperloom/orchestrator/phases/framework.py Fixed
Comment thread src/hyperloom/orchestrator/phases/framework.py Fixed
@lishuoshuo-amd
lishuoshuo-amd force-pushed the feat/custom-specialist-coordinator-owned branch from 39999fc to 6c97d6f Compare October 14, 2026 09:27
patch_owner_phase,
)
from ..collaborator import CoordinatorCollaborator
from .proposal_bench import CUSTOM_PROPOSAL_DOMAIN, ProposalBench, kb_exposure_of
from hashlib import sha1
from typing import Any

from ..collaborator import CoordinatorCollaborator

from ..collaborator import CoordinatorCollaborator
from ..specialists.domains import CUSTOM_SPECIALIST_KEY
from .dispatch_order import is_operator_work
async def _maybe_finish_custom_only_cycle(self) -> None:
"""Under ``--custom-specialist-only``, hint ``skip_to_sweep`` once this cycle's custom work has all run."""
from ..specialists.domains import custom_specialist_key
from ..state.shared_state import ESCALATE_HINT_SKIP_TO_SWEEP
lishuoshuo-amd and others added 4 commits October 15, 2026 10:34
…st; add --custom-specialist-only

The operator-defined specialist could be starved for a whole session: Orchestration
chose whether and when to dispatch it, any task tagged custom_specialist satisfied
the at-least-once guarantee without running the operator prompt, and its proposals
waited behind other explore grids, integrate_patch tasks and the framework
candidate track before anyone benched them.

- The Coordinator dispatches it once per macro cycle on FRAMEWORK_AGENT entry,
  keyed custom-c{cycle}-{prompt hash}; Orchestration is no longer offered the
  domain and the gate denies any delegate naming it as domain or tag at ingress.
- Removed the guarantee latches, custom_specialist_dispatched, the tag-based
  dispatch marking, the description in the Orchestration prompt and the
  --orch-prompt conflict.
- Operator work (the custom specialist and the grid benching its proposals)
  dispatches ahead of every other queued task; its grid no longer waits for
  other explores or framework_agent_phase_done, and the general bench leaves
  custom proposals out.
- A plateau exit from FRAMEWORK_AGENT waits for pending operator work; budget
  and terminal exits do not.
- --custom-specialist-only runs FRAMEWORK_AGENT on the custom specialist and the
  bench of its proposals and patches alone, then hints skip_to_sweep.

Co-authored-by: Cursor <cursoragent@cursor.com>
…s gates

- PolicyDenied moves to policy/denial.py (gate re-exports it) so the new
  policy/custom_specialist.py, which holds both custom-specialist ingress
  rules, imports it without a cycle; the policy package no longer imports
  the phase machine.
- The custom/operator bookkeeping moves into phases/proposal_bench.py
  (ProposalBench, the FrameworkPhase base), with kb_exposure_of.
- MachinePhase._next_phase computes the transition and holds a plateau
  exit for operator work; is_leverage_exit replaces the flag argument.
- The --custom-specialist-* flags and SharedState fields get their own
  helpers in parser.py and bootstrap.py.
- Baseline: three entries lowered by --update-baseline.

Co-authored-by: Cursor <cursoragent@cursor.com>
…y in both modes

Default mode queued it from the phase pump, which runs after orchestration's
first turn and after that tick's dispatch, so the specialists orchestration
delegated took the research lane first.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@lishuoshuo-amd
lishuoshuo-amd force-pushed the feat/custom-specialist-coordinator-owned branch from 811b726 to ae6e75c Compare October 15, 2026 02:43
…estration turns

Every Orchestration intent there is denied, yet its turn held the tick's
dispatch back for 10-20 minutes on each entry and after the custom task
finished. The custom task it used to extend now runs to the wall-budget cap,
still bounded by the phase and session ends; admission keeps the mode base.

Co-authored-by: Cursor <cursoragent@cursor.com>
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only

What it does: The operator-defined custom_specialist could go a whole session without running, because Orchestration, a tag-latched Coordinator guarantee and tag-only dispatch marking all claimed it, and its proposals were benched behind every other explore. This PR makes the Coordinator its only dispatcher (once per macro cycle on FRAMEWORK_AGENT entry, straight into handle_delegate, with every Orchestration delegate naming it denied at the gate), dispatches it and the bench of its proposals ahead of all other queued work, holds a FRAMEWORK plateau exit while that work is pending, and adds --custom-specialist-only, which runs FRAMEWORK_AGENT on that work alone.

Blocking issues: 4

  1. [free:custom-dispatch-retries-every-tick] A refused custom dispatch is retried on every FRAMEWORK tick, and under --custom-specialist-only the phase then idles until its budget runs out [verified]
    Problem: ensure_custom_specialist only stops once a task row with the cycle key exists (src/hyperloom/orchestrator/specialists/dispatch.py:667-669). When handle_delegate refuses the dispatch at admission, no row is created, so every FrameworkPhase.pump (framework.py:2307, framework.py:2315) runs warm_specialist_params, requests it again and is denied again. A patch-mode custom specialist needs 60 min (time_budget.py:114-125), and on a 2 h session FRAMEWORK_AGENT gets about 43 min, so phase_time_budget refuses it on every tick. The base refused the same dispatch too, but only once per cycle, because it kept _custom_guarantee_cycle. This PR deleted that marker and its test test_a_denied_attempt_is_not_retried_until_the_next_cycle. Its stated replacement, test_one_dispatch_per_cycle_whatever_became_of_it, mocks an existing row and never reaches the refusal path. On such sessions the custom specialist still never runs, so the description's "an operator no longer sees sessions where it never ran" does not hold for them.
    Impact: Repro on a 2 h session with a 15 min PRELUDE (43.4 min of FRAMEWORK budget): five calls give five phase_time_budget denials at head and one at base. In the default mode, each tick sends Orchestration a policy_denied observation for a delegate it never emitted, and also runs a KB warm read and logs "custom specialist: requested". Under --custom-specialist-only, no custom task ever exists, so _maybe_finish_custom_only_cycle (proposal_bench.py:80) never hints. Orchestration's turn is skipped, so FRAMEWORK_AGENT runs nothing for its whole remaining budget (three pumps gave three denials, zero tasks and no hint).
    Action: Record a refused dispatch once per (cycle, definition) so it is not retried within the cycle, and make the refusal visible to the operator. Under --custom-specialist-only, treat a refused cycle as done so the phase moves on. Restore a test that asserts one denial per cycle through the real router.

  2. [free:custom-only-goes-to-kernel] --custom-specialist-only leaves FRAMEWORK_AGENT for KERNEL_AGENT, not SWEEP as documented [verified]
    Problem: _maybe_finish_custom_only_cycle sets skip_to_sweep (src/hyperloom/orchestrator/phases/proposal_bench.py:93). From FRAMEWORK_AGENT, compute_next_phase sends every exit to KERNEL_AGENT whenever the kernel lever is enabled (machine_state.py:1839-1840), and the hint is consumed on that transition (machine.py:256-257). Nothing else reads skip_to_sweep. Three places say the phase moves on to SWEEP: the flag help (cli/parser.py:180), docs/reference/environment-variables.md:137-139, and the description's "Observable effect" line.
    Impact: With the default kernel lever enabled, a custom-only run spends a full KERNEL_AGENT phase after the custom work. By default that phase gets 0.47/0.54, or about 87%, of the time left when it starts, and an operator reading the flag does not expect it. Repro: custom-only, custom task terminal, skip_to_sweep set, then advance_phase_if_needed moves to KERNEL_AGENT.
    Action: Either route the custom-only exit past KERNEL_AGENT, or change the help, the docs and the description to say that it advances to KERNEL_AGENT when that phase is enabled.

  3. [X2] The description does not match the diff at the current head [verified]
    Problem: Head commit b4b8b6c makes Orchestration take no reactor turn at all in custom-only FRAMEWORK_AGENT (src/hyperloom/orchestrator/loop/coordinator.py:751). It also gives that custom task the 240-min wall-budget cap instead of its mode base (loop/dispatcher.py:1011, NO_LEASE_EXTENSION). The description covers custom-only only as Orchestration's specialist, explore and integrate_patch being "denied there at ingress". It never says that Orchestration skips its turn entirely, and never mentions the wall budget. Its counts also come from an earlier head: it says 21 files, three baseline entries lowered, 104 tests and framework.py at 3199 lines, but the current head has 23 files, 4 entries, 111 tests and 3200 lines.
    Impact: The release notes are built from this description (AGENTS.md "Every change states its observable effect in its own PR description"), so both behaviours would ship unannounced.
    Action: Update the description to the current head, stating both effects and the corrected counts.

  4. [T1] The FRAMEWORK plateau hold has no test that fails when the hold is removed [verified]
    Problem: The only test for MachinePhase._next_phase (src/hyperloom/orchestrator/phases/machine.py:203-221) is test_only_a_plateau_exit_from_framework_waits_for_operator_work, and it calls only the classifier is_leverage_exit. If _next_phase is replaced with the bare compute_next_phase call, test_custom_specialist.py (111 tests), test_optimize_phase_exit.py (14), test_phase_state_machine.py (56) and test_predictor_pump.py (33) all still pass. With the real _next_phase, a skip_to_sweep hint with the custom task queued stays in FRAMEWORK_AGENT; without it, the phase leaves for KERNEL_AGENT with the task still queued.
    Impact: The documented contract "a plateau exit waits for both within the phase budget" (docs/conceptual/optimization-loop.md:379-380) can regress without any failing test. AGENTS.md "Test the contract, not the plumbing" asks for this kind of contract to be pinned through its entry point.
    Action: Add a test that drives advance_phase_if_needed with a queued custom task. It should assert that a leverage exit stays in FRAMEWORK_AGENT and that a budget exit still leaves.

SKIPPED: V4 -- openprs.txt compared #1032, #1154, #1276, #1675 and #1869 on their first 100 changed files only, so overlap beyond those files was not checked.

Checked: specialists/dispatch.py (ensure_custom_specialist, maybe_force_stalled_domain_specialist, record_specialist_result), loop/intent_router.py (handle_intent, handle_delegate, record_policy_denied), loop/dispatcher.py (admission_denial_for_action, _specialist_wall_budget_sec, _drain_completions), loop/time_budget.py, loop/coordinator.py (_tick_once, _reactor_turn), phases/machine.py, phases/machine_state.py (_framework_exit, compute_next_phase, phase budgets), phases/proposal_bench.py, phases/framework.py (on_enter_framework, pump, maybe_autosubmit_specialist_patches), policy/gate.py (validate_dispatched_task, _validate_delegate_body, _validate_propose_action), policy/custom_specialist.py, AGENTS.md. I also ran a tree-wide git grep over src/, docs/ and assets/ for every removed or renamed name. | Ran: repro scripts at head and base (repeated refused dispatch on a 2 h session, custom-only idle, plateau hold with and without _next_phase, custom-only exit target), each finding re-checked by an independent reader; pytest one file at a time: test_custom_specialist.py (111), test_auto_bench_proposals.py (5), test_phase_state_machine.py (56), test_policy_gate.py (14), test_optimize_phase_exit.py (14); ruff C901 at head and base (no unit grew) | Base: a56028d | Head: b4b8b6c

lishuoshuo-amd and others added 2 commits October 15, 2026 13:16
…cle; pin the FRAMEWORK exit hold

A refused admission left no task row, so ensure_custom_specialist re-requested
it every tick, and under --custom-specialist-only the phase idled to its budget.
The refused key is kept and counts as a finished custom cycle. The plateau hold
is now tested through advance_phase_if_needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
…NT over to the next phase, KERNEL_AGENT included

Co-authored-by: Cursor <cursoragent@cursor.com>
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only

What it does: The operator-defined custom_specialist could go a whole session without running, because Orchestration, a tag-latched Coordinator guarantee and tag-only dispatch marking all claimed it, and its proposals were benched behind every other explore. This PR makes the Coordinator its only dispatcher, once per macro cycle on FRAMEWORK_AGENT entry: it goes straight into handle_delegate, the gate denies any Orchestration delegate naming it, and a refused admission is not retried within the cycle. It dispatches the custom specialist and the bench of its proposals ahead of all other queued work, and holds a FRAMEWORK plateau exit while that work is pending. It also adds --custom-specialist-only, which runs FRAMEWORK_AGENT on that work alone with no Orchestration turn and then hands over to KERNEL_AGENT when enabled, else SWEEP.

Blocking issues: 1

  1. [X2] The description misstates parser.py's length on main [verified]
    Problem: The size section says "Line counts against main: ... parser.py 1200 (1200)". At the merge base a56028d and at current main, src/hyperloom/inference_optimizer/cli/parser.py has 1185 lines. At head 54518f9 it has 1200, so the module grows by 15 lines to exactly the 1200-line module-length failure ceiling. No baseline entry carries a 1200 for it. Every other number in the description matches the head.
    Impact: The template's size/complexity field is what a reviewer uses to judge whether the shape is right, and here it reports no growth for a module that now sits on the failure limit.
    Action: Correct the entry to parser.py 1200 (main 1185).

SKIPPED: V4 -- openprs.txt compared #1032, #1154, #1276, #1675 and #1869 on their first 100 changed files only, so overlap beyond those files was not checked.

Checked: the delta b4b8b6c..54518f9 (specialists/dispatch.py ensure_custom_specialist and refused_custom_key, phases/proposal_bench.py _maybe_finish_custom_only_cycle, cli/parser.py help, docs/reference/environment-variables.md, inference_optimizer/SKILL.md, the new tests), every refusal path of IntentRouter.handle_delegate for the custom params (dispatcher.py admission gates, _admit_specialist, the patch preflight), and the description's numbers against the merge base and the head. Tree-wide git grep for refused_custom_key and for stale "SWEEP" wording. | Ran: the previous review's four findings, re-verified at this head, all resolved. On a 2 h session, a refused dispatch now gives one phase_time_budget denial from five calls, and under custom-only the phase hands over instead of idling. test_a_refused_custom_dispatch_is_requested_once_per_cycle fails on b4b8b6c with [0, 0, 0, 1, 1] != [0, 1]. The KERNEL_AGENT hand-over is documented in the help, the docs, SKILL.md and the description. The description states the turn skip and the 4 h cap. test_framework_holds_a_leverage_exit_for_queued_custom_work_but_not_a_budget_exit fails with the hold removed (1 failed, 115 passed); pytest one file at a time: test_custom_specialist.py (116), test_auto_bench_proposals.py (5), test_phase_state_machine.py (56), test_policy_gate.py (14), test_optimize_phase_exit.py (14), test_predictor_pump.py (33); ruff C901 on the changed modules | Base: a56028d | Head: 54518f9

@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only

What it does: The operator-defined custom_specialist could go a whole session without running, because Orchestration, a tag-latched Coordinator guarantee and tag-only dispatch marking all claimed it, and its proposals were benched behind every other explore. This PR makes the Coordinator its only dispatcher, once per macro cycle on FRAMEWORK_AGENT entry: it goes straight into handle_delegate, the gate denies any Orchestration delegate naming it, and a refused admission is not retried within the cycle. It dispatches the custom specialist and the bench of its proposals ahead of all other queued work, and holds a FRAMEWORK plateau exit while that work is pending. It also adds --custom-specialist-only, which runs FRAMEWORK_AGENT on that work alone with no Orchestration turn and then hands over to KERNEL_AGENT when enabled, else SWEEP.

Blocking issues: none

SKIPPED: V4 -- openprs.txt compared #1032, #1154, #1276, #1675 and #1869 on their first 100 changed files only, so overlap beyond those files was not checked.

Checked: the head is unchanged since the previous review (54518f9, same diff), and the description's only remaining mismatch is corrected. It now gives parser.py as 1200 lines (main 1185), at the 1200-line ceiling. That matches wc -l at the merge base, at current main and at the head, and module-lines = 1200 in pyproject.toml. Every other number in the size section was re-checked. The code at this head was covered last round: the delta b4b8b6c..54518f9, every refusal path of IntentRouter.handle_delegate for the custom params, and a tree-wide git grep for refused_custom_key and for stale SWEEP wording. | Ran: pytest test_custom_specialist.py at this head (116 passed). The previous round's runs at this same head still stand: the repro scripts, the refusal test failing on b4b8b6c, the hold test failing with the hold removed, and five related test files. CI at this head: 12/12 test shards and every other check succeeded. | Base: a56028d | Head: 54518f9

LGTM

lishuoshuo-amd and others added 2 commits October 15, 2026 14:30
…IALIST_ONLY instead of a CLI flag

The env is read where the --custom-specialist-* pair is validated, so a fresh
launch persists it and a resume can turn it on, as the flag did; an unreadable
value and an env without the pair both exit 2 before the session starts.

Co-authored-by: Cursor <cursoragent@cursor.com>
…er the only mode was the CLI flag

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src/hyperloom/inference_optimizer/tests/test_custom_specialist.py Fixed
Comment thread src/hyperloom/inference_optimizer/tests/test_custom_specialist.py Fixed
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only

What it does: The operator-defined custom_specialist could go a whole session without running, because Orchestration, a tag-latched Coordinator guarantee and tag-only dispatch marking all claimed it, and its proposals were benched behind every other explore. This PR makes the Coordinator its only dispatcher, once per macro cycle on FRAMEWORK_AGENT entry: it goes straight into handle_delegate, the gate denies any Orchestration delegate naming it, and a refused admission is not retried within the cycle. It dispatches the custom specialist and the bench of its proposals ahead of all other queued work, and holds a FRAMEWORK plateau exit while that work is pending. It also adds an only mode, turned on by HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1 (no CLI flag since 7fb2f70). That mode runs FRAMEWORK_AGENT on the custom work alone with no Orchestration turn, then hands over to KERNEL_AGENT when enabled, else SWEEP.

Blocking issues: 1

  1. [free:only-mode-pin-ignored] HYPERLOOM_CUSTOM_SPECIALIST_ONLY passed as an --extra-env pin is silently ignored [verified]
    Problem: The variable is read once, in main() (src/hyperloom/inference_optimizer/cli/init.py:2891 via _load_custom_specialist_args, :990), before asyncio.run(_run_optimize). _run_optimize then exports the --extra-env pins into os.environ through _resolve_run_topology (called at :2575, _export_operator_launch_shape at :2389, plus :2089 again on a resume). It also seeds the state from args.custom_specialist_only (cli/bootstrap.py:551 -> :429). Nothing reads the variable after the pins are exported. The repo promises that pins reach Hyperloom's control variables: the exporter's docstring (:1389) says "Every pin is exported under its own name, with no exceptions: Hyperloom's control variables are read with a bare os.environ.get", and docs/reference/environment-variables.md:977 says HYPERLOOM_AGENTIC_BACKEND is "Settable by export or --extra-env; both reach every reader". SKILL.md:773 also tells the launcher to forward every operator env pin as --extra-env. Neither environment-variables.md:137-143 nor SKILL.md:774 says this variable is the exception.
    Impact: A launch given --extra-env HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1 runs a full default-mode session and gives no message, while os.environ and state.json's operator_extra_env both record the pin. The pin route also skips the documented exit 2 (environment-variables.md:137-138, cli/init.py:1038): a pinned maybe, or a pinned 1 without the --custom-specialist-* pair, still reaches _run_optimize. Repro through cli.main, one process per case: pinned and not exported, the seeded custom_specialist_only is False and os.environ holds '1' after the pin export; exported instead, it is True.
    Action: Resolve only mode, and run its conflict check, after the pins are exported (after :2575), or refuse the name as a pin with exit 2 and document that. Add a cli.main test for the pin route.

SKIPPED: V4 -- openprs.txt compared #1032, #1154, #1276, #1675 and #1869 on their first 100 changed files only, so overlap beyond those files was not checked.

Checked: the delta 54518f9..e354d85 (7fb2f70, which switches only mode from the flag to the env, and e354d85, which updates the docs), covering cli/init.py (_load_custom_specialist_args, _apply_custom_specialist_resume, _custom_specialist_conflict, main, _export_operator_launch_shape and its call order), cli/parser.py, cli/bootstrap.py (parse_operator_extra_env), common/env.py (env_bool, EnvValueError), policy/custom_specialist.py, docs/reference/environment-variables.md, SKILL.md (the flag table and the launch block with load_dotenv_no_clobber) and .env.template. Also ran a tree-wide git grep for HYPERLOOM_CUSTOM_SPECIALIST_ONLY and --custom-specialist-only. Code outside the delta is unchanged from the previous reviews. | Ran: repro_extra_env_pin.py through cli.main (pin vs export, in separate processes) plus a pinned maybe and a pinned 1 without the pair (both reached _run_optimize), each re-checked by an independent reader; pytest test_custom_specialist.py (122 passed); description counts re-checked (122 tests, parser.py 1191 vs main 1185); CI at e354d85: every check succeeded, including 12/12 test shards | Base: a56028d | Head: e354d85

…he --extra-env pins are exported

main() read it before _run_optimize exported the operator's pins, so a pinned
value was silently ignored and skipped the exit-2 checks. Only mode and the
fresh launch's custom conflicts now settle right after the pins land; the
custom-specialist launch helpers move to cli/custom_specialist.py.

Co-authored-by: Cursor <cursoragent@cursor.com>
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only

What it does: The operator-defined custom_specialist could go a whole session without running, because Orchestration, a tag-latched Coordinator guarantee and tag-only dispatch marking all claimed it, and its proposals were benched behind every other explore. This PR makes the Coordinator its only dispatcher, once per macro cycle on FRAMEWORK_AGENT entry: it goes straight into handle_delegate, the gate denies any Orchestration delegate naming it, and a refused admission is not retried within the cycle. It dispatches the custom specialist and the bench of its proposals ahead of all other queued work, and holds a FRAMEWORK plateau exit while that work is pending. It also adds an only mode, turned on by HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1 through export or an --extra-env pin. That mode runs FRAMEWORK_AGENT on the custom work alone with no Orchestration turn, then hands over to KERNEL_AGENT when enabled, else SWEEP.

Blocking issues: none

SKIPPED: V4 -- openprs.txt compared #1032, #1154, #1276, #1675 and #1869 on their first 100 changed files only, so overlap beyond those files was not checked.

Checked: the delta e354d85..a9e1d54, covering _settle_custom_specialist_only and its place in _run_optimize (after _resolve_run_topology exports the pins, before seeding and _preflight), the resume path (_resume_session re-export, _reexport_session_environment, _apply_custom_specialist_resume), the side effects of _resolve_run_topology before the exit 2 (only os.environ writes and exit-2 gates), the new cli/custom_specialist.py (an AST compare against its old home shows it identical apart from the moved env read), and the description's counts. The previous review's finding is resolved at this head. | Ran: repro_pin_settle.py through cli.main up to _preflight, one case per process. A pinned 1 and an exported 1 both settle True; a pinned maybe and a pin without the pair both exit 2. The new tests run on e354d85 fail on exactly those pin cases ((99,[False]) != (99,[True]), (99,[True]) != (99,[False]), (99,[False]) != (2,[]) twice) and pass at head. pytest test_custom_specialist.py: 123 passed. CI at a9e1d54: every check succeeded, including 12/12 test shards. | Base: a56028d | Head: a9e1d54

LGTM

@xiaofei-zheng xiaofei-zheng 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 #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only

What it does: the operator-defined custom_specialist could go unrun for a whole session, because Orchestration decided when to delegate it while the Coordinator's at-least-once guarantee was latched off by any task merely tagged custom_specialist. This PR gives the Coordinator sole ownership: on_enter_framework queues one dispatch per macro cycle under a custom-c{cycle}-{sha1(prompt)[:8]} key straight into router.handle_delegate, the gate denies every agent-emitted delegate naming the domain or tag at ingress only, dispatch_order gains an operator tier ahead of the phase's dispatch-first kinds, the custom proposals get their own operator_priority explore grid, and a FRAMEWORK_AGENT optimize_no_more_leverage exit is held while that work is pending (budget and terminal exits are not). It also adds HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1, which scopes FRAMEWORK_AGENT to the custom specialist and its bench: Orchestration takes no reactor turn there, discovery / scout / stalled-domain force / predictor / general bench are skipped, the custom task runs under no_lease_extension, and the phase hints skip_to_sweep once that cycle's work is done or refused.

Blocking issues: 1

  1. [X2] The title names a --custom-specialist-only flag the branch deliberately does not add [verified]
    Problem: the title still reads "add --custom-specialist-only", but commit 8 ("turn only mode on with HYPERLOOM_CUSTOM_SPECIALIST_ONLY instead of a CLI flag") replaced that flag with an env var. At head, _add_custom_specialist_args registers only --custom-specialist-prompt-file and --custom-specialist-description (src/hyperloom/inference_optimizer/cli/parser.py:152), and the PR's own test asserts argparse rejects the flag with exit 2 (src/hyperloom/inference_optimizer/tests/test_custom_specialist.py:873). The body is correct; only the title is stale.
    Impact: the title is what the release cut and the commit on main carry. An operator following it runs hyperloom optimize --custom-specialist-only ... and gets "unrecognized arguments" instead of the mode.
    Action: retitle to name the env var, e.g. feat(custom-specialist): make the Coordinator own the custom specialist; add HYPERLOOM_CUSTOM_SPECIALIST_ONLY.

SKIPPED: local test run -- the test suite cannot be collected on this host (cli/kb.py imports fcntl); verification relied on reading the code, a direct import-level check of dispatch_order/domains, and the 12 green CI test shards at a9e1d54.

Checked: specialists/dispatch.py::ensure_custom_specialist; loop/intent_router.py::handle_intent vs handle_delegate; policy/gate.py::_validate_delegate_body / validate_dispatched_task (check_source split) and policy/custom_specialist.py; phases/framework.py::on_enter_framework, pump, _maybe_bench_untested_proposals, _enqueue_bench_grid, maybe_autosubmit_specialist_patches, materialize_candidate; phases/proposal_bench.py::operator_work_pending, _maybe_finish_custom_only_cycle; phases/machine.py::_next_phase / advance_phase_if_needed against machine_state.py::_framework_exit; phases/dispatch_order.py::is_operator_work; loop/dispatcher.py::_specialist_wall_budget_sec / _specialist_deadline; loop/coordinator.py::_tick_once; state/_shared_state/render.py::untested_proposal_rows; state/shared_state.py::from_dict; cli/init.py::_settle_custom_specialist_only, _resolve_run_topology, the resume conflict block, cli/custom_specialist.py; docs/reference/environment-variables.md, docs/conceptual/optimization-loop.md, SKILL.md, .env.template | Ran: import-level check of dispatch_order.in_dispatch_order / is_operator_work and domains.custom_specialist_key; CI at head (12/12 test shards, coverage, code-metrics, import-linter green) | Base: a56028d | Head: a9e1d54

@lishuoshuo-amd lishuoshuo-amd changed the title feat(custom-specialist): make the Coordinator own the custom specialist; add --custom-specialist-only feat(custom-specialist): make the Coordinator own the custom specialist; add HYPERLOOM_CUSTOM_SPECIALIST_ONLY Oct 15, 2026
@xiaofei-zheng
xiaofei-zheng dismissed their stale review October 15, 2026 09:20

Title updated to name HYPERLOOM_CUSTOM_SPECIALIST_ONLY; the only blocking finding is resolved at the same head.

@xiaofei-zheng

Copy link
Copy Markdown
Collaborator

PR #1876 -- feat(custom-specialist): make the Coordinator own the custom specialist; add HYPERLOOM_CUSTOM_SPECIALIST_ONLY

What it does: the operator-defined custom_specialist could go unrun for a whole session, because Orchestration decided when to delegate it while the Coordinator's at-least-once guarantee was latched off by any task merely tagged custom_specialist. This PR gives the Coordinator sole ownership: on_enter_framework queues one dispatch per macro cycle under a custom-c{cycle}-{sha1(prompt)[:8]} key straight into router.handle_delegate, the gate denies every agent-emitted delegate naming the domain or tag at ingress only, dispatch_order gains an operator tier ahead of the phase's dispatch-first kinds, the custom proposals get their own operator_priority explore grid, and a FRAMEWORK_AGENT optimize_no_more_leverage exit is held while that work is pending (budget and terminal exits are not). It also adds HYPERLOOM_CUSTOM_SPECIALIST_ONLY=1, which scopes FRAMEWORK_AGENT to the custom specialist and its bench: Orchestration takes no reactor turn there, discovery / scout / stalled-domain force / predictor / general bench are skipped, the custom task runs under no_lease_extension, and the phase hints skip_to_sweep once that cycle's work is done or refused.

Blocking issues: none. The one finding of the earlier review -- the title naming a --custom-specialist-only flag the branch does not add -- is resolved by the retitle at the same head (a9e1d54); that review is dismissed. The body is unchanged and still matches the diff.

SKIPPED: local test run -- the test suite cannot be collected on the review host (cli/kb.py imports fcntl); verification relied on reading the code, a direct import-level check of dispatch_order/domains, and the green CI at this head.

Checked: specialists/dispatch.py::ensure_custom_specialist (the (cycle, prompt) key, refused_custom_key, the research-mode fallback); loop/intent_router.py::handle_intent vs handle_delegate -- only handle_intent calls policy.validate_intent, so the Coordinator's own task is not caught by its own ingress denial; policy/gate.py::_validate_delegate_body / validate_dispatched_task, confirming the new denials are gated on check_source and dispatch-time replay (gate.py:369, check_source=False) still admits the row, and that removing _validate_custom_specialist_configured opens no hole because ingress now denies a superset; policy/custom_specialist.py and the only-mode scope; phases/framework.py::on_enter_framework, pump, _maybe_bench_untested_proposals, _enqueue_bench_grid, plus maybe_autosubmit_specialist_patches and materialize_candidate -- the Critic path runs off the specialist settle handler and record_proposal, so only mode's early return does not strand the custom patch and no Coordinator-side integrate_patch crosses deny_search_under_custom_only; phases/proposal_bench.py::operator_work_pending / _maybe_finish_custom_only_cycle; phases/machine.py::_next_phase against machine_state.py::_framework_exit -- the hold matches optimize_no_more_leverage only, the budget exits carry distinct reasons, and a held tick clears dispatcher.admission_frozen (machine.py:247), so the hold cannot starve the bench it waits for; phases/dispatch_order.py::is_operator_work; loop/dispatcher.py::_specialist_wall_budget_sec and _specialist_deadline, which tightens the 4 h shape budget to the session and phase deadlines; loop/coordinator.py::_tick_once; state/_shared_state/render.py::untested_proposal_rows, where every row carries the suffix-stripped domain the custom filter matches; state/shared_state.py::from_dict, which drops the removed custom_specialist_dispatched key so old sessions resume; cli/init.py::_settle_custom_specialist_only against _resolve_run_topology (the --extra-env pins are exported first) and the resume conflict block; cli/custom_specialist.py; docs/reference/environment-variables.md, docs/conceptual/optimization-loop.md, SKILL.md and .env.template.

Ran: import-level check of dispatch_order.in_dispatch_order / is_operator_work and domains.custom_specialist_key; CI at a9e1d54 (12/12 test shards, coverage, code-metrics, import-linter, ruff, mypy, bandit, CodeQL green).

LGTM

@xiaofei-zheng xiaofei-zheng 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.

No blocking issues. The earlier finding (the title naming a --custom-specialist-only flag the branch does not add) is resolved by the retitle at the same head a9e1d54; the diff and body are unchanged since that review. Paths checked and commands run are listed in the preceding comment.

@xiaofei-zheng
xiaofei-zheng merged commit 9f3e685 into main Oct 15, 2026
42 of 44 checks passed
@xiaofei-zheng
xiaofei-zheng deleted the feat/custom-specialist-coordinator-owned branch October 15, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants