Skip to main content

Setting the target score

The score threshold tells reflex when to stop — “make my model at least this good.” Rather than picking an arbitrary number, you should set the target from a real benchmark. There are three ways to set the target, in priority order:

OptimizerConfig

All configuration goes through OptimizerConfig:

Train/test split

By default, reflex splits your dataset 80/20 before running. The 80% train portion is used during the optimization loop — failing samples are drawn from here and all iteration scores are computed on these examples. The 20% test set is held out completely, and the baseline and final scores you see in the results summary are computed on that held-out set only. This matters because without a split, the same examples that drive rewrites also determine whether the prompt “improved.” That inflates the reported improvement, especially on small datasets where a handful of examples have an outsized effect on the mean score.
The split is deterministic (seeded at 42) so the same dataset always produces the same partition.
With small datasets (fewer than ~20 examples), the test set may be too small for stable scores. In that case, use --train-split 1.0 to disable splitting and rely on the score trajectory to judge improvement instead.

Validation split and early stopping

Reflex supports a 3-way train / val / test split to detect overfitting mid-run. Without a validation set, a prompt can score well on training examples simply by overfitting to their specific patterns — the optimization loop has no way to notice. With a validation set, reflex evaluates each candidate prompt on the val examples after every iteration. If the train score climbs but val plateaus or declines, that’s a sign the prompt is fitting the training examples specifically rather than generalizing. Early stopping on val plateau saves you compute and returns the least-overfit prompt.
Python API:
The validation split is deterministic (same seed as train/test). The summary shows all three split sizes, a per-iteration val trajectory, and — if early stopping triggered — marks which iteration was actually the best:
Use --early-stopping-patience 2 for fast iteration or 3–4 when your dataset is small and single-iteration val scores are noisy. Without --early-stopping-patience, val scores are still tracked and reported but optimization runs to completion.
The validation set adds one extra eval call per optimization iteration. With val_ratio=0.1 on 100 examples you’re evaluating 10 extra samples per iteration — usually inexpensive compared to the reasoning step.

Mini-batch mode

By default every optimization iteration evaluates the full training set. For large datasets this can be slow — each iteration costs n_train LLM calls. Mini-batch mode samples a random subset each round:
Each iteration draws a fresh sample seeded by batch_seed + i, so the optimizer sees different examples each round. The stochasticity can also help escape local optima by smoothing out noise from individual examples. Baseline and final verification evals always use the full test setbatch_size only affects the per-iteration training evals used by the optimization strategy.

Periodic full-eval checkpoints

Mini-batch scores are noisy — a single good or bad batch can skew the trajectory. Use --full-eval-steps to periodically score the full training set for an accurate checkpoint:
Iterations 5, 10, 15, … score the full training set; all others score the mini-batch. Full-eval iterations are marked with a ◈ full eval badge in the dashboard flow graph.
A good starting point is 20–50% of your training set size. Too small a batch makes the per-iteration score noisy; too large gives diminishing returns over the full set.

Statistical significance

Reflex reports whether the improvement from baseline to final is statistically significant using a paired test on per-sample scores. This compares how each individual sample scored under the original prompt vs the optimized one. The result appears in result.summary():
The test uses the Wilcoxon signed-rank test (non-parametric, no normality assumption) if scipy is installed, and falls back to a paired t-test otherwise. Install scipy for best results:
For noisy tasks where LLM responses vary run-to-run, use --eval-runs to average multiple passes before computing the test:
This reports mean ± std in the results summary:
--eval-runs only affects the baseline and final verification evals — not the optimization iterations, which always use a single pass for speed. --eval-runs 3 triples the cost of those two checkpoints.

Strategy-specific parameters

Pass strategy-specific parameters via extra_kwargs:

Parallel execution

Strategies like structural and PDO evaluate multiple prompt variants per iteration. These variants are evaluated in parallel using threads. For cloud APIs (OpenAI, OpenRouter, Together), parallelism works out of the box:
For Ollama, you need to explicitly enable parallel inference — by default Ollama processes one request at a time:
If reflex detects Ollama without OLLAMA_NUM_PARALLEL set, it automatically falls back to 1 worker and logs a warning with setup instructions.
Higher OLLAMA_NUM_PARALLEL uses more VRAM. Guidelines:

Choosing a reasoning model

Reflex is an agent — it observes eval results, diagnoses failures, and iteratively rewrites your prompt. To do this reasoning, it calls an LLM. By default that’s Claude Sonnet, but you can swap in any model: a local Ollama model, an OpenAI-compatible endpoint, or another cloud provider. The reasoning model is separate from the model being optimized (-m). A more capable reasoning model produces better prompt rewrites.
Python API:
The reasoning model needs strong analytical abilities. Local models work well for simpler strategies (iterative), but the auto strategy benefits from a more capable model since it makes multi-step decisions about which optimization axes to apply.
Automatic language detection. Reflex samples up to 20 user messages from your training set and detects the dominant script family using Unicode heuristics (no external dependencies). The reasoning model is then instructed to write all revised prompts and diagnostic explanations in that language. This prevents multilingual models such as Qwen3 from silently switching languages mid-run when optimizing non-English datasets. No configuration is required — the detection is automatic.

Run persistence and checkpointing

Reflex automatically saves every run to a .reflex/ directory. Each iteration is checkpointed so that if a run crashes or is interrupted, you can resume exactly where it left off.

Directory structure

Each run gets a sequential ID (001, 002, …) and a timestamped directory. The config, dataset path, and initial prompt are captured at the start so every run is fully reproducible.

Resuming interrupted runs

If a run is interrupted (Ctrl-C, crash, network timeout), use --resume to pick up where it left off:
The checkpoint contains the full state: current best prompt, score trajectory, completed iterations, baseline scores, and strategy-specific state. On resume, reflex skips the baseline eval and jumps straight to the next iteration.

Listing runs

Shows all runs with their status, strategy, scores, and iteration count. Add -v for config details (reasoning model, target models).

Custom run directory

By default, runs are stored in .reflex/ in the current working directory. Use --run-dir to change this:

Python API

Metrics

By default, reflex uses ROUGE. You can switch to a different metric or use an LLM judge:
--metric and --judge are mutually exclusive. If neither is specified, ROUGE is used as the default. --judge-criteria only applies when --judge is set. It accepts a path to a plain text file describing your scoring rubric (1–5 scale). Use this when the default accuracy/helpfulness/clarity/completeness criteria don’t match your task — for example, when you need to enforce a strict output format, check domain-specific correctness, or evaluate against a proprietary style guide.
rubric.md (example)