OpenEvolve — Program evolution¶
Program self-evolution. Python source is the genome: model calls mutate it, a sandboxed evaluator supplies reward, and a MAP-Elites island archive keeps the population. Runs through
evolve()/async_evolve()with a customStrategy+aggregator_factoryat L1 governance. Example:examples/openevolve/openevolve_program_evolution.py.
| Paper | no paper — the port follows the released code (the technique is AlphaEvolve-shaped) |
| Upstream code | algorithmicsuperintelligence/openevolve@411fb59c, examples/function_minimization + the database implementation |
| Example | examples/openevolve/openevolve_program_evolution.py |
| Domain | the bundled function-minimization task, 8 evaluator seeds (6 train / 2 held out) + 6 disjoint test seeds |
| Layer | L1 program (blast_radius=0.6, AST-gated and sandbox-isolated) |
| Fidelity | benchmark_faithful — what the classes mean |
Port author: cyanneko.
Algorithm mapping¶
| OpenEvolve mechanism | AgentDescent representation |
|---|---|
EpsilonGreedy |
the in-pool parent pick as a named SelectionPolicy |
| Python program candidate | Task rollouts over a source-code artifact |
| Model mutation | propose(rendered, task, output, reward) |
| Full program replacement | OpenEvolveStrategy.to_diff() |
| Function-minimization evaluator | run() plus reward_program() |
| MAP-Elites islands | OpenEvolveAggregator and its shared archive |
| Concurrent candidate workers | evolve(max_concurrency=...) |
| Completion-order commits | async_evolve(async_ratio=...) |
| Versioned best program | AgentDescent Ledger dev head |
| Stale candidate handling | AgentDescent evidence cards and staleness policy |
The artifact is generated executable code, so the port declares
blast_radius=0.6 and is classified as an L1 change. The algorithm-specific
evaluator remains the acceptance authority, as it is in OpenEvolve.
Fidelity and boundaries¶
The reference is OpenEvolve commit
411fb59c886c18704caaffb611e17cf9e7d824d2,
specifically examples/function_minimization and the database implementation.
Preserved mechanics:
- Python source is the genome and a model mutates a selected parent.
- The evaluator's value, distance, reliability, and basin-multiplier formula is
preserved from
evaluator.py. - Parent selection mixes exploitation and exploration.
- Each island owns a MAP-Elites grid over program length and code diversity.
- Children remain on their target island and elites migrate in a ring.
Intentional differences:
- The compact genome is rewritten in full instead of using SEARCH/REPLACE patches.
- Feature bins use fixed length boundaries and insertion-time token-Jaccard diversity rather than evolving min/max scaling.
- AgentDescent supplies workers, the ledger, evidence cards, barriers, and the
barrier-free runtime. OpenEvolve's process controller and database therefore
become a
Strategyand customAggregator. - Candidate execution is deterministic and budgeted. An AST gate rejects unsafe syntax and hard-coded evaluator optima before the sandbox starts.
- Candidate isolation clears the environment, removes network access, mounts the candidate read-only, and applies CPU, address-space, file-size, open-file, and process limits inside the sandbox runner.
Isolation has two backends and picks whichever the host offers: Bubblewrap
(bwrap) on Linux, and Seatbelt (sandbox-exec) on macOS. Both deny
network access and confine writes to a scratch directory; the resource limits
come from setrlimit inside the runner and so are the same on either platform,
except that Darwin has no RLIMIT_AS and the runner reports which limits the
platform refused rather than pretending it applied them. A host with neither
backend raises rather than running model-written code unsandboxed. The offline
test suite skips only the sandbox execution test when no backend is available;
all strategy, archive, engine, and CLI tests still run.
The two backends are not equivalent, and the difference is stated rather than
glossed: Bubblewrap additionally clears the environment and mounts the root
read-only, where Seatbelt leaves reads and the environment alone. The Seatbelt
profile is therefore a defence-in-depth layer over an AST gate that has already
rejected imports and attribute access, not the only thing between a model and
the disk. What it does enforce is checked against the kernel rather than by
reading the profile back:
test_seatbelt_actually_blocks_the_writes_the_profile_claims_to_block runs a
process under the real profile and asserts that a write outside the scratch
directory fails, a write inside it succeeds, and socket.create_connection
cannot reach the network.
Measured results — function minimization¶
The method¶
| Setting | Value |
|---|---|
| Model | deepseek-v4-flash, Anthropic-shaped API |
| Sampling | temperature 0.7, thinking disabled, --max-tokens 32000 |
| Mode | async_evolve(n_workers=4, async_ratio=3), --staleness full |
| Budget | 24 rollouts, hard-capped; --max-seconds 1800 |
| Islands | 3, migration interval 4 |
| Evaluator tasks | 8: 6 train and 2 held out |
| Independent test | 6 disjoint seeds after the run |
| Objective budget | 200 objective calls per evaluator seed |
| Isolation | Seatbelt (sandbox-exec), macOS |
| Replay | none; a single live engine run |
The recorded output is
bench/results/openevolve-quality-run.json.
The three-mode timing harness is still
bench/openevolve_agentdescent.py;
this page reports quality, and the cross-algorithm speedup numbers live in
efficiency.md.
The result¶
24 rollouts, 3 islands, --staleness full, 4 workers, 24 mutation calls,
76,294 tokens, 0 failures, 50.8 seconds of wall clock. Every figure below is
scored on the held-out trial seeds, which the search never saw:
| baseline | best found | ||
|---|---|---|---|
| combined score | 0.9638 | 1.4995 | ceiling is 1.5 |
| framework reward | 0.7620 | 0.9997 | |
| mean distance to the global optimum | 0.5260 | 0.00057 | 920× closer |
| mean value found (optimum: -1.5187) | -1.2892 | -1.5187 | |
| value standard deviation | 0.1392 | 0.0 | same answer every seed |
The ceiling is 1.5 because combined_score is
(0.5·value + 0.3·distance + 0.2·reliability) times a basin multiplier that
tops out at 1.5, and the winner scores 0.9997 / 0.9994 / 1.0 on the three terms.
The evolved program is not merely better than random search; it has effectively
solved the benchmark.
The winning program appeared at iteration 15 on island 2 and its own summary reads: "Replaced the final random perturbation phase with a Nelder-Mead simplex refinement step ... then adds a lighter fine-tuning pass." 586 characters of uniform random search became 7,848 characters of coarse grid, adaptive-step compass search, and simplex refinement.
The archive machinery is all live: 25 programs evaluated, 22 valid, island cell
counts [3, 2, 2], and 3 ring migrations. --staleness full considered 24
stale cards and discarded none.
Two settings decide whether this port searches at all
Both produce an empty model reply, which the round records as a candidate
that did not improve — indistinguishable from a search that found nothing.
make_propose counts empty replies and warns, naming both as the causes.
Reasoning off. Measured on deepseek-v4-flash: with it on, a mutation
call spends 96% of its output on thinking — 75k characters against a 3.2k
program — takes a median 234 s, and hits the 32000-token ceiling in 3 of 6
samples. Off: 512–990 output tokens, 7 s, 0 of 6 unparseable.
--max-tokens at least 32000. Upstream's config.yaml says 16000 and
mutates with SEARCH/REPLACE diffs; this port rewrites the whole genome, so it
needs more than upstream, not less. Six samples: 2048 gave no parseable
program at all, 16000 failed 5 of 6, 32000 failed 2 of 6.
Run it¶
Preview without an API key, network access, or sandbox process:
python -m examples.openevolve.openevolve_program_evolution --dry-run
# six mutations through an OpenAI-compatible endpoint
python -m examples.openevolve.openevolve_program_evolution \
--provider glm --model glm-5.2 --iterations 6 --workers 3 --yes
# the run measured above
python -m examples.openevolve.openevolve_program_evolution --yes --seed 0 \
--tasks 8 --budget-rollouts 24 --workers 4 \
--islands 3 --migration-interval 4 \
--async --async-ratio 3 --staleness full \
--max-seconds 1800 --max-tokens 32000 --model deepseek-v4-flash
Add --async for the barrier-free engine, or --serial for the upstream serial
algorithm. OPENAI_API_KEY and OPENAI_BASE_URL must be set for the glm
provider.
--serial and the benchmark's serial mode are two different baselines
--serial is the shared port flag:
one worker, so there is nothing to merge and the loop is the published
one. bench/openevolve_agentdescent.py also has a mode called serial, and it
means something narrower — evolve(max_concurrency=1) with the full worker
count, i.e. the same algorithm run without thread concurrency. That mode
isolates threading; the flag isolates merging.
Offline tests: tests/test_openevolve_example.py.