Skip to content
5 changes: 4 additions & 1 deletion docs/conceptual/optimization-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,10 @@ One phase, two levers worked in parallel:
independent of how fast proposals arrive.

`specialist` serves both levers — investigation, patch authoring, and
candidate discovery are all dispatches of the one specialist action.
candidate discovery are all dispatches of the one specialist action. An
operator can add one domain of their own, `custom_specialist`, whose focus is
the text they supply at launch; until one has started, the Coordinator
requests it at most once per macro cycle in this phase.

The phase advances to KERNEL_AGENT only when **both** levers are dry
(`optimize_no_more_leverage`). Either arm going quiet raises
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ Set with CLI flags, not env vars. Pre-set `ISL` / `OSL` / `CONC` / `PRECISION` /
- **Session / resume:** `--resume-from`, `--force-resume`, `--reset-state`,
`--extend-hours`.
- **Quantization:** `--quantize`, `--quantize-scheme`.
- **Operator-defined specialist:** `--custom-specialist-prompt-file` (focus
text, max 16 KiB) and `--custom-specialist-description` (one line, max 200
chars), passed together; unset means no `custom_specialist`. Refused with
`--orch-prompt`, `--no-framework-agent`, `--research-lane-capacity 0` or
`--reset-state`. Persisted in `state.json`; re-pass both on `--resume-from`
only to replace the definition, or pass `--reset-state` alone to clear it.

Run `hyperloom optimize --help` for the exhaustive flag list.

Expand Down
10 changes: 9 additions & 1 deletion src/hyperloom/inference_optimizer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,14 @@ brief:
exclusively own their leased cards: they may start/stop their own servers
(any port that is not the production serving port 8888), profile, autotune,
and run real benchmark loops. The one invariant is that they must not touch
the production serving process, its cards, or port 8888.
the production serving process, its cards, or port 8888.
- **Operator-defined specialist**: with `--custom-specialist-prompt-file` and
`--custom-specialist-description` set, `custom_specialist` joins the
domains Orchestration may dispatch, and its system prompt carries the
operator's focus verbatim. Until a `custom_specialist` task has started, the
Coordinator requests one at most once per macro cycle in FRAMEWORK_AGENT,
in research mode when source patches cannot be authored. Unset, Orchestration
is not offered the domain and the policy gate denies it.
- **Plateau**: both arms' signals are computed every tick and rendered in the
orchestration prompt. One arm dry is advisory — the phase stays open on the
other lever. **Both arms dry advances the phase** via
Expand Down Expand Up @@ -749,6 +756,7 @@ and the operator's stated value is lost:
| Target advisory | `--no-target-advisory` | Disable external-target hints in prompts without disabling final-report comparison. `primary_gap` uses the existing latency/throughput categories; the interactivity axis is displayed as interactivity. |
| Quantization prelude | `--quantize` | Optional. Natural-language quantization request. Runs the quantization-agent once before the loop and rewrites `--model` to the quantized model. See Step 2b. Never runs on a resume. |
| Env pins | `--extra-env NAME=VALUE` | Repeatable; forward **every** one verbatim as its own flag (do not drop any or fold into the `Environment:` block). The CLI persists them in `state.json` and serializes them into `$INFERENCE_OPTIMIZER_EXTRA_ENV`; a dropped pin is lost silently — e.g. a missing `SGLANG_USE_AITER=0` leaves the explore aiter-MoE filter blind. A `--resume-from` re-exports the persisted set, so re-pass them only to change the set. |
| Operator-defined specialist | `--custom-specialist-prompt-file` + `--custom-specialist-description` | Only when the prompt asks for a specialist with its own focus. Write the operator's focus text verbatim to a file and pass its path (max 16 KiB); the description is one line (max 200 chars) telling Orchestration when to pick it. Pass both or neither. The CLI refuses them (exit 2) together with `--orch-prompt`, `--no-framework-agent`, `--research-lane-capacity 0` or `--reset-state`. Persisted in `state.json`; a `--resume-from` keeps the stored definition, so re-pass both only to replace it, or pass `--reset-state` alone to clear it. |

### Step 2b — Optional quantization prelude (`--quantize`)

Expand Down
98 changes: 93 additions & 5 deletions src/hyperloom/inference_optimizer/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ def _build_orchestration_prompt(
agentx_corpus_shape: Mapping[str, Any] | None = None,
agentx_grading: Mapping[str, Any] | None = None,
agentx_backend: str = "",
custom_specialist_description: str = "",
) -> str:
"""Compose the Orchestration system prompt from typed inputs (``--orch-prompt`` overrides)."""
registry = action_registry or ACTION_CATALOGUE
Expand All @@ -364,6 +365,7 @@ def _build_orchestration_prompt(
agentx_corpus_shape=agentx_corpus_shape,
agentx_grading=agentx_grading,
agentx_backend=agentx_backend,
custom_specialist_description=custom_specialist_description,
rules_fragment_path=_orchestration_rules_fragment_path(),
framework_source_roots=resolve_kernel_search_roots(),
session_framework_tree=resolve_framework_tree(framework),
Expand Down Expand Up @@ -939,6 +941,77 @@ def _resolve_critic_choice(args: argparse.Namespace) -> str:
return chosen


_CUSTOM_SPECIALIST_PROMPT_MAX_BYTES = 16 * 1024
_CUSTOM_SPECIALIST_DESCRIPTION_MAX_CHARS = 200


def _load_custom_specialist_args(args: argparse.Namespace) -> None:
"""Validate the ``--custom-specialist-*`` pair and set ``args.custom_specialist_prompt``.

Raises:
ValueError: When only one flag is given or either value is unusable.
"""
path = getattr(args, "custom_specialist_prompt_file", None)
description = getattr(args, "custom_specialist_description", None)
args.custom_specialist_prompt = ""
if path is None and description is None:
return
if path is None or description is None:
raise ValueError("--custom-specialist-prompt-file and --custom-specialist-description must be passed together")
try:
text = Path(path).expanduser().read_text(encoding="utf-8").strip()
except (OSError, UnicodeDecodeError) as exc:
raise ValueError(f"--custom-specialist-prompt-file: cannot read {path}: {exc}") from exc
if not text:
raise ValueError(f"--custom-specialist-prompt-file: {path} is empty")
if len(text.encode("utf-8")) > _CUSTOM_SPECIALIST_PROMPT_MAX_BYTES:
raise ValueError(f"--custom-specialist-prompt-file: {path} exceeds {_CUSTOM_SPECIALIST_PROMPT_MAX_BYTES} bytes")
description = description.strip()
if not description or "\n" in description or "\r" in description:
raise ValueError("--custom-specialist-description must be a single non-empty line")
if len(description) > _CUSTOM_SPECIALIST_DESCRIPTION_MAX_CHARS:
raise ValueError(
f"--custom-specialist-description exceeds {_CUSTOM_SPECIALIST_DESCRIPTION_MAX_CHARS} characters"
)
args.custom_specialist_prompt = text
args.custom_specialist_description = description


def _apply_custom_specialist_resume(args: argparse.Namespace, state: SharedState) -> None:
"""Flags passed on a resume replace the stored custom specialist; otherwise the stored one stays.

A changed definition is guaranteed its own first run.
"""
prompt = getattr(args, "custom_specialist_prompt", "")
if not prompt:
return
if (prompt, args.custom_specialist_description) != (
state.custom_specialist_prompt,
state.custom_specialist_description,
):
state.custom_specialist_dispatched = False
state.custom_specialist_prompt = prompt
state.custom_specialist_description = args.custom_specialist_description
print(f" re-exported custom specialist: {state.custom_specialist_description}")


def _custom_specialist_conflict(args: argparse.Namespace, state: SharedState) -> str:
"""Return why a custom specialist from this launch or the stored state cannot run, or ``""``."""
# --reset-state wipes the stored definition, so only a definition passed on this launch counts.
stored = "" if getattr(args, "reset_state", False) else state.custom_specialist_prompt
if not (getattr(args, "custom_specialist_prompt", "") or stored):
return ""
if args.orch_prompt:
return "--orch-prompt replaces the Orchestration prompt, so custom_specialist would never be offered"
if bool(getattr(args, "no_framework_agent", False)) or not state.framework_agent_phase_enabled:
return "the FRAMEWORK_AGENT phase is disabled, so custom_specialist cannot be guaranteed to run"
if int(getattr(args, "research_lane_capacity", 1) or 0) <= 0:
return "--research-lane-capacity 0 builds no specialist executor"
if getattr(args, "reset_state", False):
return "--reset-state wipes the custom specialist definition; reset first, then launch with the flags"
return ""


def _reset_state_file(session_dir: Path, *, framework: str) -> None:
"""Back up ``state.json`` to ``state.json.preReset.<unix_ts>`` and start fresh (Recipe KB untouched).

Expand Down Expand Up @@ -1797,12 +1870,15 @@ async def _run_optimize(args: argparse.Namespace) -> int:
file=sys.stderr,
)
sys.exit(2)
_latency_conflict = latency_budget_scope_error(
state.framework, getattr(args, "max_latency_ms", None)
) or latency_budget_resume_conflict(state, getattr(args, "max_latency_ms", None))
if _latency_conflict:
# Checked before any resume step persists state, so a refused resume leaves the session as it was.
_resume_conflict = (
latency_budget_scope_error(state.framework, getattr(args, "max_latency_ms", None))
or latency_budget_resume_conflict(state, getattr(args, "max_latency_ms", None))
or _custom_specialist_conflict(args, state)
)
if _resume_conflict:
session_lock.release()
print(f"ERROR: cannot resume this session -- {_latency_conflict}.", file=sys.stderr)
print(f"ERROR: cannot resume this session -- {_resume_conflict}.", file=sys.stderr)
sys.exit(2)
prior_stop = state.stop_reason
print(f"Resuming session: {session_dir}")
Expand Down Expand Up @@ -1900,6 +1976,7 @@ async def _run_optimize(args: argparse.Namespace) -> int:
print(f" re-exported server_args : {_resume_server_args}")
if _resume_extra_env:
print(f" re-exported extra_env : {','.join(sorted(_resume_extra_env))}")
_apply_custom_specialist_resume(args, state)
# Custom-workload paths: an explicit --framework-path / --benchmark-scripts-dir on this resume wins, else the
# persisted value.
_restore_operator_supplied_paths_from_state(args, state)
Expand Down Expand Up @@ -2415,6 +2492,7 @@ async def _run_optimize(args: argparse.Namespace) -> int:
agentx_corpus_shape=coordinator.shared_state.agentx_corpus_shape,
agentx_grading=coordinator.shared_state.grading,
agentx_backend=coordinator.shared_state.agentx_backend,
custom_specialist_description=coordinator.shared_state.custom_specialist_description,
),
"critic": args.critic_prompt or _load_critic_prompt(),
}
Expand All @@ -2435,6 +2513,7 @@ async def _run_optimize(args: argparse.Namespace) -> int:
agentx_corpus_shape=coordinator.shared_state.agentx_corpus_shape,
agentx_grading=coordinator.shared_state.grading,
agentx_backend=coordinator.shared_state.agentx_backend,
custom_specialist_description=coordinator.shared_state.custom_specialist_description,
),
)
# Build specialist executor only when research_lane capacity > 0 (0 degrades to LLM-direct grid).
Expand Down Expand Up @@ -2562,6 +2641,15 @@ def main(argv: list[str] | None = None) -> int:
v = getattr(args, attr)
if v and Path(v).exists():
setattr(args, attr, Path(v).read_text(encoding="utf-8"))
try:
_load_custom_specialist_args(args)
except ValueError as exc:
parser.error(str(exc))
# A fresh session's conflicts depend on flags only; a resume checks them against its stored state.
if not args.resume_from:
custom_conflict = _custom_specialist_conflict(args, SharedState())
if custom_conflict:
parser.error(custom_conflict)
return asyncio.run(_run_optimize(args))
if args.command == "recover":
return _run_recover_session(args)
Expand Down
2 changes: 2 additions & 0 deletions src/hyperloom/inference_optimizer/cli/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ def _int_arg(arg_name: str, default: int) -> int:
# rather than from argv.
operator_server_args=str(getattr(args, "server_args", "") or "").strip(),
operator_extra_env=parse_operator_extra_env(args),
custom_specialist_prompt=str(getattr(args, "custom_specialist_prompt", "") or ""),
custom_specialist_description=str(getattr(args, "custom_specialist_description", "") or ""),
bypass_scripts_dir=os.environ.get("HYPERLOOM_BYPASS_SCRIPTS_DIR", "").strip(),
framework_repo_path=os.environ.get("FRAMEWORK_REPO_PATH", "").strip(),
benchmark_backend=os.environ.get("HYPERLOOM_BENCHMARK_BACKEND", "").strip().lower(),
Expand Down
19 changes: 19 additions & 0 deletions src/hyperloom/inference_optimizer/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,25 @@ def _build_parser() -> argparse.ArgumentParser:
"--orch-prompt", type=str, default=None, help="Override Orchestration system prompt (file path or inline)"
)
opt.add_argument("--critic-prompt", type=str, default=None, help="Override Critic system prompt")
opt.add_argument(
"--custom-specialist-prompt-file",
dest="custom_specialist_prompt_file",
type=str,
default=None,
help="Register an operator-defined specialist type (custom_specialist) whose domain focus is this file's "
"text (max 16 KiB). Requires --custom-specialist-description. Orchestration may select it, and the "
"Coordinator dispatches it at least once in FRAMEWORK_AGENT. Persisted for --resume-from; passing it on a "
"resume replaces the stored definition. The text is written in clear to state.json and every task's "
"prompt.md: do not put credentials in it.",
)
opt.add_argument(
"--custom-specialist-description",
dest="custom_specialist_description",
type=str,
default=None,
help="One line (max 200 chars) telling Orchestration when to pick custom_specialist. Requires "
"--custom-specialist-prompt-file.",
)
opt.add_argument(
"--local-kb-root",
dest="local_kb_root",
Expand Down
Loading
Loading