Skip to content

feat(specialist): operator-defined custom specialist type - #1804

Merged
xiaofei-zheng merged 7 commits into
mainfrom
feat/custom-specialist-type
Oct 11, 2026
Merged

xiaofei-zheng merged 7 commits into
mainfrom
feat/custom-specialist-type

Conversation

@lishuoshuo-amd

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

Copy link
Copy Markdown
Collaborator

Summary

Adds one operator-defined specialist type, custom_specialist (KB anchor custom_specialist). The operator supplies its focus and a one-line description at launch:

python -m hyperloom optimize ... \
  --custom-specialist-prompt-file ./my_focus.md \
  --custom-specialist-description "Tunes the MoE all-to-all path for EP>1 deployments"
  • System prompt: keeps the shared identity, output protocol and iron rules. The operator text replaces the Domain focus verbatim, and the description fills the identity's Description line.
  • User prompt: mandate, hardware, PD, execution budget and gap, the Experience KB block when the Experience service returned one, plus the source hint in patch mode. The Experience block stays because the FRAMEWORK_AGENT warmup records it as shown to the specialist. KB, roofline, recipe, lessons, pitfalls and the PR feed are left out. Orchestration notes are still appended.
  • Orchestration: only when configured, custom_specialist joins the specialist EMIT domain list, followed by OPERATOR-DEFINED DOMAIN: custom_specialist — <description>. Both the initial build and the phase-change rebuild carry it.
  • PolicyGate: a dispatch by domain or by custom_specialist tag is denied with specialist_custom_not_configured when no definition exists.
  • Persistence: the definition lives in SharedState. On --resume-from, passing the flags replaces it. A new definition also re-arms the at-least-once guarantee, while the same or no definition keeps it.
  • At least once per session: in FRAMEWORK_AGENT, the Coordinator dispatches it when it has not started yet and none is queued or running. The gap is the most actionable one, or gap.custom.session when there are none. It runs in patch mode by default and falls back to research mode, with an observation, when source patches cannot be authored. Admission denials create no task row, so attempts are tracked per macro cycle in memory: at most one attempt per cycle.
  • Stalled-domain forcing: the operator-defined layer is left out of KNOWLEDGE_DOMAIN_TAGS, so its round counter is set to 0 on dispatch and never incremented, and it is never force-dispatched. Its runs come from Orchestration and the guarantee only.

Validation and conflicts

Argument checks run in main():

  • Both flags must be passed together.
  • The prompt file must be readable and non-empty after strip, at most 16 KiB.
  • The description must be a single non-empty line of at most 200 characters.

The launch fails fast when the type is configured, either by flags or by the resumed state, together with any of:

  • --orch-prompt (the replaced Orchestration prompt would never offer it)
  • --no-framework-agent, or a resumed state with FRAMEWORK_AGENT disabled
  • --research-lane-capacity 0
  • --reset-state together with the flags on this launch (the reset would wipe the new definition)

A bare --reset-state on resume is not a conflict: the stored definition is cleared with the rest of the state, which is how an operator removes it.

The check runs before any state is written: in main() for a new session, and right after the state is loaded on resume.

The prompt text is written in plain text to state.json and to each task's prompt.md, so --help warns not to put credentials in it.

Unconfigured behaviour is unchanged

All new parameters default to empty, and every new branch is gated on a non-empty definition. The type is recognised only by its own name, custom_specialist, which no prompt shows unless it is configured; the generic word custom (for example a tag an LLM writes in a --framework custom session) resolves to nothing, as before.

  • KNOWLEDGE_DOMAIN_TAGS is identical to main, so round counters, stalled-domain forcing and the gate's tag vocabulary are unchanged.
  • A built-in dispatch carrying a custom tag is still admitted.
  • Prompts were rendered on main and on this branch, then hashed: every built-in specialist domain × {patch, research} × extra tags (including custom and custom_specialist), both system and user prompts, plus the Orchestration prompt for 5 phases. All 225 hashes are identical.

Tests

  • test_custom_specialist.py (57 tests):
    • System and user prompts asserted by exact equality against the shared sections.
    • Runner description override.
    • Orchestration EMIT list for configured and unconfigured sessions, across phases.
    • Gate deny and allow shapes.
    • CLI argument checks.
    • Resume replace, re-arm and keep.
    • Conflicts, including the fresh-launch exit before _run_optimize; a stored definition with --reset-state (also with --orch-prompt) is not a conflict, while the flags with --reset-state are, with or without a stored definition.
    • The guarantee: gap choice, synthetic gap, queued and running dedupe, tag-only dispatch, research fallback, at most one attempt per macro cycle after a denial.
    • The spawn flag on a real Coordinator.
    • Guarantee on a real Coordinator, gate and router: one research-mode task is enqueued and the next cycle holds back while it is queued; a queued or running custom task, by domain or tag, also holds it back.
    • Experience KB: a real warmup on a Coordinator with a stub Experience service records the injection, and the block reaches the custom user prompt built by the runner.
    • Unconfigured parity: the exact KNOWLEDGE_DOMAIN_TAGS tuple, a custom-tagged built-in dispatch admitted, and stalled-domain forcing picking the serving gap over a custom-hinted one.
  • Mutation-checked: reverting each of the per-cycle marker, the resume re-arm, the fresh-launch conflict check, the queued/running check and the research fallback makes its test fail. The three parity tests fail on acd741263; the Experience KB test and the two --reset-state rows fail on bcb506aca.
  • Complexity ceiling: no function this change adds or raises is above cyclomatic complexity 20 (ruff check --select C901, head against the merge base, per the style guide).
  • Related suites pass, 481 tests in total: test_cli_bootstrap*, test_cli_resume_launch_shape, test_experience_kb_integration, test_per_domain_prompts, test_policy_gate, test_pump_decouple, test_shared_state_*, test_specialist_dispatch_params, test_specialist_lifecycle, test_specialist_prompt_builder_coverage_unit.

End-to-end runs

Spur MI355X, Qwen3-8B, sglang, GLM backend, --no-kernel, framework phase only. The Experience service was not configured in these workspaces.

At bcb506aca, 1 h budget each; the run without the flags was stopped at about 53 min, in FRAMEWORK_AGENT:

  • Without the flags: no task names custom_specialist, no specialist_custom_not_configured denial, round counters are keyed by the eight built-in anchors only, and the custom state fields stay at their defaults.
  • With the flags: Orchestration dispatched domain=custom_specialist (no tags) in the first FRAMEWORK_AGENT batch. The task started in the same tick, so the guarantee returned on custom_specialist_dispatched and never dispatched. The system prompt carried the operator focus verbatim under Layer: operator-defined. KB anchor: custom_specialist.. The task succeeded and was the only custom task of the session.
  • Not exercised by either 1 h run: the guarantee's own dispatch, and stalled-domain forcing (no domain reached the 8-round threshold; the highest counter was 5).

On the first head, 2 h: Orchestration also selected custom_specialist on its own, the task succeeded with a sourced "no actionable overhead" finding, and the stalled-domain force re-dispatched it on its own research hint; bcb506aca removed that path.

The follow-up fix (Experience KB section, --reset-state) is covered by unit tests, not by an end-to-end run.

Docs

  • SKILL.md: a row in the caller-responsibility table and an IR-4 paragraph.
  • docs/reference/environment-variables.md: the flags under workload configuration.
  • docs/conceptual/optimization-loop.md: one sentence in the FRAMEWORK_AGENT section.

Not covered

  • The ordering of the resume-path conflict check is verified by reading the code, not by a test. The check is at cli/__init__.py:1874, before the first resume state.save at line 1905.

Add a custom_specialist domain whose focus and description come from
--custom-specialist-prompt-file / --custom-specialist-description.

- System prompt keeps the shared identity, output protocol and iron rules;
  the operator text replaces the domain focus verbatim.
- User prompt carries mandate, hardware, PD, budget and gap, plus the
  source hint in patch mode.
- Orchestration sees the domain in the specialist EMIT list with a
  one-line selection hint only when configured; PolicyGate denies it
  otherwise.
- The definition persists in SharedState; flags on --resume-from replace it.
  Conflicting launches (--orch-prompt, FRAMEWORK_AGENT disabled,
  --research-lane-capacity 0, --reset-state) fail fast.
- The Coordinator dispatches it at least once in FRAMEWORK_AGENT, at most
  once per macro cycle, falling back to research mode when source patches
  cannot be authored.
@lishuoshuo-amd
lishuoshuo-amd requested a review from a team as a code owner October 10, 2026 03:31
- Track the guarantee attempt per macro cycle in memory; admission
  denials create no task row, so a registry lookup re-dispatched every tick.
- Check custom specialist conflicts before any state save: in main() for
  new sessions and right after state load on resume.
- Reset custom_specialist_dispatched when resume supplies a new definition.
- Pin custom system/user prompts with exact-equality tests.
@lishuoshuo-amd
lishuoshuo-amd force-pushed the feat/custom-specialist-type branch from 43f6446 to 875f873 Compare October 10, 2026 06:28


def test_fresh_launch_conflict_exits_before_the_session_starts(tmp_path, monkeypatch):
import hyperloom.inference_optimizer.cli as cli

@pytest.mark.parametrize("pruned", [True, False])
async def test_guarantee_falls_back_to_research_when_patches_are_impossible(guarantee_coord, pruned, monkeypatch):
import hyperloom.orchestrator.specialists.runner as runner_mod


def test_fresh_launch_conflict_exits_before_the_session_starts(tmp_path, monkeypatch):
import hyperloom.inference_optimizer.cli as cli

@pytest.mark.parametrize("pruned", [True, False])
async def test_guarantee_falls_back_to_research_when_patches_are_impossible(guarantee_coord, pruned, monkeypatch):
import hyperloom.orchestrator.specialists.runner as runner_mod
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1804 -- feat(specialist): operator-defined custom specialist type

What it does: Operators could not point a specialist at a focus outside the fixed specialist catalogue. This PR adds --custom-specialist-prompt-file / --custom-specialist-description, persists the definition in SharedState, lets Orchestration select custom_specialist (EMIT list plus an operator-defined guide line, only when configured), admits it in the policy gate only when a definition exists, renders the operator focus into the specialist system prompt, and guarantees one dispatch per session in FRAMEWORK_AGENT (research-mode fallback when no git tree), with resume replacing the definition and conflict checks against --orch-prompt, --no-framework-agent and a zero research lane.

Blocking issues: 1

  1. [X5] New operator-facing flags are documented only in --help [verified]
    Problem: --custom-specialist-prompt-file and --custom-specialist-description are added at src/hyperloom/inference_optimizer/cli/parser.py:816 but appear nowhere in src/hyperloom/inference_optimizer/SKILL.md (neither the caller-responsibility flag table at line 730 nor the FRAMEWORK_AGENT section at line 402) or in docs/how-to/optimize-custom-workload.md (FRAMEWORK_AGENT section, line 144).
    Impact: SKILL.md tells the agent to forward every operator-stated value as its matching CLI flag, but an operator request such as "add a specialist focused on X" has no row to map to, so the definition is silently dropped; operators reading the how-to never learn the knob, its unset default (feature off), the 16 KiB / 200-char limits or the conflict rules.
    Action: Add a row for the pair to the SKILL.md caller-responsibility table and a short paragraph to both FRAMEWORK_AGENT sections: operator-facing, unset means disabled, the size limits, the conflicts with --orch-prompt / --no-framework-agent / --research-lane-capacity 0, and that resume with a new definition replaces the old one.

Checked: all 13 changed files; cli/init.py _load_custom_specialist_args/_custom_specialist_conflict/_apply_custom_specialist_resume/_build_orchestration_prompt; prompt_builder _llm_selectable_domains/_format_emit_hint; gate _validate_custom_specialist_configured; dispatch maybe_ensure_custom_specialist; dispatcher.py:743; runner _prepare; sub_agent_runner _context_for; SharedState from_dict; loop/conversation.py observation consumer | Ran: pytest test_custom_specialist.py (47 passed); ruff C901 on dispatch.py and cli/init.py; git merge-tree against main (clean); live GLM e2e session on Spur (Qwen3-8B, sglang) where Orchestration selected custom_specialist and it completed | Base: dc46f8d | Head: 875f873

List the operator-defined specialist flags in the SKILL caller table, the IR-4 specialist contract, the workload configuration reference and the FRAMEWORK_AGENT concept section.
Recognise the custom specialist only by its own name and keep the operator-defined layer out of the knowledge-domain tags. A generic 'custom' tag or domain_hint no longer makes stalled-domain forcing pick a denied custom dispatch every tick, and a built-in dispatch tagged 'custom' is admitted as before.
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1804 -- feat(specialist): operator-defined custom specialist type

What it does: Operators had no way to point a specialist at a focus outside the fixed specialist catalogue. This PR adds --custom-specialist-prompt-file / --custom-specialist-description, persists the definition in SharedState, and registers one operator-defined catalogue entry, custom_specialist, that is kept out of KNOWLEDGE_DOMAIN_TAGS. When configured, Orchestration is offered the domain, the policy gate admits it, the specialist system prompt carries the operator focus verbatim, and the Coordinator requests one run per macro cycle in FRAMEWORK_AGENT until one starts. When unconfigured, the gate denies any dispatch that names custom_specialist; every prompt, round counter, stalled-domain choice and other gate decision is identical to main, and state.json only gains the three new fields at their empty defaults, which both main and this branch load.

Blocking issues: none

Checked: all 16 changed files; unconfigured parity base dc46f8d vs head (15 Orchestration prompts, 260 built-in specialist system/user prompt pairs across 13 tag variants including custom and custom_specialist, KNOWLEDGE_DOMAIN_TAGS, _anchor_for/domain_for_tag/normalize_dispatch_tags, and a 260-row PolicyGate matrix: identical except dispatches naming custom_specialist, which are now denied as the description states); configured sessions acd7412 vs head after the anchor change (gate, profile, lanes, runner._prepare domain and focus identical; the prompt differs only in the KB anchor: line); maybe_ensure_custom_specialist on a real Coordinator (unconfigured: no task query and no intent; configured: one research-mode custom_specialist task enqueued through the real gate and router, second call a no-op); maybe_force_stalled_domain_specialist and stalled_domains after a custom spawn; warmup domain_hint back-fill and gate autofill for the custom domain; resume ordering at cli/init.py:1878/1905/1979; earlier X5 card re-verified as fixed by acd7412; independent second reader attacked the "none" conclusion and it held | Ran: pytest test_custom_specialist.py (50 passed); test_per_domain_prompts.py + test_policy_gate.py + test_specialist_dispatch_params.py (156 passed); test_specialist_lifecycle.py + test_cli_bootstrap.py + test_cli_resume_launch_shape.py + test_shared_state_persistence.py (87 passed); head test_custom_specialist.py against acd7412 (7 failed, 43 passed, including the three unconfigured-parity tests); PYTHONPATH probes for parity, configured shapes, pump and router; gh pr checks at bcb506a (30 pass, 7 skipping, 0 failing; the test shards, pylint and CodeQL were still in progress when ci.txt was fetched and completed green on the same head) | Base: dc46f8d | Head: bcb506a

…nd allow a bare reset

The custom user prompt left out the Experience KB section while the
FRAMEWORK_AGENT warmup still recorded the block as injected; render it.

A stored definition made --resume-from ... --reset-state exit 2 with a
remedy that could not run; only a definition passed on this launch now
conflicts with --reset-state, and a bare reset clears the stored one.
…d router

Enqueue the guarantee on a real Coordinator and check that it creates one
research task and holds back while it is queued; also skip on a queued or
running domain-form custom task.
…g branches

Fold the resume conflict into the existing resume-conflict check, print the
re-export from the helper that applies it, and set the spawn flag without a
branch, so _run_optimize and _spawn_fitting_queued keep their merge-base
complexity.
@lishuoshuo-amd

Copy link
Copy Markdown
Collaborator Author

PR #1804 -- feat(specialist): operator-defined custom specialist type

What it does: Operators had no way to give a specialist a focus of their own, because the specialist catalogue is fixed. This PR adds --custom-specialist-prompt-file / --custom-specialist-description, persists the definition in SharedState, and registers one session-scoped catalogue entry, custom_specialist, kept out of KNOWLEDGE_DOMAIN_TAGS. When configured, Orchestration is offered the domain, the policy gate admits it, its system prompt carries the operator focus verbatim over a reduced user prompt that keeps the Experience KB block, and the Coordinator requests it once per macro cycle in FRAMEWORK_AGENT until one starts. Conflicting launches exit 2 before any state is written, and a bare --reset-state clears a stored definition. When unconfigured, prompts and gate decisions are unchanged except that dispatches naming custom_specialist are denied. The latest commit folds the custom resume check into the existing resume-conflict chain, prints the re-exported custom specialist resume line only when the flags are re-passed (a bare resume or bare reset no longer prints it), and sets the spawn flag with |=, bringing _run_optimize and _spawn_fitting_queued back to their merge-base complexity; state, prompts, gate decisions and exit codes are unchanged by it.

Blocking issues: none

Checked: all 16 changed files; head delta a6f876f..e798aba (cli/init.py _resume_conflict chain, _apply_custom_specialist_resume print, dispatcher.py |=; 6b05215 tests); resume order at head (latency scope, latency resume, custom conflict at cli/init.py:1874-1882, first save :1905, apply :1979, save :2113, reset :2450, Coordinator :2454); D12 per changed src file, head vs merge base: ruff C901 at 20 lists the same eight backlog units with equal numbers on both sides (_run_optimize 99/99, _spawn_fitting_queued 29/29; 101 and 30 at a6f876f), no added function above 8, no raised one above 11 (main 8 to 11); earlier C1 (Experience KB block) and S5 (bare --reset-state) fixes still hold at head; unconfigured parity base dc46f8d vs head (Orchestration and built-in specialist prompts identical; gate identical except dispatches naming custom_specialist) | Ran: pytest test_custom_specialist.py at head (57 passed) and the head file on an a6f876f worktree (57 passed); five reverted-fix mutations at head (queued/running check 4 failed, tag matching 3, spawn flag 1, reset rule 2, Experience section 1); 16 related suites one file at a time (481 passed); five dispatcher/spawn suites plus test_latency_budget and test_preflight_resume_framework (319 passed); PYTHONPATH probes for resume print and flag semantics (head vs a6f876f), parity and reset; git fetch origin main + git merge-tree --write-tree HEAD FETCH_HEAD (main e5f2be1, clean, tree 071b8c02a) and eight suites on the merged tree (342 passed); CI at head (29 success, 7 skipped, 0 failing; readthedocs success); an independent second reader attacked the "none" conclusion and every claim here, and the conclusion held | Base: dc46f8d | Head: e798aba

LGTM

@xiaofei-zheng

xiaofei-zheng commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

PR #1804 -- feat(specialist): operator-defined custom specialist type

What it does: an operator could not add a specialist domain of their own -- the catalogue in specialists/domains.py was the whole vocabulary. This adds one operator-defined type, custom_specialist, configured by --custom-specialist-prompt-file + --custom-specialist-description: the pair is validated in main(), persisted on SharedState, offered to Orchestration in the specialist EMIT list only when configured, denied at the policy gate (specialist_custom_not_configured) when it is not, rendered as the specialist's domain focus verbatim, and dispatched at least once per session by the Coordinator in FRAMEWORK_AGENT. The operator-defined layer is kept out of KNOWLEDGE_DOMAIN_TAGS, so round counters and stalled-domain forcing are untouched.

Blocking issues: none

Checked: the full consumer set of the new catalogue entry (SPECIALIST_DOMAINS, SPECIALIST_DOMAIN_KEYS, KNOWLEDGE_DOMAIN_TAGS, kb_anchor, get_domain, domain_for_tag, rounds_since_last_*) across the head tree; the resume ordering in cli/__init__.py (load_or_init 1864 -> conflict check 1873 -> _apply_custom_specialist_resume 1979 -> state.save 2113 -> load_or_init 2430 -> _reset_state_file 2449) and the single _seed_shared_state call site at 2303 in the fresh-launch branch; maybe_ensure_custom_specialist for escape into the FRAMEWORK pump (IntentRouter.handle_intent records PolicyDenied and returns), double dispatch (queued+running dedupe plus the per-macro-cycle marker set before the dispatch) and the research-mode fallback; the synthetic gap.custom.session against append_gap_attempt (state/gaps.py:105 returns None for an unknown id); SharedState.from_dict for the three additive fields on a pre-PR state.json; --research-lane-capacity's default (research_lane_ceiling(), always positive) behind the conflict check.
Ran: ruff check --select C901 --config lint.mccabe.max-complexity=20 over the changed src/ files at head and at the merge base -- the same 8 units over the ceiling with identical numbers on both sides (_run_optimize 99/99, warm_specialist_params 58/58, _finalize 33/33, _spawn_fitting_queued 29/29, ...), so nothing this PR adds or raises crosses 20; hashed build_orchestration_prompt for 5 phases and build_specialist_prompts for all 10 built-in domains x {patch, research} x {no tag, custom, communication} at head and at base -- every pair byte-identical, the only new rows being custom_specialist's. The repo's test suite could not run on this host (fcntl); CI is green at head across both coverage jobs and all 12 shards.
Base: dc46f8d | Head: e798aba

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.

Approving on the review posted above: no blocking issues.

@xiaofei-zheng
xiaofei-zheng merged commit 7b518de into main Oct 11, 2026
37 checks passed
@xiaofei-zheng
xiaofei-zheng deleted the feat/custom-specialist-type branch October 11, 2026 12:06
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