Claude Skill

orchestrate

Drive the entire cap-evolve pipeline end to end, autonomously. Use when the user wants the whole optimization run with minimal hand-holding. Sequences intake → implement-and-check → baseline → the chosen algorithm loop → finalize → report, enforces the cap-evolve-check hard gate

LLM Mart · 0 points · 13 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download skillberry-ai-cap-evolve-skills_orchestrate_orchestrate-49fcedb.zip · 9 KB
Part of skillberry-ai/cap-evolve — 22 skills

Install

skills CLI npx skills add https://github.com/skillberry-ai/cap-evolve/tree/main/skills/orchestrate/orchestrate
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skillberry-ai-cap-evolve@llmmart
Git git clone https://github.com/skillberry-ai/cap-evolve.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole skillberry-ai/cap-evolve collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

orchestrate — the whole pipeline, end to end

orchestrate is the autonomous driver: it runs every phase in order and enforces the guardrails so a full optimization run needs little supervision. It does not add new logic — it sequences the phase skills and refuses to let the run skip a safety check. Its value is that the honesty discipline (ask-if-missing, hard gate, val-only acceptance, sealed test) is applied automatically rather than relying on the operator to remember each one.

Inputs / outputs (manifest tokens)

  • needs: project — resolved from capevolve.yaml (which capability / optimizer / algorithm / budget).
  • provides: report — the end-to-end result: baseline → best val → sealed test, with the winner named.

The sequence (and the guardrail at each step)

  1. intake — collect inputs, scaffold the project, ask for any missing NEEDED input (never fabricate one).
  2. implement-and-check — implement the adapter; cap-evolve check must be green (HARD GATE — do not advance until {"ok": true}).
  3. baseline — freeze the split (once, seeded), score the seed on val, check headroom (stop early if the seed already saturates val).
  4. <algorithm> — run the loop named in capevolve.yaml (default all-at-once): propose → evaluate(val) → diagnose → gate → accept/reject, until budget/stall. Acceptance is always on val, by significance (Δ > k·SE).
  5. finalize — score the best candidate on the sealed test split, once.
  6. report — baseline vs test; name the winner; surface pass^k and uncertainty.

The wiring is validated structurally: each step's needs must be satisfied by an upstream provides in the manifest, so a misordered or incompatible pipeline is caught before it runs.

Agent-mode loop (orchestration_mode: agent)

When the spec sets orchestration_mode: agent, cap-evolve does intake → check → baseline, then hands YOU the loop (it prints a handoff with the run_dir). YOU — the coding agent in this conversation — run the optimization yourself: read the selected algorithm's "Agent-mode loop" section (skills/algorithms/<algorithm_skill>/SKILL.md), make the capability edits, and run the evaluations directly. You do not delegate the search to a separate optimizer agent — that per-iteration "optimizer" edit-proposer is a deterministic-mode concept; in agent mode you are the optimizer. (You may still spawn helper subagents for parallel sub-tasks if an algorithm's loop calls for it, but the driver is you.)

One continuous agent, with the user in the loop. The agent that ran the intake/onboarding is the same agent that drives the optimization — one continuous conversation, not a fresh agent spawned by the CLI. cap-evolve run does not start a new agent; it only does the baseline plumbing and hands the loop back to you. Stay reachable the whole time: the user can interject, steer, re-prioritize, or halt at any round (governance throughout), and you fold their input into the next round. Ask setup questions up front (in intake) so the loop can run without blocking on a human — but never treat the run as a fire-and-forget subprocess; it is you, continuing.

Rules:

  1. Drive through cap-evolve primitives, never around them. Every evaluation goes through cap-evolve's eval (so per-rollout JSON + results land in the run dir); every accept/reject goes through the gate on val (Δ > k·SE); every accepted candidate is snapshotted via the store; log round boundaries with the run dir's event log. This is what keeps events.jsonl/rollouts/results/snapshots populated so the dashboard renders with no changes.
  2. Honesty is self-policed: never touch the sealed test split until the end; revert on regression; acceptance is val-only.
  3. Between rounds, verify the run dir has what the dashboard needs before continuing: the round's events are logged, results/rollouts are written, and each accepted candidate is snapshotted. If a round produced no run-dir artifacts, the dashboard will be blank — fix that before proceeding.
  4. Re-read stop_condition each round. Stop when it is met, or budget/stall hits.
  5. Seal once, at the end: run the finalize phase script skills/phases/finalize/scripts/run.py (scores the best candidate on the sealed test split exactly once), then the report phase script skills/phases/report/scripts/run.py — see docs/AGENT_ORCHESTRATION.md for the exact invocations. Neither is a cap-evolve subcommand; both are scripts. A run with no finalize has no result.

How to run

python scripts/run.py --spec .capevolve/project/capevolve.yaml            # print the plan
python scripts/run.py --spec .capevolve/project/capevolve.yaml --execute  # run it (cap-evolve run)

Without --execute it prints the ordered plan (sequence, components, gate mode, budget) for inspection — run this first to confirm the pipeline before spending anything. Or, host-agnostic, follow RUN.md step by step; or cap-evolve run --spec.

Stopping rules

Stop when any holds:

  • budget exhausted — max_iterations, max_metric_calls, or max_usd hit.
  • stall — N consecutive rejects (the search has plateaued; more tries just burn budget chasing noise the gate will keep rejecting).
  • no headroom — the baseline already saturates val.

Whatever the stop reason, always finish with finalize + report so the honest, sealed-test number is recorded. An optimization run with no finalize has no result.

What good vs bad looks like

  • Good: the plan inspected before --execute; every guardrail enforced automatically; the run ends with a sealed-test number and a named winner, even when the answer is "no significant gain".
  • Bad: advancing past a red cap-evolve check; gating on train; finalizing more than one candidate; declaring success on val without ever scoring test.

References

  • references/concepts.md — the phase sequence as a needs/provides DAG, where each honesty guardrail lives, and the stop rules, with sources.
Files (cap-evolve)
  • references
    • concepts.md 3.8 KB
      # Concepts — orchestrating the pipeline honestly
      
      > orchestrate adds no measurement logic; it sequences the phases and enforces,
      > automatically, the guardrails that keep a run honest. This note maps the
      > pipeline as a dependency graph and shows where each guardrail lives.
      > Implementation: this skill's `scripts/run.py` (`needs`/`provides` resolution).
      
      ## The pipeline is a needs/provides DAG
      
      Each phase declares what tokens it `needs` and `provides`. orchestrate orders the
      phases so every `needs` is satisfied by an upstream `provides`, which is also how a
      misordered or incompatible pipeline is caught *before* it runs:
      
      ```
      intake            provides: project, tasks
        → implement-and-check   needs: project        provides: checked
          → baseline            needs: project, tasks  provides: splits, baseline, candidate
            → <algorithm>       needs: candidate, ...  (propose → evaluate → diagnose → gate)
              → finalize        needs: candidate       provides: report (sealed test)
                → report        reads run dir          provides: report (human summary)
      ```
      
      The edges are not cosmetic: `baseline` cannot run until `implement-and-check`
      emits `checked`, so the hard gate cannot be skipped; `finalize` consumes the best
      candidate chosen on val, so selection happens before the test seal.
      
      ## Where each honesty guardrail lives
      
      The pipeline's honesty is the sum of per-phase invariants, applied in order:
      
      1. **ask-if-missing (intake)** — a missing NEEDED input is a question for the user,
         never a fabrication. Wrong here and everything downstream measures nothing.
      2. **hard gate (implement-and-check)** — `cap-evolve check` must be green; the adapter
         must be implemented and the scorer deterministic before any budget is spent.
      3. **freeze-once split + headroom (baseline)** — the split is written once and
         seeded; if the seed already saturates val, stop.
      4. **val-only significance gate (the loop)** — acceptance is decided on val, by
         Δ > k·SE, never on train (overfit) and never on test (leak).
      5. **sealed test, scored once (finalize)** — `test_used` makes re-scoring an error,
         so the headline number is unbiased.
      6. **honest reading (report)** — test vs baseline, the val-test gap as overfitting,
         pass^k as reliability, uncertainty always shown.
      
      orchestrate's contribution is that these are applied *automatically and in order*,
      rather than depending on an operator to remember each one under time pressure.
      
      ## Stop rules — and why each exists
      
      - **budget exhausted** (`max_iterations` / `max_metric_calls` / `max_usd`): the
        hard ceiling.
      - **stall** (N consecutive rejects): the search has plateaued. Because the gate
        rejects non-significant gains, a run of rejects means remaining proposals are
        not clearing the noise floor — more tries mostly burn budget. Stalling early
        also limits the multiple-comparisons exposure (every extra candidate is another
        chance for a noise spike to look like a win).
      - **no headroom**: the baseline already saturates val; there is nothing to gain.
      
      Whatever the reason, the run **always ends with finalize + report**. A run that
      stops without scoring the sealed test has produced edits but no result — and a
      result is the only deliverable.
      
      ## Sources
      - GEPA: Reflective Prompt Evolution (Agrawal et al., 2025) — the
        propose → reflect → evaluate → select loop orchestrate sequences:
        https://arxiv.org/abs/2507.19457
      - τ-bench (Yao et al., 2024) — reliability (pass^k) as the end-of-run signal:
        https://arxiv.org/abs/2406.12045
      - Koehn, "Statistical Significance Tests for MT Evaluation" (EMNLP 2004) — the
        significance discipline the acceptance loop enforces:
        https://aclanthology.org/W04-3250/
      - Hastie, Tibshirani, Friedman, *Elements of Statistical Learning* — train/val/test
        roles the DAG encodes: https://hastie.su.domains/ElemStatLearn/
      
  • scripts
    • abstract.py 148 B
      """orchestrate composes other skills; it declares no abstract methods of its own.
      check.py verifies it can resolve the manifest and read a spec."""
      
    • check.py 685 B
      """Wiring check for orchestrate."""
      from __future__ import annotations
      import json, sys
      import _bootstrap  # noqa: F401
      def main() -> int:
          rep = {"ok": False, "problems": [], "notes": []}
          try:
              import run
              if not hasattr(run, "main"):
                  rep["problems"].append("run.py missing main()")
              from cap_evolve.specfile import read_yaml  # noqa: F401
              rep["notes"].append("orchestrate wiring ok")
          except Exception as e:  # noqa: BLE001
              rep["problems"].append(f"import failed: {e}")
          rep["ok"] = not rep["problems"]
          print(json.dumps(rep, indent=2))
          return 0 if rep["ok"] else 1
      if __name__ == "__main__":
          sys.exit(main())
      
    • run.py 4.8 KB
      """orchestrate — resolve capevolve.yaml + the manifest into a validated run plan.
      
      Builds the ordered sequence from the manifest + spec (NOT a hardcoded list) and
      validates the needs/provides DAG: walking the sequence, each step's ``needs``
      tokens must already be satisfied by an upstream step's ``provides`` (or be
      externally supplied — ``project``/``tasks`` come from intake/the adapter). A typo
      in a meta ``needs``/``provides`` token, or a step ordered before its producer,
      fails the plan loudly instead of silently running a broken pipeline.
      
      The sequence now includes intake + the cap-evolve check gate before baseline. With
      ``--execute`` it hands off to ``cap-evolve run``.
      """
      
      from __future__ import annotations
      
      import argparse
      import json
      import sys
      from pathlib import Path
      
      import _bootstrap  # noqa: F401
      
      from cap_evolve.specfile import read_yaml
      
      # Tokens not produced by any skill — supplied by intake / the project adapter.
      EXTERNAL_TOKENS = {"project", "tasks"}
      
      # The fixed pipeline shape (phases + the spec-selected algorithm). Capability +
      # optimizer skills are not sequenced steps — they are *bound into* the algorithm
      # step (the algorithm calls the optimizer; the capability defines the edit surface).
      PHASE_ORDER = ["intake", "implement-and-check", "baseline", "<algorithm>", "finalize", "report"]
      
      
      def _skills_dir() -> Path | None:
          here = Path(__file__).resolve()
          for parent in here.parents:
              if (parent / "_registry" / "manifest.json").exists():
                  return parent
              if (parent / "skills" / "_registry" / "manifest.json").exists():
                  return parent / "skills"
          return None
      
      
      def _load_manifest(skills_dir: Path) -> dict:
          return json.loads((skills_dir / "_registry" / "manifest.json").read_text())["skills"]
      
      
      def _resolve_algorithm(name: str) -> str:
          """Old hill-climb skill names collapse to the one ``hill-climb`` skill."""
          if name in ("all-at-once", "cyclic", "hardest-first"):
              return "hill-climb"
          return name or "hill-climb"
      
      
      def build_sequence(spec: dict) -> list[str]:
          algo = _resolve_algorithm(spec.get("algorithm_skill", "hill-climb"))
          return [algo if s == "<algorithm>" else s for s in PHASE_ORDER]
      
      
      def validate_dag(sequence: list[str], manifest: dict) -> dict:
          """Check that every step's needs are satisfied by an upstream provides.
      
          Returns ``{ok, satisfied: [...], problems: [...]}``.
          """
          available = set(EXTERNAL_TOKENS)
          problems, trace = [], []
          for step in sequence:
              s = manifest.get(step)
              if s is None:
                  problems.append(f"step {step!r} is not in the manifest")
                  continue
              needs = list(s.get("needs", []) or [])
              missing = [n for n in needs if n not in available]
              if missing:
                  problems.append(
                      f"step {step!r} needs {missing} which no upstream step provides "
                      f"(available so far: {sorted(available)})")
              trace.append({"step": step, "needs": needs,
                            "provides": list(s.get("provides", []) or []),
                            "missing": missing})
              available.update(s.get("provides", []) or [])
          return {"ok": not problems, "trace": trace, "problems": problems}
      
      
      def main(argv=None) -> int:
          p = argparse.ArgumentParser(prog="orchestrate")
          p.add_argument("--spec", default=".capevolve/project/capevolve.yaml")
          p.add_argument("--project", default=".capevolve/project")
          p.add_argument("--execute", action="store_true", help="run the plan via `cap-evolve run`")
          args = p.parse_args(argv)
      
          spec = read_yaml(Path(args.spec).read_text()) if Path(args.spec).exists() else {}
          skills_dir = _skills_dir()
          manifest = _load_manifest(skills_dir) if skills_dir else {}
      
          sequence = build_sequence(spec)
          dag = validate_dag(sequence, manifest) if manifest else {
              "ok": False, "problems": ["no manifest found — run build_manifest.py"], "trace": []}
      
          plan = {
              "sequence": sequence,
              "dag_valid": dag["ok"],
              "dag": dag,
              "capabilities": spec.get("capabilities"),
              "optimizer": spec.get("optimizer_skill"),
              "algorithm": _resolve_algorithm(spec.get("algorithm_skill", "hill-climb")),
              "focus": spec.get("algorithm_focus"),
              "gate_mode": spec.get("gate_mode", "auto"),  # auto → harness uses the paired per-task gate
              "budget": {"max_iterations": spec.get("max_iterations"), "stall": spec.get("stall")},
              "rule": "cap-evolve check must be green before baseline; test scored once at finalize.",
          }
      
          if not dag["ok"]:
              print(json.dumps(plan, indent=2))
              return 1
      
          if args.execute:
              from cap_evolve.cli import main as cli_main
              return cli_main(["run", "--spec", args.spec, "--project", args.project])
      
          print(json.dumps(plan, indent=2))
          return 0
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
    • _bootstrap.py 3.6 KB
      """Thin shim: locate cap_evolve, then defer to cap_evolve._bootstrap.
      
      Skill scripts ``import _bootstrap`` first. The real path-resolution logic lives
      ONCE in ``cap_evolve._bootstrap`` (so it can't drift across skills); this shim
      only has to find that package, which means a minimal upward walk for ``core/`` —
      the single bit of bootstrapping that genuinely must run before cap_evolve is
      importable. Everything else delegates.
      """
      
      from __future__ import annotations
      
      import os
      import sys
      from pathlib import Path
      
      
      def _seed_path() -> None:
          """Minimal: put a dir containing the cap_evolve package on sys.path.
      
          ``CAPEVOLVE_CORE`` is honoured BEFORE any ambient import. An editable install of a
          *different* cap-evolve checkout registers a ``sys.meta_path`` finder, which outranks
          both ``sys.path`` and ``PYTHONPATH`` — so "cap_evolve imports fine" is not evidence that
          it imports the checkout you are standing in. Deferring to the ambient package here made
          an explicit override unreachable, and the symptom was a stale core silently answering
          for this one (``ModuleNotFoundError: cap_evolve.constraints`` from a checkout that
          predates that module). An explicit env var wins.
          """
          env = os.environ.get("CAPEVOLVE_CORE")
          want = Path(env).resolve() if env else None
          if want and (want / "cap_evolve" / "__init__.py").exists():
              loaded = sys.modules.get("cap_evolve")
              already = getattr(loaded, "__file__", None)
              if already and Path(already).resolve().parent.parent == want:
                  return                      # right checkout already imported: touch nothing
              p = str(want)
              if p in sys.path:
                  sys.path.remove(p)
              sys.path.insert(0, p)
              if loaded is not None:
                  # Evicting a module makes a re-import yield a DIFFERENT object, so anything
                  # already holding a reference fails an `is` check. Only ever do it when the
                  # loaded package really is the wrong checkout — otherwise this "fix" becomes
                  # the bug (it broke two identity assertions in core/tests exactly once).
                  for name in [m for m in sys.modules
                               if m == "cap_evolve" or m.startswith("cap_evolve.")]:
                      sys.modules.pop(name, None)
              for finder in list(sys.meta_path):
                  if "cap_evolve" in getattr(finder, "MAPPING", {}):
                      sys.meta_path.remove(finder)
              return
          # A checkout's own core outranks an ambient install. Without this, a skill script run
          # from checkout X silently executed against checkout Y's cap_evolve (an editable install
          # registers a sys.meta_path finder, which outranks sys.path), and the only symptom was
          # missing modules — or, worse, a green result measured against the wrong tree.
          here = Path(__file__).resolve()
          own = next((p / "core" for p in here.parents
                      if (p / "core" / "cap_evolve" / "__init__.py").exists()), None)
          if own is not None:
              os.environ.setdefault("CAPEVOLVE_CORE", str(own))
              return _seed_path()
          try:
              import cap_evolve  # noqa: F401
              return
          except Exception:
              pass
          cands = []
          for parent in here.parents:
              cands.append(parent / "core")
              cands.append(parent)
          for c in cands:
              if (c / "cap_evolve" / "__init__.py").exists():
                  p = str(c)
                  if p not in sys.path:
                      sys.path.insert(0, p)
                  return
      
      
      _seed_path()
      from cap_evolve._bootstrap import ensure_core  # noqa: E402
      
      # Anchor the upward walk at THIS skill script's location (not the core module's).
      ensure_core(Path(__file__).resolve())
      
  • meta.yaml 344 B
    component: orchestrate
    name: orchestrate
    summary: Autonomous end-to-end driver; sequences all phases with the cap-evolve-check hard gate and stop rules.
    entry: scripts/run.py
    abstract: scripts/abstract.py
    check: scripts/check.py
    needs: [project]
    provides: [report]
    compatible_with:
      capabilities: ["*"]
      optimizers: ["*"]
      algorithms: ["*"]
    
  • SKILL.md 6.7 KB
    ---
    name: orchestrate
    description: Drive the entire cap-evolve pipeline end to end, autonomously. Use when the user wants the whole optimization run with minimal hand-holding. Sequences intake → implement-and-check → baseline → the chosen algorithm loop → finalize → report, enforces the cap-evolve-check hard gate before spending budget, decides when to stop (budget/stall), and surfaces the honest test number at the end. Reads capevolve.yaml; respects the ask-user-if-missing rule for inputs.
    component: orchestrate
    argument-hint: "--spec .capevolve/project/capevolve.yaml [--execute]"
    allowed-tools: Read, Write, Edit, Bash
    provides: [report]
    needs: [project]
    sources: [evo]
    ---
    
    # orchestrate — the whole pipeline, end to end
    
    orchestrate is the autonomous driver: it runs every phase in order and enforces
    the guardrails so a full optimization run needs little supervision. It does not
    add new logic — it *sequences* the phase skills and refuses to let the run skip a
    safety check. Its value is that the honesty discipline (ask-if-missing, hard gate,
    val-only acceptance, sealed test) is applied automatically rather than relying on
    the operator to remember each one.
    
    ## Inputs / outputs (manifest tokens)
    - **needs:** `project` — resolved from `capevolve.yaml` (which capability / optimizer /
      algorithm / budget).
    - **provides:** `report` — the end-to-end result: baseline → best val → sealed
      test, with the winner named.
    
    ## The sequence (and the guardrail at each step)
    1. **intake** — collect inputs, scaffold the project, **ask for any missing NEEDED
       input** (never fabricate one).
    2. **implement-and-check** — implement the adapter; **`cap-evolve check` must be green**
       (HARD GATE — do not advance until `{"ok": true}`).
    3. **baseline** — freeze the split (once, seeded), score the seed on val, check
       **headroom** (stop early if the seed already saturates val).
    4. **\<algorithm\>** — run the loop named in `capevolve.yaml` (default `all-at-once`):
       propose → evaluate(val) → diagnose → gate → accept/reject, until budget/stall.
       Acceptance is **always on val**, by significance (Δ > k·SE).
    5. **finalize** — score the best candidate on the **sealed test split, once**.
    6. **report** — baseline vs test; name the winner; surface pass^k and uncertainty.
    
    The wiring is validated structurally: each step's `needs` must be satisfied by an
    upstream `provides` in the manifest, so a misordered or incompatible pipeline is
    caught before it runs.
    
    ## Agent-mode loop (`orchestration_mode: agent`)
    When the spec sets `orchestration_mode: agent`, cap-evolve does intake → check → baseline, then hands YOU the loop (it prints a handoff with the `run_dir`). **YOU — the coding agent in this conversation — run the optimization yourself:** read the selected algorithm's **"Agent-mode loop"** section (`skills/algorithms/<algorithm_skill>/SKILL.md`), make the capability edits, and run the evaluations directly. You do **not** delegate the search to a separate optimizer agent — that per-iteration "optimizer" edit-proposer is a *deterministic-mode* concept; in agent mode you are the optimizer. (You may still spawn helper subagents for parallel sub-tasks if an algorithm's loop calls for it, but the driver is you.)
    
    **One continuous agent, with the user in the loop.** The agent that ran the intake/onboarding is the *same* agent that drives the optimization — one continuous conversation, not a fresh agent spawned by the CLI. `cap-evolve run` does not start a new agent; it only does the baseline plumbing and hands the loop back to you. Stay reachable the whole time: the user can interject, steer, re-prioritize, or halt at any round (governance throughout), and you fold their input into the next round. Ask setup questions up front (in intake) so the loop can run without blocking on a human — but never treat the run as a fire-and-forget subprocess; it is you, continuing.
    
    Rules:
    
    1. **Drive through cap-evolve primitives, never around them.** Every evaluation goes through cap-evolve's eval (so per-rollout JSON + results land in the run dir); every accept/reject goes through the gate on **val** (Δ > k·SE); every accepted candidate is snapshotted via the store; log round boundaries with the run dir's event log. This is what keeps `events.jsonl`/rollouts/results/snapshots populated so the **dashboard renders with no changes**.
    2. **Honesty is self-policed:** never touch the sealed test split until the end; revert on regression; acceptance is val-only.
    3. **Between rounds, verify the run dir has what the dashboard needs** before continuing: the round's events are logged, results/rollouts are written, and each accepted candidate is snapshotted. If a round produced no run-dir artifacts, the dashboard will be blank — fix that before proceeding.
    4. **Re-read `stop_condition` each round.** Stop when it is met, or budget/stall hits.
    5. **Seal once, at the end:** run the finalize phase script `skills/phases/finalize/scripts/run.py` (scores the best candidate on the sealed test split exactly once), then the report phase script `skills/phases/report/scripts/run.py` — see `docs/AGENT_ORCHESTRATION.md` for the exact invocations. Neither is a `cap-evolve` subcommand; both are scripts. A run with no finalize has no result.
    
    ## How to run
    ```
    python scripts/run.py --spec .capevolve/project/capevolve.yaml            # print the plan
    python scripts/run.py --spec .capevolve/project/capevolve.yaml --execute  # run it (cap-evolve run)
    ```
    Without `--execute` it prints the ordered plan (sequence, components, gate mode,
    budget) for inspection — run this first to confirm the pipeline before spending
    anything. Or, host-agnostic, follow `RUN.md` step by step; or `cap-evolve run --spec`.
    
    ## Stopping rules
    Stop when **any** holds:
    - **budget exhausted** — `max_iterations`, `max_metric_calls`, or `max_usd` hit.
    - **stall** — N consecutive rejects (the search has plateaued; more tries just
      burn budget chasing noise the gate will keep rejecting).
    - **no headroom** — the baseline already saturates val.
    
    Whatever the stop reason, **always finish with finalize + report** so the honest,
    sealed-test number is recorded. An optimization run with no finalize has no result.
    
    ## What good vs bad looks like
    - **Good:** the plan inspected before `--execute`; every guardrail enforced
      automatically; the run ends with a sealed-test number and a named winner, even
      when the answer is "no significant gain".
    - **Bad:** advancing past a red `cap-evolve check`; gating on train; finalizing more
      than one candidate; declaring success on val without ever scoring test.
    
    ## References
    - `references/concepts.md` — the phase sequence as a needs/provides DAG, where
      each honesty guardrail lives, and the stop rules, with sources.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related