Skip to content

feat: mix full rewrites into diff-based evolution, plus trace fixes (v0.4.1) - #580

Merged
codelion merged 6 commits into
algorithmicsuperintelligence:mainfrom
SUPR1M3:feat/full-rewrite-probability
Nov 2, 2026
Merged

codelion merged 6 commits into
algorithmicsuperintelligence:mainfrom
SUPR1M3:feat/full-rewrite-probability

Conversation

@SUPR1M3

@SUPR1M3 SUPR1M3 commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #116

Summary

Adds full_rewrite_probability. When diff_based_evolution: true, each iteration asks for a full rewrite instead of SEARCH/REPLACE diffs with probability p. The default 0.0 keeps current behaviour.

diff_based_evolution: true
full_rewrite_probability: 0.2   # 20% of iterations ask for a full rewrite

evolution_trace:
  enabled: true
  include_failures: true        # optional: log failed iterations too

Changes

  • Mode selection: the worker picks each iteration's mode with a generator seeded from (random_seed, iteration). Runs stay reproducible whichever worker handles an iteration, and the ensemble's model sampling (Seed ensemble model sampling per iteration in worker processes #559) is untouched.
  • Mode drives prompt and parsing: the chosen mode decides both the prompt template and how the response is parsed.
  • Mode is recorded: it is stored on the iteration result (failures included), in program metadata (metadata["evolution_mode"]), in the evolution trace, and as the template key passed to log_prompt.
  • Validation: checked in Config.from_dict and in the controller, so configs built in code are covered too. The probability must be in [0, 1], it requires diff_based_evolution: true, and it can't be combined with prompt.programs_as_changes_description.
  • evolution_trace.include_failures (off by default): iterations that fail before producing a child are written to <trace>_failures.jsonl with their error and mode. With include_prompts, the prompt and LLM response are included too. Worker errors raised after the LLM call now carry the prompt, response, parent and island, so parse failures can be inspected.

Tests

  • tests/test_full_rewrite_probability.py: mode selection and reproducibility, prompt and parse routing, metadata and trace recording, config validation.
  • tests/test_controller_trace_failures.py: runs the controller loop and checks that failed iterations land in the failures file.
  • tests/test_evolution_trace.py: tracer support for include_failures.
  • Updated test_diff_format_validation_worker.py and test_worker_config_serialization.py for the new field.

python -m unittest discover tests: 1035 tests, OK.

Experiment results

The script and config are on exp/full-rewrite-116, kept out of this PR.

Setup: qwen3:8b (16k context, thinking off) on Ollama, Kaggle 2×T4 · circle_packing (phase 1 config) and function_minimization · p ∈ {0, 0.2, 0.5} · 3 seeds · 50 iterations · 18 runs. The trace was complete: no mode mismatches and no untraced iterations.

Final best score (mean ± SD over 3 seeds)

p circle_packing function_minimization
0 0.546 ± 0.014 1.4947 ± 0.0082
0.2 0.631 ± 0.004 1.4974 ± 0.0025
0.5 0.642 ± 0.033 1.4992 ± 0.0004

These final scores overstate the effect:

  • In 2 of the 3 p=0.2 circle_packing seeds, the jump to ~0.62 came from a lucky diff at iteration 1, not from a rewrite. Ollama's sampling isn't reproducible with concurrent requests on the GPU, even with byte-identical prompts, so 3 seeds are noisy.
  • function_minimization is at its ceiling (~1.5), so differences there are within noise. AUC does rise with p (1.467 / 1.481 / 1.491), and the spread across seeds shrinks.

Per-attempt comparison (circle_packing, first improvement attempts on the initial program)

Mode Median score Reached ≥ 0.57 Scored 0
Full rewrite 0.52 11/23 4/23
Diff 0.34 5/61 21/61

Within mixed runs, a full rewrite produced a new best 8% of the time vs 3% for a diff (95% Wilson intervals [4%, 14%] and [1%, 7%]). On function_minimization the two rates were equal (8% vs 7%).

Failure modes

  • Parsing: rewrites almost never fail to parse (1–4%). Diff parse failures run 3–15%, and most of them are one error, "Unmatched SEARCH/REPLACE delimiter outside a diff block" (48 occurrences).
  • Evaluation: failure rates are similar for the two modes (7–17%).
  • Open question: diffs in mixed runs failed evaluation more often than at p=0 (circle_packing: 24/177 vs 4/130). It isn't explained by whether the parent was a rewrite (diff→diff 13% vs rewrite→diff 11%), nor by parent score or prompt length.

Takeaway

Full rewrites help mostly by escaping a weak starting program. A nonzero p looks worthwhile and 0.2 is a reasonable value to suggest. This data can't tell 0.2 from 0.5. A follow-up on circle_packing with 5–6 seeds and p ∈ {0.1, 0.2, 0.5} would settle it. The default stays at 0.0 so existing behaviour doesn't change.

Related

Two bugs found while running the experiment are fixed in separate PRs: #582 (per-iteration LLM request seed, a follow-up to #554/#559) and #581 (children dropped from the trace when their parent left the archive). This PR doesn't depend on either one, and all three merge cleanly in any order.


Added for the v0.4.1 release

Main is merged in, and these are on top of the original commit.

Review fixes for include_failures

  • Timed-out and crashed iterations now reach the failures file.
  • Failed LLM calls now keep the prompt, parent and island, like later failures.
  • failures_path now matches the file written when compress is on.
  • Dotted trace names no longer collide: run.v2 and run.v3 both wrote to run_failures.jsonl.

Issues folded in

README: cut from 1,074 to about 200 lines, leading with research adoption and without emojis. Detailed material moved to docs/.

Version: bumped to 0.4.1.

Verification

  • Unit tests: 1064 pass, 5 skipped. The skipped ones are the HDF5 tests, because CI doesn't install h5py; they pass locally with h5py installed.
  • The new tests fail on the source without these fixes.
  • End-to-end with a stub LLM through the real process pool (p=0.5, 20 iterations): every iteration was accounted for once, all 12 children were full rewrites and all 8 failures were diffs.

Closes #116
Closes #381
Closes #574

🤖 Generated with Claude Code

…telligence#116)

Add full_rewrite_probability: with diff_based_evolution on, each iteration
asks for a full rewrite instead of SEARCH/REPLACE diffs with this
probability. The default 0.0 keeps today's behaviour.

- The worker picks the mode per iteration from a generator seeded with
  (random_seed, iteration), so runs stay reproducible whichever worker runs
  an iteration, and the ensemble's model picks (algorithmicsuperintelligence#559) are untouched.
- The chosen mode drives both prompt building and response parsing, and is
  recorded on the result (failures included), in program metadata
  ("evolution_mode"), in the evolution trace, and as the template key
  passed to log_prompt.
- Config validation (from_dict and the controller, for configs built in
  code): the probability must be in [0, 1], needs diff_based_evolution, and
  cannot be combined with prompt.programs_as_changes_description.

Also add evolution_trace.include_failures (off by default): iterations that
fail before producing a child are written to <trace>_failures.jsonl with
their error and mode, plus the prompt and LLM response when include_prompts
is on. Worker errors after the LLM call now carry the prompt, response,
parent and island, so parse failures can be inspected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exporter JSON-encodes non-scalar metadata but the loader only decoded
strings starting with "{", so lists came back as strings. Decoding every
"["-prefixed string instead would corrupt real strings that look like JSON.

The exporter now records which attributes it encoded in
__json_encoded_keys__, and the loader decodes exactly those. Files without
the list load as before, since their values can't be told apart.

Closes algorithmicsuperintelligence#574

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes for evolution_trace.include_failures:
- Timed-out and crashed iterations now reach the failures file; they were
  the failures with the least other evidence and were missing from it.
- Failed LLM calls keep the prompt, parent and island like later failures.
- failures_path now matches the file written when compress is on (it
  lacked .gz). Dotted trace names no longer share one failures file:
  run.v2 and run.v3 both wrote to run_failures.jsonl.

Each child now stores the sampled model as metadata["llm_model"], also in
the trace, so ensemble models can be compared. token_usage only names the
model when the provider returns usage data.

Closes algorithmicsuperintelligence#381

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codelion codelion changed the title feat: mix full rewrites into diff-based evolution (full_rewrite_probability) feat: mix full rewrites into diff-based evolution, plus trace fixes (v0.4.1) Nov 2, 2026
Cut the README from 1,074 to about 200 lines. It now opens with adoption
and a list of papers using OpenEvolve, then results, quick start and an
overview. Emojis are gone, which also fixes the Quick Start and
Installation anchors they were breaking, and the dead example output link
is removed. The quick start now clones the repo, since the examples are
not part of the pip package.

Detailed material moves to docs/: LLM providers, configuration, system
messages, showcase, how it works, FAQ and roadmap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codelion
codelion merged commit 73448d7 into algorithmicsuperintelligence:main Nov 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants