> ## Documentation Index
> Fetch the complete documentation index at: https://dsrs.herumbshandilya.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Optimizers

> The Optimizer trait, every optimizer configuration, and every report type: COPRO, MIPROv2, GEPA, SIMBA, and BootstrapFewShot

An optimizer proposes candidates (instruction or demo overlays), evaluates them with your metric on your trainset, and keeps the best. The convenience entry point is each optimizer's `compile_module` method: it takes a module, a training set, and a metric, then searches for better instructions, and in some cases demos, for each `Predict` leaf. After it returns, the winner is installed and calling the module produces better results with no code changes.

```rust theme={null}
use dspy_rs::COPRO;

let optimizer = COPRO::builder()
    .breadth(10)
    .depth(1)
    .eval_concurrency(16)
    .build();
optimizer
    .compile_module(&mut module, &trainset, &metric)
    .await?;
```

The module must declare its optimizable leaves via [`Predictors`](/docs/components/modules#predictor-discovery-predictors) (one `predictors!` line). All six optimizers are thin strategies over the shared evaluation engine. Candidates are **data, never mutation**: each candidate is a name-keyed `Candidate` injected *ambiently* per rollout (`fx::with_params`) — nothing touches the module during evaluation, so different candidates evaluate concurrently — and the winner is installed exactly once at the end (`OptimizeTarget::install`). The engine types (`Engine`, `Candidate`, `Budget`, `Spend`, `ParetoView`) are documented in [Optimizer engine](/docs/components/optimizer-engine).

Five strategies tune parameter values through overlays; the sixth, [`Structural`](#structural), proposes graph edits over the [edit calculus](/docs/components/edit-calculus) and runs on the program lane only.

<svg viewBox="0 0 760 420" role="img" aria-label="Three translucent tracing-paper overlays fanned above the base map, each changing one grid-referenced dial; the base program is never marked" style={{width: '100%', maxWidth: '700px', display: 'block', margin: '2rem auto'}}>
  <rect x="90" y="210" width="580" height="180" rx="8" fill="currentColor" fillOpacity="0.05" stroke="currentColor" strokeOpacity="0.55" strokeWidth="2" />

  <g stroke="currentColor" strokeOpacity="0.12" strokeWidth="1">
    <line x1="90" y1="255" x2="670" y2="255" />

    <line x1="90" y1="300" x2="670" y2="300" />

    <line x1="90" y1="345" x2="670" y2="345" />

    <line x1="235" y1="210" x2="235" y2="390" />

    <line x1="380" y1="210" x2="380" y2="390" />

    <line x1="525" y1="210" x2="525" y2="390" />
  </g>

  <text x="112" y="238" fontFamily="ui-monospace, monospace" fontSize="11" fill="currentColor" fillOpacity="0.8">program frontdesk  (base, hash 9f3a...)</text>

  <circle cx="180" cy="322" r="13" fill="#ed6c13" fillOpacity="0.15" stroke="#ed6c13" strokeWidth="1.5" />

  <text x="180" y="352" fontFamily="ui-monospace, monospace" fontSize="9.5" fill="currentColor" fillOpacity="0.7" textAnchor="middle">sum</text>

  <circle cx="330" cy="322" r="13" fill="#ed6c13" fillOpacity="0.15" stroke="#ed6c13" strokeWidth="1.5" />

  <text x="330" y="352" fontFamily="ui-monospace, monospace" fontSize="9.5" fill="currentColor" fillOpacity="0.7" textAnchor="middle">drafter</text>

  <line x1="193" y1="322" x2="317" y2="322" stroke="#ed6c13" strokeWidth="1.5" />

  <rect x="422" y="310" width="188" height="24" rx="5" fill="none" stroke="currentColor" strokeOpacity="0.5" strokeWidth="1.5" strokeDasharray="4 3" />

  <text x="516" y="326" fontFamily="ui-monospace, monospace" fontSize="10.5" fill="currentColor" fillOpacity="0.85" textAnchor="middle">drafter.instruction</text>

  <line x1="343" y1="318" x2="420" y2="320" stroke="currentColor" strokeOpacity="0.4" strokeWidth="1.5" />

  <g transform="rotate(-6 260 110)">
    <rect x="120" y="40" width="280" height="120" rx="6" fill="currentColor" fillOpacity="0.03" stroke="currentColor" strokeOpacity="0.35" strokeWidth="1.5" strokeDasharray="6 4" />

    <text x="140" y="66" fontFamily="ui-monospace, monospace" fontSize="10.5" fill="currentColor" fillOpacity="0.7">overlay c41</text>

    <path d="M 150 96 C 190 86, 240 104, 300 92" fill="none" stroke="#ed6c13" strokeOpacity="0.6" strokeWidth="2" strokeLinecap="round" />

    <text x="150" y="130" fontFamily="ui-sans-serif, system-ui, sans-serif" fontSize="10" fill="currentColor" fillOpacity="0.55">warmth 0.91</text>
  </g>

  <g transform="rotate(1 400 100)">
    <rect x="240" y="26" width="280" height="120" rx="6" fill="currentColor" fillOpacity="0.03" stroke="currentColor" strokeOpacity="0.4" strokeWidth="1.5" strokeDasharray="6 4" />

    <text x="260" y="52" fontFamily="ui-monospace, monospace" fontSize="10.5" fill="currentColor" fillOpacity="0.75">overlay c58</text>

    <path d="M 270 84 C 320 72, 380 94, 440 80" fill="none" stroke="#ed6c13" strokeOpacity="0.7" strokeWidth="2" strokeLinecap="round" />

    <text x="270" y="118" fontFamily="ui-sans-serif, system-ui, sans-serif" fontSize="10" fill="currentColor" fillOpacity="0.55">warmth 0.82, brevity 0.78</text>
  </g>

  <g transform="rotate(7 540 120)">
    <rect x="370" y="48" width="280" height="120" rx="6" fill="currentColor" fillOpacity="0.03" stroke="currentColor" strokeOpacity="0.35" strokeWidth="1.5" strokeDasharray="6 4" />

    <text x="390" y="74" fontFamily="ui-monospace, monospace" fontSize="10.5" fill="currentColor" fillOpacity="0.7">overlay c62</text>

    <path d="M 400 106 C 450 96, 500 114, 560 100" fill="none" stroke="#ed6c13" strokeOpacity="0.6" strokeWidth="2" strokeLinecap="round" />

    <text x="400" y="140" fontFamily="ui-sans-serif, system-ui, sans-serif" fontSize="10" fill="currentColor" fillOpacity="0.55">brevity 0.93</text>
  </g>

  <line x1="500" y1="172" x2="512" y2="306" stroke="#ed6c13" strokeOpacity="0.5" strokeWidth="1.5" strokeDasharray="3 4" />

  <text x="560" y="196" fontFamily="ui-sans-serif, system-ui, sans-serif" fontSize="10.5" fill="currentColor" fillOpacity="0.65">each sheet rewrites</text>
  <text x="560" y="211" fontFamily="ui-sans-serif, system-ui, sans-serif" fontSize="10.5" fill="currentColor" fillOpacity="0.65">the same named dial</text>
  <text x="380" y="412" fontFamily="ui-sans-serif, system-ui, sans-serif" fontSize="11" fill="currentColor" fillOpacity="0.7" textAnchor="middle">candidates are overlays; the base program is never mutated</text>
</svg>

Step-by-step how-to pages: [COPRO](/docs/optimizers/copro), [MIPROv2](/docs/optimizers/miprov2), [GEPA](/docs/optimizers/gepa), and [Structural](/docs/optimizers/structural).

## The Optimizer trait

```rust theme={null}
#[async_trait::async_trait(?Send)]
pub trait Optimizer: Send + Sync {
    fn engine_config(&self) -> EngineConfig { EngineConfig::default() }

    async fn compile(
        &self,
        target: &mut OptimizeTarget<'_>,
        engine: &mut Engine,
    ) -> Result<Report>;
}
```

The trait is **object-safe** by design: optimizers compose (`Box<dyn Optimizer>` pipelines can share one `Engine` — one budget, one rollout cache, one score matrix — across stages). The target carries the thing under optimization and its example set *by reference*; the engine carries the spend.

`OptimizeTarget` is the lane-erased pair of (thing under optimization, evaluation harness), one of two lanes:

* **`OptimizeTarget::module(&mut module, &trainset, &metric)`** — a typed `Module` (+ `Predictors` discovery), a trainset slice, and a `TypedMetric`. The trainset is `&[E]` for any [row type](/docs/components/data) that projects into the module's input via `ToInput`; the `Serialize` bound feeds rollout-cache uids, which content-hash the whole row. `OptimizeTarget::module_with_valset(...)` adds an optional validation set (the layout GEPA's Pareto bookkeeping uses). Construction runs the **naming pass**: every declared leaf is stamped with its declared name, so trace spans, candidate entries, and persistence all address the same names.
* **`OptimizeTarget::program(&interp, &examples, &metric)`** — an interpreter-loaded IR [`Program`](/docs/components/program-and-nodes), labeled `DemoRow` examples, and a JSON-native `ProgramMetric`. The winner is retrievable as an `ir::Overlay` (`OptimizeTarget::winner_overlay`) for `Program::bake`.

For the common case you never build these by hand — each optimizer's `compile_module(&mut module, &trainset, &metric)` inherent method constructs a module target and a default engine, runs `compile`, and installs the winner. `compile` returns an error when the target has no optimizable leaves, when a metric evaluation fails, or when an LM call fails during candidate evaluation.

`compile` returns the `Report` enum (`Report::None`, `Report::Gepa(GEPAResult)`, `Report::Simba(SimbaReport)`, `Report::Bootstrap(BootstrapReport)`, and `Report::Custom(serde_json::Value)` as the third-party extension point), with `into_gepa()`/`into_simba()`/`into_bootstrap()` accessors. The typed `compile_module` sugar unwraps it:

| Optimizer          | `compile_module` returns |
| ------------------ | ------------------------ |
| `COPRO`            | `()`                     |
| `MIPROv2`          | `()`                     |
| `GEPA`             | `GEPAResult`             |
| `SIMBA`            | `SimbaReport`            |
| `BootstrapFewShot` | `BootstrapReport`        |

`Structural` is the exception to this table and to the trait: it edits program structure, so its input is an interpreter-loaded program rather than a lane-erased target, and its entry point is [`compile_program`](#structural) instead of `compile_module`/`compile`.

## Choosing an optimizer

| Optimizer          | Strategy                                                  | Needs feedback? | Cost                                       |
| ------------------ | --------------------------------------------------------- | --------------- | ------------------------------------------ |
| `BootstrapFewShot` | One-shot demo harvesting from a teacher pass              | No              | Low (2 × trainset)                         |
| `COPRO`            | Breadth-first instruction search                          | No              | Low (breadth × depth × trainset)           |
| `SIMBA`            | Minibatch introspective ascent (demos + rules)            | No              | Low (steps × minibatch)                    |
| `GEPA`             | Genetic-Pareto evolution with feedback                    | **Yes**         | Medium-high (iterations × eval)            |
| `MIPROv2`          | Trace-guided candidate generation                         | No              | Medium (candidates × trials × trainset)    |
| `Structural`       | LM-guided graph edits over `ir::Edit` (program lane only) | No              | Medium (examples + iterations × minibatch) |

GEPA is the only optimizer that requires textual feedback from the metric. The others use numerical scores alone.

## COPRO

Breadth-first instruction search: generates `breadth` candidate instructions per predictor, evaluates each on the trainset, installs the best, and repeats for `depth` rounds. The base instruction always competes in every round. How-to: [COPRO](/docs/optimizers/copro).

| Field              | Type         | Default | Description                                                                             |
| ------------------ | ------------ | ------- | --------------------------------------------------------------------------------------- |
| `breadth`          | `usize`      | `10`    | Candidates per round per predictor. Must be greater than 1; `compile` errors otherwise. |
| `depth`            | `usize`      | `3`     | Optimization rounds; each refines the previous best.                                    |
| `init_temperature` | `f32`        | `1.4`   | Currently unused. Reserved for LM-generated candidate diversity.                        |
| `track_stats`      | `bool`       | `false` | Per-round statistics flag. The report type is `()`, so nothing is returned either way.  |
| `prompt_model`     | `Option<LM>` | `None`  | Separate LM for generating candidate instructions.                                      |
| `eval_concurrency` | `usize`      | `16`    | Concurrent LM calls during candidate evaluation.                                        |

Cost: approximately `breadth × depth × num_predictors × trainset_size` LM calls, minus rollout-cache hits. Runs with an unlimited engine budget.

## MIPROv2

Trace-guided instruction and demo optimizer. Four phases: one traced teacher pass over the trainset; demo bootstrapping from successful spans via the trace name-join; generation of `num_candidates` instruction variants per predictor seeded by prompting tips; evaluation of up to `num_trials` candidates per predictor on one sampled minibatch, keeping the best. Demos are installed before instruction search so candidates are scored against the module as it will actually run. How-to: [MIPROv2](/docs/optimizers/miprov2).

| Field                    | Type          | Default | Description                                                                                                              |
| ------------------------ | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| `num_candidates`         | `usize`       | `10`    | Instruction variants generated per predictor.                                                                            |
| `num_trials`             | `usize`       | `20`    | Maximum candidates evaluated per predictor. When lower than `num_candidates`, only the first `num_trials` are evaluated. |
| `minibatch_size`         | `usize`       | `25`    | Examples per candidate evaluation.                                                                                       |
| `max_bootstrapped_demos` | `usize`       | `4`     | Demos installed per predictor from successful traces.                                                                    |
| `min_demo_score`         | `f64`         | `0.0`   | Minimum score for a span to qualify as a demo source: its own span eval when present, the whole-program score otherwise. |
| `eval_concurrency`       | `usize`       | `16`    | Concurrent LM calls during candidate evaluation.                                                                         |
| `seed`                   | `Option<u64>` | `None`  | Fixes minibatch sampling. `None` is nondeterministic.                                                                    |

Public helper type: `PromptingTips` (the rotation of prompting best practices appended to candidates, `default_tips()` and `format_for_prompt()`). `compile_module` returns `()`; through the trait, the report is `Report::None`.

## GEPA

Genetic-Pareto instruction evolution driven by per-example feedback. Each generation samples a parent proportional to Pareto coverage, re-evaluates it on a trainset minibatch, has a reflection LM (`prompt_model`) rewrite the instruction from the feedback and the mutated component's execution trace, then scores the child on the validation columns. Without a `prompt_model`, mutation degrades to deterministic feedback concatenation. Only instruction space is searched: no demo mutation, no crossover. How-to: [GEPA](/docs/optimizers/gepa).

<Warning>
  GEPA errors if any `Eval` from the metric has `feedback: None`. Build metrics with `Eval::with_feedback`.
</Warning>

| Field                | Type            | Default | Description                                                                   |
| -------------------- | --------------- | ------- | ----------------------------------------------------------------------------- |
| `num_iterations`     | `usize`         | `20`    | Evolutionary generations.                                                     |
| `minibatch_size`     | `usize`         | `25`    | Examples per parent re-evaluation within each generation.                     |
| `num_trials`         | `usize`         | `10`    | Currently unused. Reserved for multi-child evolution.                         |
| `temperature`        | `f32`           | `1.0`   | Currently unused. Reserved for mutation diversity control.                    |
| `track_stats`        | `bool`          | `true`  | Record all candidates, evolution history, and frontier history in the result. |
| `track_best_outputs` | `bool`          | `false` | Re-run the best instruction on the eval set and record outputs.               |
| `max_rollouts`       | `Option<usize>` | `None`  | Hard cap on evaluation rollouts.                                              |
| `max_lm_calls`       | `Option<usize>` | `None`  | Hard cap on LM calls (rollouts plus generation).                              |
| `prompt_model`       | `Option<LM>`    | `None`  | Reflection LM that rewrites instructions from feedback. Strongly recommended. |
| `eval_concurrency`   | `usize`         | `16`    | Concurrent LM calls during candidate evaluation.                              |
| `seed`               | `Option<u64>`   | `None`  | Fixes minibatch sampling and parent selection.                                |

GEPA additionally exposes `compile_module_with_valset(module, trainset, valset, metric)` — sugar over `OptimizeTarget::module_with_valset` plus the `Optimizer` trait. With `Some(valset)`, initial evaluation and child scoring use the validation set while parent re-evaluation uses trainset minibatches; with `None`, the trainset serves both roles (this is what `compile_module` does).

### GEPAResult

| Field                                 | Type                             | Description                                                                                   |
| ------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------- |
| `best_candidate`                      | `GEPACandidate`                  | Best average score on the Pareto frontier; installed on the module.                           |
| `all_candidates`                      | `Vec<GEPACandidate>`             | Every evaluated candidate. Empty unless `track_stats`.                                        |
| `total_rollouts`                      | `usize`                          | Evaluation rollouts consumed.                                                                 |
| `total_lm_calls`                      | `usize`                          | LM calls consumed (rollouts plus candidate generation).                                       |
| `evolution_history`                   | `Vec<(usize, f32)>`              | `(generation, best_average_score)` pairs for plotting convergence.                            |
| `highest_score_achieved_per_val_task` | `Vec<f32>`                       | Best score per validation example across all candidates.                                      |
| `best_outputs_valset`                 | `Option<Vec<serde_json::Value>>` | Best outputs on the eval set. `Some` only with `track_best_outputs`.                          |
| `frontier_history`                    | `Vec<ParetoStatistics>`          | Frontier snapshot per generation (see [Optimizer engine](/docs/components/optimizer-engine)). |

`GEPACandidate` carries `id`, `instruction`, `module_name`, `example_scores: Vec<f32>`, `parent_id: Option<usize>`, and `generation`, plus `average_score()` and `mutate(new_instruction, generation)`.

## SIMBA

Stochastic Introspective Mini-Batch Ascent, the cheap agentic default. SIMBA keeps one current program as a candidate overlay and hill-climbs. Each step: sample a seeded trainset minibatch; pick the best and worst rollout of the current program on it (served from engine bookkeeping, no extra rollouts); propose exactly one move; accept it through the engine's minibatch gate. A child is promoted to a full-trainset evaluation only when its minibatch mean strictly beats the current program's, so rejected moves never pay for a full pass. The winner is installed when `compile` returns.

The two moves, in order of preference:

| Move                    | Trigger                                                            | Effect                                                                                                                                                                                                                                       |
| ----------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SimbaMove::AppendDemo` | Best rollout scored at least `min_demo_score` and yields new demos | Appends one harvested demo per predictor, capped at `max_demos`, oldest dropped, duplicates skipped.                                                                                                                                         |
| `SimbaMove::AppendRule` | Otherwise                                                          | A reflection LM distills one rule from the best and worst rollouts, appended to the instruction of the predictor with the most spans in the worst rollout. Without `prompt_model`, the worst rollout's metric feedback is appended verbatim. |

| Field              | Type            | Default | Description                                                                                    |
| ------------------ | --------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `max_steps`        | `usize`         | `8`     | Ascent steps to attempt.                                                                       |
| `minibatch_size`   | `usize`         | `8`     | Examples sampled per step.                                                                     |
| `max_demos`        | `usize`         | `4`     | Demo-set cap per predictor.                                                                    |
| `min_demo_score`   | `f64`           | `1.0`   | Rollout score needed to qualify as a demo source; below it the step falls back to append-rule. |
| `prompt_model`     | `Option<LM>`    | `None`  | Reflection LM for append-rule moves. Strongly recommended.                                     |
| `max_metric_calls` | `Option<usize>` | `None`  | Hard cap on metric calls (rollouts).                                                           |
| `max_lm_calls`     | `Option<usize>` | `None`  | Hard cap on LM call units (rollouts plus reflection).                                          |
| `eval_concurrency` | `usize`         | `16`    | Rollouts in flight during evaluation.                                                          |
| `seed`             | `Option<u64>`   | `None`  | Fixes minibatch sampling.                                                                      |

Cost: `trainset_size` for the baseline pass, then `minibatch_size` rollouts per step, plus the remaining `trainset_size - minibatch_size` only on promotion and one reflection call per rule move. The run stops cleanly when the budget no longer fits a step.

### SimbaStep and SimbaReport

Each `SimbaStep` records one step:

| Field                    | Type          | Description                                                 |
| ------------------------ | ------------- | ----------------------------------------------------------- |
| `step`                   | `usize`       | Step index, 0-based.                                        |
| `move_kind`              | `SimbaMove`   | `AppendDemo` or `AppendRule`.                               |
| `parent_minibatch_score` | `f64`         | Current program's minibatch mean, the gate threshold.       |
| `child_minibatch_score`  | `f64`         | Proposed child's mean on the same minibatch.                |
| `accepted`               | `bool`        | Whether the gate promoted the child.                        |
| `full_score`             | `Option<f64>` | Full-trainset mean of the child; `Some` only when promoted. |

The `SimbaReport` summarizes the run:

| Field            | Type             | Description                                                                             |
| ---------------- | ---------------- | --------------------------------------------------------------------------------------- |
| `baseline_score` | `f64`            | Mean metric score of the unmodified module over the trainset.                           |
| `final_score`    | `f64`            | Full-trainset mean of the final program; equals the baseline when nothing was accepted. |
| `steps`          | `Vec<SimbaStep>` | Per-step outcomes, in order.                                                            |
| `accepted`       | `usize`          | Steps promoted by the gate.                                                             |
| `rejected`       | `usize`          | Steps rejected by the gate.                                                             |
| `spend`          | `Spend`          | Engine spend for the whole run, reflection calls included.                              |

## BootstrapFewShot

The simplest complete optimizer: one teacher pass, one candidate, one comparison. It runs the module over the trainset under trace capture, harvests few-shot demos from successful spans of rollouts scoring at least `min_demo_score`, evaluates the demo candidate on the same engine (teacher rollouts already sit in the rollout cache, so the baseline never re-runs), and installs the demos only when the candidate's mean beats the baseline.

| Field              | Type            | Default | Description                                                                                                                                                                                         |
| ------------------ | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `max_demos`        | `usize`         | `4`     | Maximum demos installed per predictor.                                                                                                                                                              |
| `min_demo_score`   | `f64`           | `1.0`   | Minimum score for a span to qualify as a demo: its own span eval when the metric attached one, the whole-rollout score otherwise. The default assumes a 0 to 1 metric; lower it for graded metrics. |
| `eval_concurrency` | `usize`         | `16`    | Concurrent rollouts during evaluation.                                                                                                                                                              |
| `max_metric_calls` | `Option<usize>` | `None`  | Hard cap on metric calls (rollouts).                                                                                                                                                                |
| `max_lm_calls`     | `Option<usize>` | `None`  | Hard cap on LM call units.                                                                                                                                                                          |

### BootstrapReport

| Field                 | Type                      | Description                                                                                                                |
| --------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `baseline_score`      | `f64`                     | Mean metric score of the unmodified module over the trainset.                                                              |
| `candidate_score`     | `Option<f64>`             | Mean score with demos attached. `None` when no demos were harvested or the budget stopped before the candidate evaluation. |
| `adopted`             | `bool`                    | Whether the demo candidate beat the baseline and was installed.                                                            |
| `demos_per_predictor` | `BTreeMap<String, usize>` | Demos harvested per predictor, keyed by leaf name.                                                                         |
| `spend`               | `Spend`                   | Engine spend for the whole run.                                                                                            |

<Note>
  `adopted: false` with a populated `demos_per_predictor` means demos were harvested but did not beat the baseline; the module is left unchanged.
</Note>

## Structural

LM-guided hill-climbing over the [edit calculus](/docs/components/edit-calculus), program lane only. Each generation gathers the `legal_edits` menu for every leaf, has a reflection LM (`prompt_model`) choose one edit from the serialized menu plus the incumbent's evaluation feedback, applies it with `Program::edited`, carries the incumbent overlay across the change with `migrate_overlay`, loads the child through a caller-supplied `RuntimeEnv` factory, and accepts it through the engine's minibatch gate: only a strict win on the shared minibatch promotes the child to a full-set evaluation and makes it the new incumbent. Edits that fail to apply, children that fail to load, and reflection replies that do not parse are recorded and skipped. How-to: [Structural](/docs/optimizers/structural).

Entry points: `compile_program(&interp, &examples, &metric, env)` and `compile_program_with_overlay(&interp, Some(overlay), &examples, &metric, env)`, where `env: Fn() -> RuntimeEnv` supplies fresh bindings for each child load. The winner is returned in the report (program plus migrated overlay), never installed; bake it with `Program::bake`.

| Field              | Type            | Default | Description                                                                                       |
| ------------------ | --------------- | ------- | ------------------------------------------------------------------------------------------------- |
| `num_iterations`   | `usize`         | `8`     | Generations to attempt; each proposes exactly one edit.                                           |
| `minibatch_size`   | `usize`         | `8`     | Examples in the shared minibatch parent and child are compared on.                                |
| `prompt_model`     | `Option<LM>`    | `None`  | Reflection LM that chooses an edit from the menu. Without it the choice is a seeded-uniform pick. |
| `max_rollouts`     | `Option<usize>` | `None`  | Hard cap on evaluation rollouts. Every child is a fresh program that re-scores from scratch.      |
| `max_lm_calls`     | `Option<usize>` | `None`  | Hard cap on LM call units (rollouts plus reflection).                                             |
| `eval_concurrency` | `usize`         | `16`    | Concurrent rollouts during evaluation.                                                            |
| `seed`             | `Option<u64>`   | `None`  | Fixes minibatch sampling and the fallback edit choice.                                            |

### StructuralStep and StructuralReport

Each `StructuralStep` records one generation:

| Field                    | Type             | Description                                                                |
| ------------------------ | ---------------- | -------------------------------------------------------------------------- |
| `generation`             | `usize`          | Generation index, 0-based.                                                 |
| `leaf`                   | `String`         | The leaf the chosen edit targets.                                          |
| `edit`                   | `Edit`           | The concrete proposed edit (serde data, replayable against `parent_hash`). |
| `parent_hash`            | `u64`            | `program_hash` of the parent the edit was applied to.                      |
| `parent_minibatch_score` | `f64`            | Parent's mean on the shared minibatch, the gate threshold.                 |
| `child_minibatch_score`  | `Option<f64>`    | Child's mean on the same minibatch; `None` when the child never scored.    |
| `accepted`               | `bool`           | Whether the gate promoted the child.                                       |
| `full_score`             | `Option<f64>`    | Full-set mean of the child; `Some` only when accepted.                     |
| `rejection`              | `Option<String>` | Why the child never scored (apply/load failure), when it didn't.           |

The `StructuralReport` summarizes the run:

| Field                   | Type                  | Description                                                                        |
| ----------------------- | --------------------- | ---------------------------------------------------------------------------------- |
| `program`               | `Arc<Program>`        | The winning program (the input program when nothing was accepted).                 |
| `overlay`               | `Overlay`             | The incumbent overlay re-minted against the winner at every accepted edit.         |
| `baseline_score`        | `f64`                 | Mean metric score of the input program (plus overlay) over the examples.           |
| `final_score`           | `f64`                 | Full-set mean of the final program; equals the baseline when nothing was accepted. |
| `edits`                 | `Vec<Edit>`           | The accepted edits, in order (a lineage, not one batch).                           |
| `steps`                 | `Vec<StructuralStep>` | Per-generation outcomes, in order.                                                 |
| `accepted` / `rejected` | `usize`               | Generations promoted / not promoted.                                               |
| `spend`                 | `Spend`               | Engine spend for the whole run, reflection calls included.                         |

## Demo harvesting

Demo harvesting is a pure name join over captured traces. A rollout trace records one span per `Predict` invocation under the leaf name the module declares via `Predictors` (stamped by the target's naming pass), so successful spans (parsed output present) scoring at least the optimizer's `min_demo_score` become flat demo rows for exactly the predictor that produced them: no pointer identity, identical behavior for fx and struct harnesses. Rows are gated and ranked by their effective score, deduplicated on input fields so repeated inputs do not crowd the demo set, and capped per predictor. `BootstrapFewShot`, `MIPROv2`, and `SIMBA` share this machinery; it is internal to the crate and not part of the public API.

A span's effective score is the whole-rollout metric score unless the metric attached a span-level eval through `TypedMetric::evaluate_spans` (see [Evaluation](/docs/components/evaluation)), which then takes precedence in both directions: a span scored down stays out of the demo pool even when its rollout won, and a span scored up qualifies even when its rollout lost. Without span evals the behavior is exactly the whole-rollout join described above.

## See also

* [Optimizer engine](/docs/components/optimizer-engine): `Engine`, `OptimizeTarget`, `Candidate`, `Budget`, `Spend`, and `ParetoView`
* [Evaluation](/docs/components/evaluation): `TypedMetric`, `Eval`, and `Eval::with_feedback`
* [Traces](/docs/components/traces): the span capture that feeds demo harvesting and reflection
* [The edit calculus](/docs/components/edit-calculus): the structural moves `Structural` proposes
* How-to pages: [COPRO](/docs/optimizers/copro), [MIPROv2](/docs/optimizers/miprov2), [GEPA](/docs/optimizers/gepa), [Structural](/docs/optimizers/structural)
* Runnable examples:
  * COPRO: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs) and [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs)
  * MIPROv2: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs)
  * GEPA: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs) and [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/10-gepa-llm-judge.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/10-gepa-llm-judge.rs)
  * Optimizer interface smoke test: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/94-smoke-slice5-optimizer-interface.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/94-smoke-slice5-optimizer-interface.rs)
