Run a scaffold¶
For model-driven search, running a discovery method is four objects — a model, a config, a scaffold, and a compatible runnable task — and one call. Coding-agent modes construct their agent instead of an API model.
import galapagos as gx
model = gx.GalapagosModel.from_card(name="openai/gpt-5.5", host="openrouter")
config = gx.GalapagosConfig.from_config(scaffold_name="openevolve")
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) # best combined_score found
print(result.best.content) # the winning program (a string)
scaffold.run(task=...) drives the six-component loop: select
parents from the Population → build a prompt → propose a candidate → evaluate it → add
the scored Genome back → repeat until the budget is spent.
The four objects¶
Model¶
GalapagosModel.from_card(name=..., host=...) resolves a hosted, OpenAI-compatible endpoint. The
supported hosts are openai, openrouter (the default), anthropic, gemini, azure, vllm, and
litellm — or pass an explicit OpenAI-compatible base URL as the host. The API key is read from
the environment per host — OPENAI_API_KEY for openai, OPENROUTER_API_KEY for openrouter,
ANTHROPIC_API_KEY for anthropic, GEMINI_API_KEY (or GOOGLE_API_KEY) for gemini, and
AZURE_OPENAI_API_KEY for azure. OpenRouter requires OPENROUTER_API_KEY (or an explicitly
passed api_key) so an OpenAI credential is never sent to OpenRouter. vllm and
litellm need no key (see the host/env-var tables in
Models).
Every API-model run calls a live LLM, so set an OpenRouter key first: Galapagos reads it from
OPENROUTER_API_KEY — see
Installation.
Config¶
GalapagosConfig.from_config(scaffold_name=...) loads the scaffold's bundled default config; or pass
path="cfg.yaml" for your own. Read and override tunables with dotted paths:
config = gx.GalapagosConfig.from_config(scaffold_name="openevolve")
config.set("population.num_islands", 8)
config.set("general.max_iterations", 200)
config.get("general.max_iterations") # -> 200
The general section holds run-level settings such as the iteration budget, the checkpoint interval,
the mutation approach, and the inner-retry policy.
Scaffold¶
Three ways to construct one:
# 1. by name via the registry (the base class dispatches)
scaffold = gx.GalapagosScaffold.from_card(name="openevolve", config=config, model=model)
# 2. a concrete subclass loads its own card + defaults (config/model optional)
scaffold = gx.OpenEvolveScaffold.from_card(model=model)
# 3. build-your-own from components (see "Write your own scaffold")
scaffold = gx.GalapagosScaffold.from_card(population=..., selection_policy=..., proposer=...)
List the runnable scaffolds at any time:
gx.available_scaffolds() # every bundled card: ['adaevolve', 'ale_agent', 'algotune_agent', 'beam_search', 'best_of_n', 'best_of_n_attempts', 'claude_code', 'codex', 'evox', 'gepa', 'meta_harness', 'openevolve', 'random', 'topk']
gx.registered_scaffolds() # the runnable subset — the same fourteen
All bundled scaffolds are runnable
The catalog ships 14 cards — adaevolve, ale_agent, algotune_agent, beam_search, best_of_n,
best_of_n_attempts, claude_code, codex, evox, gepa, meta_harness, openevolve, random, and topk — and
every one has a runnable Python controller.
GalapagosScaffold.from_card("nope", ...) raises a clear KeyError listing the runnable set.
Task¶
GalapagosTask.from_card(name=...) loads the problem statement, the seed program, and the
Evaluator. The Evaluator is supplied by the task, not the scaffold. A run still requires a
runnable task and compatible language, dependencies, hardware, and execution mode.
task = gx.GalapagosTask.from_card(name="circle_packing")
task.context # the problem text injected into prompts
task.runnable # True iff it ships a seed + evaluator.py
task.status # 'stable'
task.initial_genome() # the seed Genome
The catalog bundles 1,274 task cards; 1,269 are runnable. circle_packing and
function_minimization are the canonical quickstart examples. See the
task catalog.
Meta-Harness¶
Meta-Harness requires persistent archive storage and does not support checkpoint resume:
galapagos run --scaffold meta_harness --task circle_packing \
--output-dir outputs/meta-circle-packing
The archive is <output-dir>/meta_harness_D. Its default proposer is Claude Code, authenticated by
CLAUDE_CODE_OAUTH_TOKEN or an existing Claude login. With the default Docker evaluation mode the
controller, proposer, and evaluator all execute inside the task container environment. Set
proposer.mode=llm only when you intentionally want the API-model fallback.
The Iteration Budget¶
The base loop stops when state.iteration >= general.max_iterations. Set it on the config, or
override it inline on run:
config.set("general.max_iterations", 100) # cap on iterations
result = scaffold.run(task=task, max_iterations=50) # inline override of max_iterations
Reading RunResult¶
run returns a RunResult:
result = scaffold.run(task=task)
result.best # the best Genome (or None)
result.best_score # result.best.fitness, or -inf
result.history # list[Genome] — the seed + every evaluated child, in order
result.run_dir # run directory (if the scaffold set one)
result.summary # a dict, e.g.:
{
"scaffold": "openevolve",
"task": "circle_packing",
"iterations": 100, # loop steps taken
"evaluations": 94, # genomes evaluated = 1 (seed) + iterations - no_diff
"best_score": 2.61, # best combined_score
"best_metrics": {"combined_score": 2.61, "validity": 1.0}, # the best Genome's full metric dict
"cost_usd": 0.42, # accumulated model spend
"no_diff": 7, # wasted steps where the Proposer returned a no-op
"rejected_too_long": 2, # candidates dropped for exceeding the length cap
"language": "python", # the task's program language
"population_size": 40, # genomes currently in the Population
}
The winning artifact is result.best.content (a string of source code). Its metric dict is
result.best.scores and the headline number is result.best.fitness (==
result.best.scores["combined_score"]).
Record the complete trajectory¶
Set an output directory when you need the complete record—not only the winning program. It
automatically enables both raw JSONL streams; no separate trajectory path is needed. Add
--emit-etif only when you also want the normalized, scaffold-neutral JSON document:
The run writes:
outputs/demo/
├── evolutionary_trajectory.jsonl
│ # candidates and every evolutionary lifecycle event
├── tool_calling_trajectory.jsonl
│ # one validated event per tool execution attempt
├── config.yaml # latest secret-free effective config
├── run.json # terminal summary and artifact index
├── best/ # canonical winning candidate
├── checkpoints/
│ └── checkpoint_<i>/
│ ├── state.json
│ └── tool_calling_trajectory.jsonl
│ # cumulative tool calls through checkpoint i
└── etif.json # optional normalized export added by --emit-etif
evolutionary_trajectory.jsonl includes candidate creation as well as behavior without a solution
row: evaluator or model failures, migration, crossover, eviction, adaptation, guidance/strategy
evolution, agents, tools, memory, checkpoints, and final evaluation. See
Trajectories (ETIF) for the event envelope and extension rules.
From Python, run_dir enables the raw streams in the same way; export them afterward when needed:
from galapagos.trajectory import convert_run_dir
result = scaffold.run(
task=task,
run_dir="outputs/demo",
)
convert_run_dir(result.run_dir)
A short, cheap run¶
Every model-driven run calls a live LLM and spends budget. Keep an exploratory run small by starting on the tiny
function_minimization task and capping the iteration count:
import galapagos as gx
model = gx.load_model("openai/gpt-4o-mini", host="openrouter")
scaffold = gx.OpenEvolveScaffold.from_card(model=model)
task = gx.load_task("function_minimization") # the fastest task
result = scaffold.run(task=task, max_iterations=20)
print(result.best_score) # > the seed score
The CLI¶
The galapagos console script wraps the same flow. Use repeatable --set flags for model and config
overrides.
# a short run on the smallest task
galapagos run --scaffold openevolve --task function_minimization \
--set proposer.model_name=openai/gpt-4o-mini \
--set proposer.api_base=openrouter \
--set general.max_iterations=20
# a longer run via OpenRouter
galapagos run --scaffold openevolve --task circle_packing \
--set proposer.model_name=openai/gpt-5.5 \
--set proposer.api_base=openrouter \
--set general.max_iterations=100
# point at a custom config YAML, set the seed
galapagos run --scaffold adaevolve --task function_minimization \
--config my_config.yaml \
--set proposer.model_name=openai/gpt-4o-mini \
--set proposer.api_base=openrouter \
--set seed=7
# inspect the catalogs
galapagos scaffold list
galapagos task list
galapagos run prints the final best_score and the summary JSON. Use
galapagos submit to validate a card.