Skip to main content
The optimizer engine is the shared evaluation core under every optimizer. Strategies register candidates and ask the engine to evaluate them against an OptimizeTarget; the engine handles candidate binding, rollout fan-out, caching, budget accounting, and score bookkeeping. Every optimizer in DSRs (COPRO, GEPA, MIPROv2, SIMBA, bootstrap, Structural) is a thin strategy over this one core. There is one Engine. What varies is the target — the lane-erased pair of (thing under optimization, evaluation harness): Because candidate injection is ambient in both lanes — nothing is ever applied to shared state during evaluation — rollouts for different candidates share one bounded-concurrency fan-out. All items on this page are exported from the crate root except where a feature gate is noted.

OptimizeTarget<'a>

The thing an optimizer optimizes: a module or a program, packaged with its example set (by reference) and metric. ProgramMetric is the JSON-native sibling of TypedMetric: loaded programs have no static output type, so the metric scores the interpreter’s output JsonMap against a labeled DemoRow.

Engine

Owns the candidate registry, the score matrix, the rollout cache, and the budget meter. Strategies register candidates and call the evaluate methods against a target; the engine handles binding, fan-out, caching, accounting, and bookkeeping. The metric runs outside the trace capture scope, so LM-as-judge metrics do not pollute the execution trace. If the uncached portion of a batch does not fit the remaining budget, the engine runs nothing, leaves spend unchanged, and returns BudgetExhausted.

EngineConfig

Candidates

A Candidate is data: slots: BTreeMap<String, CandidateSlot> mapping leaf name (the Predictors contract name) to a partial per-leaf configuration, plus a stable content hash. It is cheap to clone, serializable, and never applied by mutation: the engine scopes it ambiently around each rollout (fx::with_params); the single mutating step is the caller-driven final OptimizeTarget::install. The empty candidate (Candidate::default()) is the baseline, the module exactly as it is.
CandidateSlot (instruction plus demos per leaf name) is a different type from the IR ir::Overlay, which maps ParamId to ParamValue over a compiled Program. The program lane consumes the IR type; Candidate::to_params() + Params::bind is the bridge between them.

Budget and Spend

Budget sets hard caps on evaluation spend; None means unlimited. Budget::allows(&spend, upcoming_rollouts) reports whether the batch fits. Zero upcoming rollouts always fit, so cache-only batches never stall. Call and metric caps are enforced prospectively (the batch must fit under the cap); the token cap is retrospective (a batch may overshoot, and the following batch is refused). Spend is what the engine has consumed so far:
This Budget is not the IR runtime Budget documented on Runtime. The runtime type caps one program execution: max_lm_calls: Option<u32>, max_tokens, and a deadline: Option<Instant>, enforced call by call inside the run by a BudgetMeter with parent chaining for nested agent loops. The optimizer type caps an entire optimization search across all rollouts: it adds max_metric_calls, has no deadline, and is checked per batch before anything runs, with consumption tracked in Spend. The program lane runs each rollout under the runtime Budget::unlimited() while the optimizer Budget governs the batch.

RolloutCache

In-memory map from a rollout key to its Eval (score plus optional feedback). A candidate re-evaluated on a seen example returns the cached Eval with no LM call and no metric call. The key recipe is (baseline, candidate, example, salt), formatted as four 16-digit hex hashes joined by colons: Public surface: get, insert, len, is_empty.

Score bookkeeping

ScoreMatrix is a per-instance matrix of candidates (rows, registration order) by examples (columns). Cells are None until scored. Methods: new(columns), candidates(), examples(), ensure_rows(n), record(candidate, example, score), score(candidate, example), row(candidate), mean(candidate), best_by_mean(), pareto(), pareto_over(columns). The column-restricted view supports GEPA-style setups where train and validation examples share one matrix. ParetoView is a dominance snapshot computed from the matrix. best_scores() gives the best score per viewed column; wins(candidate) counts columns the candidate wins or ties on (tolerance 1e-6); frontier() lists candidates winning on at least one column; statistics() summarizes. A candidate with zero wins is dominated. GEPA samples parents proportional to their Pareto coverage directly from this view. ParetoStatistics fields: num_candidates, num_examples_covered, avg_coverage: f32, max_coverage, min_coverage. A healthy search grows num_candidates slowly while avg_coverage rises; num_candidates == 1 means the search has collapsed.

Outcome types

Rollout mechanics

Each program-lane rollout runs interp.run(input, Some(overlay), Budget::unlimited()) under its own capture scope with TraceMeta.candidate_hash set to the candidate’s hash and a program tag carrying the program hash. Each module-lane rollout scopes the candidate’s fx::Params ambiently around the whole traced rollout (rollout_traced), so every Predict leaf binds its own entry at call time. After the metric scores a module-lane rollout, any span-level evals it returns from TypedMetric::evaluate_spans are stamped onto the trace’s spans; demo-harvesting optimizers prefer these over the rollout score (see Evaluation). Every pending (candidate, example) pair — across all candidates, in both lanes — joins one buffer_unordered stream bounded by EngineConfig::concurrency.

See also