The evolutionary loop¶
Galapagos models its bundled evolutionary-search methods — from a plain mutate-and-select loop to agent-driven and meta-evolutionary variants — as a composition of six components over one unit type, the Genome. A method is simply a choice of which implementation fills each of the six slots; the loop itself never changes.
The unit
A Genome — one candidate, plus everything needed to select, score, and trace it.
Who owns what
The task supplies the Evaluator. The scaffold supplies the other five. The Population owns admission.
Stop condition
general.max_iterations normally; a finite-frontier method may stop when its search space is
exhausted. The loop is strictly sequential — there is no parallel-iteration knob.
The loop in one sentence
Each iteration: select parents from the Population → build a prompt from them and Memory →
propose a candidate → evaluate it with a deterministic verifiable function → add the
scored Genome back to the Population (the store owns admission) → optionally write what was
learned to Memory. Repeat until general.max_iterations, or until a finite-frontier method has
no expandable state left.
The six components¶
Each role is an abstract base class in galapagos.components, with one or more shipped
implementations. The components own the behaviour; the orchestrator only drives the loop and
enforces the iteration budget.
| Component | Role | Shipped implementations |
|---|---|---|
| Population | the candidate store — holds Genomes, owns the topology, and owns admission (add() returns whether the genome got in). |
InMemoryPopulation, IslandPopulation |
| SelectionPolicy | the active, stateful policy — picks parents + inspirations, adapts over time. | ExploreExploitPolicy, UCBBanditPolicy, IdentityPolicy |
| PromptBuilder | the renderer — formats the selection + Memory into a Prompt (.system / .user). Pure formatting. |
DefaultPromptBuilder |
| Proposer | the variation operator — turns a prompt into a new Genome. | LLMProposer, CrossoverProposer |
| Evaluator | the deterministic verifiable scorer — maps a Genome to an EvalResult. Supplied by the task, not the scaffold. |
SubprocessEvaluator, ContainerEvaluator |
| Memory | the cross-cutting, free-form knowledge store (text in, text out). Optional. | NullMemory, ScratchpadMemory |
See Core components for each role in depth, the per-method coverage matrix, and how each conceptual role maps to a shipped class.
The unit of evolution: Genome¶
Everything flowing through the loop is a Genome — one candidate solution plus everything needed to
select, evaluate, and trace it: content, id, parent_id, lineage ("a → b → c"), scores,
metadata, artifacts. Its fitness is genome.scores["combined_score"], exposed as
genome.fitness. See Genome.
A method = which implementation fills each slot¶
The same six slots express every bundled method. Differences between methods are differences in which implementation fills a slot — not differences in architecture. For example:
| Slot | OpenEvolve | AdaEvolve |
|---|---|---|
| Population | map_elites_islands (islands + MAP-Elites) |
qd_island_archipelago (islands + dynamic spawning) |
| SelectionPolicy | three_tier_explore_exploit |
adaptive_intensity_ucb (UCB island routing + adaptive intensity) |
| PromptBuilder | openevolve_template |
adaevolve_template (+ tactic injection) |
| Proposer | diff |
diff |
| Evaluator | task (from the task) |
task (from the task) |
| Memory | (none) | paradigm_tactics (breakthrough tactics) |
AdaEvolve is OpenEvolve with four slots swapped — the defining two are an adaptive
SelectionPolicy (a UCB bandit over islands with adaptive exploration intensity) and a Memory of
paradigm-breakthrough tactics, alongside variant Population (dynamic island spawning) and
PromptBuilder (tactic injection) implementations. Nothing about the loop changes; only the
slots do.
Agent-driven scaffolds are no exception: when an autonomous agent picks its own parent, the slot is
just the IdentityPolicy (it returns the whole Population and defers the choice to the Proposer).
Meta-scaffolds — a search whose Proposer runs an inner search — are just nesting: the
Proposer interface (propose(prompt, env) -> Genome) is deliberately broad enough that a nested
Galapagos scaffold can fill the slot.
Running the loop¶
The whole loop is reachable through the card protocol: load a model, a scaffold, and a
task from cards, then call run.
import galapagos as gx
config = gx.GalapagosConfig.from_config(scaffold_name="openevolve")
model = gx.GalapagosModel.from_card(name="openai/gpt-4o-mini", host="openrouter")
scaffold = gx.GalapagosScaffold.from_card(name="openevolve", config=config, model=model)
task = gx.GalapagosTask.from_card(name="circle_packing")
result = scaffold.run(task=task)
print(result.best_score, result.summary)
result is a RunResult carrying best (the top Genome), best_score, history, and a summary
dict (cost, iterations, …).
Test-time search¶
The model is a frozen callable; the scaffold provides all the intelligence (selection + prompting).
The shipped scaffolds are openevolve, adaevolve, evox, gepa, ale_agent, algotune_agent,
meta_harness, the three SkyDiscover baselines best_of_n, topk, beam_search, and the Galapagos
variants best_of_n_attempts, random, claude_code, and codex — fourteen in total.
Cards: the single source of truth¶
Tasks and scaffolds use YAML cards in the local library and Hub. Model cards are a
local loading format. Discovery bundles are published through the Hub API or browser workflow;
the galapagos submit CLI currently accepts only task and scaffold repositories.
See also¶
- Core components — the six interfaces in depth, with the coverage matrix.
- Genome — the unit of evolution.
- Cards — the YAML protocol that loads and submits everything.
- Models — the model the Proposer calls.