Python API¶
The import package is galapagos; the distribution is open-galapagos (the PyPI name galapagos
belongs to an unrelated project — install from source with pip install -e .). The public
surface is small — import everything from the top level.
import galapagos as gx
# primary classes
gx.GalapagosModel, gx.GalapagosConfig, gx.GalapagosScaffold, gx.GalapagosTask
gx.AdaEvolveScaffold, gx.ALEAgentScaffold, gx.AlgoTuneScaffold, gx.BeamSearchScaffold, gx.BestOfNScaffold, gx.EvoXScaffold, gx.MetaHarnessScaffold, gx.OpenEvolveScaffold, gx.TopKScaffold
# BestOfNAttemptsScaffold, ClaudeCodeScaffold: from galapagos.scaffolds import ...
# records
gx.Genome, gx.Selection, gx.EvalResult, gx.LoopContext, gx.RunResult
# registry + functional loaders
gx.available_scaffolds, gx.available_tasks, gx.registered_scaffolds
gx.configure_logging # opt-in console logging (importing galapagos installs a NullHandler)
gx.AutoScaffold, gx.register_scaffold
gx.load_scaffold, gx.load_model, gx.load_task, gx.load_config
Primary classes¶
GalapagosModel¶
Resolve a model from a name + host (or a model-card YAML at path). Hosts: openai,
openrouter (the default), anthropic, gemini, azure, vllm, litellm — or any explicit
OpenAI-compatible base URL (a string containing ://). The API key is read from the host's own
env vars (OPENROUTER_API_KEY for openrouter; OPENAI_API_KEY for openai;
ANTHROPIC_API_KEY; GEMINI_API_KEY/GOOGLE_API_KEY;
AZURE_OPENAI_API_KEY/AZURE_API_KEY/OPENAI_API_KEY; vllm/litellm need none) — or pass
api_key= explicitly. Subclasses implement generate(prompt: Prompt) -> Generation.
model = gx.GalapagosModel.from_card(name="openai/gpt-4o-mini", host="openrouter")
model = gx.load_model("openai/gpt-4o-mini", host="openrouter") # functional alias
GalapagosConfig¶
GalapagosConfig.from_config(scaffold_name=None, *, path=None, **overrides) -> GalapagosConfig
cfg.get(dotted, default=None) -> Any
cfg.set(dotted, value) -> GalapagosConfig
cfg.section(name) -> Any # the typed section dataclass (e.g. GeneralConfig)
cfg.as_dict() -> dict
A thin config object over a nested dict, accessed by dotted paths.
from_config(scaffold_name="openevolve") loads the scaffold's bundled defaults;
from_config(path="cfg.yaml") loads a file.
cfg = gx.GalapagosConfig.from_config(scaffold_name="openevolve")
cfg.set("population.num_islands", 8).set("general.max_iterations", 200)
cfg.get("general.max_iterations") # -> 200
GalapagosScaffold¶
The orchestrator that drives the six components around the loop.
GalapagosScaffold.from_card(name=None, *, path=None, config=None, model=None,
population=None, selection_policy=None, prompt_builder=None,
proposer=None, evaluator=None, memory=None, seed=None, **kw) -> GalapagosScaffold
scaffold.run(task=None, *, max_iterations=None, trajectory_path=None, run_dir=None,
resume_from=None) -> RunResult
# run_dir: write the complete run bundle, including evolutionary_trajectory.jsonl
# and the sibling tool_calling_trajectory.jsonl projection
# trajectory_path: optional compatibility override for the raw event-stream filename
# resume_from: a checkpoint dir (or a run dir -> its latest checkpoint) to restore population + loop context from
MetaHarnessScaffold.run() is the exception: it requires run_dir, stores archive D under
<run_dir>/meta_harness_D, and rejects resume_from because Meta-Harness does not support resume.
Three load modes: by name (from_card("openevolve", model=...) — dispatches via the registry),
subclass defaults (OpenEvolveScaffold.from_card() — loads its own card + config + default
model), and build-your-own (pass component instances / module.Class paths / .py paths to the
six role kwargs).
@classmethod
def build_components(cls, config, model) -> dict # the five scaffold-side components (override in a subclass)
# adaptation hooks (no-ops by default):
def before_step(self) -> None: ... # before selection
def after_step(self, child: Genome, result) -> None: ... # after eval (result is None on a no-op)
def periodic(self) -> None: ... # once per iteration, after the step
The fourteen runnable subclasses are AdaEvolveScaffold, ALEAgentScaffold, AlgoTuneScaffold,
BeamSearchScaffold, BestOfNScaffold, BestOfNAttemptsScaffold, ClaudeCodeScaffold,
CodexScaffold, EvoXScaffold, GepaScaffold, MetaHarnessScaffold, OpenEvolveScaffold,
RandomScaffold, and TopKScaffold. See
Write your own scaffold.
GalapagosTask¶
GalapagosTask.from_card(name=None, *, path=None, local_path=None, cache_dir=None, source="auto",
download_external=False, hub_url=None, token=None) -> GalapagosTask
task.context -> str # the problem statement injected into prompts
task.status -> str # 'stable' | 'experimental' | 'spec' | 'external'
task.runnable -> bool # True iff it ships a seed + evaluator
task.initial_genome() -> Genome # the seed Genome
task.evaluator -> Evaluator | None # ContainerEvaluator (the default) or SubprocessEvaluator
# (general.eval_mode: local — the same evaluator.py in a host
# subprocess; environment/timing can differ).
task.set_eval_mode(mode) -> GalapagosTask # per-run override of the resolved mode ('local' / 'docker')
The task supplies the Evaluator (not the scaffold). A runnable scaffold can be paired with a runnable, compatible task; agent/container requirements can further restrict a pairing.
For a name, source="auto" resolves an explicit/bundled local card first, then reuses the Hub
cache or downloads the repo from the Hub. source="local" forbids Hub access; source="hub"
forces a fresh download. An explicit path= is always local. from_card() preserves its historical
external-resource behavior, so pass download_external=True when the task's declared large data
must also be materialized.
The six components¶
from galapagos.components import .... Every component is an abstract base with shipped impls.
Population¶
class Population: # the candidate store
def add(self, genome: Genome) -> bool: ... # returns whether admitted
def query(self, spec: dict | None = None) -> list[Genome]: ...
def all(self) -> list[Genome]: ...
def best(self) -> Genome | None: ...
| Impl | Purpose |
|---|---|
InMemoryPopulation(capacity=1000, drop_invalid=False) |
A bounded top-k / leaderboard list kept sorted by fitness. |
IslandPopulation(num_islands=4, migration_interval=25, migration_rate=2, descriptor=None) |
Islands of MAP-Elites cells with periodic ring migration. |
SelectionPolicy¶
class SelectionPolicy: # the active, stateful policy
def select(self, population, ctx: LoopContext | None = None) -> Selection: ...
def observe(self, genome: Genome, ctx: LoopContext | None = None) -> None: ...
| Impl | Purpose |
|---|---|
ExploreExploitPolicy(seed=0, explore_ratio=0.3, num_inspirations=3) |
Explore/exploit split + fitness-weighted exploit; diverse inspirations. |
UCBBanditPolicy(seed=0, num_islands=4, c=1.4, num_inspirations=2) |
UCB1 routing over islands; mirrors posteriors into ctx.blackboard['ucb']. |
IdentityPolicy() |
Delegated/agent-driven: returns the whole population, defers the choice to the Proposer. |
PromptBuilder¶
class PromptBuilder: # the renderer (pure formatting, no selection)
def build(self, selection: Selection, memory=None, ctx: LoopContext | None = None) -> Prompt: ...
| Impl | Purpose |
|---|---|
DefaultPromptBuilder(system_message=None, suggest_simplification_after_chars=500) |
The canonical multi-section template (task → metrics → feedback → inspirations → memory → current program). Memory is rendered when the loop has a Memory component; it is not a constructor flag. |
Proposer¶
Env(model, selection, evaluator=None, memory=None, ctx=None) is the toolbox handed to a Proposer.
| Impl | Purpose |
|---|---|
LLMProposer() (alias DiffProposer) |
One LLM call → an EditStrategy (SEARCH/REPLACE diff, or full rewrite when mutation_approach=full_rewrite) applied to the parent; no-op detection. DiffProposer remains as a back-compat alias. |
CrossoverProposer(novelty_threshold=0.9, recent=12) |
Crossover + token-Jaccard novelty rejection (one resample on near-duplicates). |
Helper: apply_edit(parent_code, response, *, fence_fallback=True) -> (new_code, changed).
Evaluator¶
class Evaluator: # the pure scorer (supplied by the task)
def evaluate(self, genome: Genome) -> EvalResult: ...
| Impl | Purpose |
|---|---|
SubprocessEvaluator(evaluator_path, timeout=120, suffix=".py", cascade_evaluation=True, cascade_thresholds=None, max_retries=0, secret_allowlist=None) |
Runs the task's evaluator.py in an isolated host subprocess (with evaluate_stageN cascade support). Built when the run sets general.eval_mode: local. |
ContainerEvaluator(evaluator_path, task_dir, *, timeout=120, dockerfile=None, env_vars=None, gpus=None, network=None, ...) |
The same evaluator.py scored inside the task's own container image — the default. The run opts out with --general.eval_mode local. |
Task evaluator contract: evaluate(program_path) -> dict with at least combined_score (float),
and optional validity / status / per_instance / artifacts.text_feedback.
Memory¶
class Memory: # free-form knowledge (optional)
def read(self, spec: dict | None = None) -> str: ...
def write(self, knowledge: str, **meta) -> None: ...
| Impl | Purpose |
|---|---|
NullMemory() |
The empty memory — the default. |
ScratchpadMemory(max_notes=8) |
A rolling meta-scratchpad of distilled design insights. |
Records¶
from galapagos import Genome, Selection, EvalResult, LoopContext, RunResult
Genome¶
@dataclass
class Genome:
content: str # the artifact being evolved (code, prompts, config, ...)
id: str # auto-assigned 'g000001'
parent_id: str | None = None
lineage: str = ""
scores: dict[str, float] = {} # filled by the Evaluator
metadata: dict = {} # selection data: island, cell, generation, embeddings, ...
artifacts: dict = {} # evaluator side-output (text_feedback, traces)
@property
def fitness(self) -> float # scores['combined_score'], else mean of numeric scores, else -inf
def child(self, content, **metadata) -> Genome # descendant with lineage wired up
Selection¶
@dataclass
class Selection:
parent: Genome | None # the parent to mutate (None => delegated selection)
inspirations: list[Genome] = [] # context-only inspirations
pool: list[Genome] = [] # the full visible population (for delegated selection)
EvalResult¶
@dataclass
class EvalResult:
metrics: dict[str, float] = {} # must contain 'combined_score'
artifacts: dict = {}
valid: bool = True # gates admission
per_instance: list[float] | None = None # per-test-case success vector
text_feedback: str | None = None # surfaced into later prompts
@property
def combined_score(self) -> float
LoopContext¶
@dataclass
class LoopContext:
iteration: int = 0
cost_usd: float = 0.0
prompt_tokens: int = 0
completion_tokens: int = 0
best: Genome | None = None
run_dir: str | None = None
task_context: str = ""
blackboard: dict = {} # what each component publishes for the others (keyed by name)
recent: list = [] # recently-admitted genomes (bounded, in-memory; feeds Previous-Attempts prompt sections)
started_at: float
def record_cost(self, cost_usd, prompt_tokens=0, completion_tokens=0) -> None
@property
def elapsed_s(self) -> float
RunResult¶
@dataclass
class RunResult:
best: Genome | None
summary: dict = {} # {scaffold, task, iterations, evaluations, best_score, best_metrics, cost_usd, no_diff, rejected_too_long, language, population_size}
run_dir: str | None = None
history: list[Genome] = [] # the seed + every evaluated child, in order
@property
def best_score(self) -> float # best.fitness, or -inf
Loaders & registry¶
load_model(name=None, host=None, *, path=None, base_url=None, api_key=None, **kw) -> GalapagosModel
load_config(scaffold_name=None, *, path=None, **overrides) -> GalapagosConfig
load_scaffold(name=None, *, path=None, model=None, config=None, **kw) -> GalapagosScaffold
load_task(name=None, *, path=None, local_path=None, cache_dir=None, source="auto",
download_external=True, hub_url=None, token=None) -> GalapagosTask
available_scaffolds() -> list[str] # all bundled scaffold cards (all runnable)
available_tasks() -> list[str] # all bundled task cards
registered_scaffolds() -> list[str] # the runnable subset (== available_scaffolds() today)
@register_scaffold("name") # decorator: wire a Scaffold subclass to its card name
AutoScaffold.from_card(name, ...) # name -> runnable scaffold (used internally by load_scaffold)
The functional load_* functions mirror the corresponding *.from_card classmethods. load_task()
is a compatibility wrapper around GalapagosTask.from_card() with one intentional default
difference: it fetches declared external_resources unless download_external=False. Both APIs use
the same source resolution and Hub cache. There is no longer an offline or force_download
keyword — source replaced both.
Trajectories¶
ETIF export — galapagos.trajectory¶
from galapagos.trajectory import (
convert_run_dir, run_dir_to_etif, ConvertError, SCHEMA_VERSION, INLINE_MAX_BYTES,
CandidateRecord, EvolutionEvent, ToolEventDetails, AgentSessionRecord, RunProvenance,
)
convert_run_dir(run_dir, *, out_path=None, inline_threshold=INLINE_MAX_BYTES) -> Path # writes etif.json
run_dir_to_etif(run_dir, *, inline_threshold=INLINE_MAX_BYTES) -> EvolutionaryTrajectory
ETIF (Evolutionary Trajectory Interchange Format) is the current post-run export. ETIF v1.1 turns the
unified evolutionary_trajectory.jsonl stream into a candidate ledger, causal event timeline,
agent-session records, and provenance. Legacy runs fall back to evolution_events.jsonl and then
trajectory.jsonl. convert_run_dir is what
galapagos export RUN_DIR (and
run --emit-etif) calls. Its primary models are CandidateRecord, EvolutionEvent, and
EvolutionaryTrajectory; the field-by-field spec is rfcs/0006-etif-v1.1.md. Explicit v1.0
(EvolutionaryTrajectoryV10, run_dir_to_etif_v10, convert_run_dir_v10) and v0.3
compatibility APIs retain the prior document shapes.
ToolEventDetails validates the shared tool/execute detail vocabulary. arguments and result
remain arbitrary JSON, while call identity/order, status, duration, structured errors, and optional
artifact-backed results are normalized. Tool-enabled model calls keep aggregate usage in
model_call.details; individual tool payloads live on causal tool events.
galapagos.traces — Claude Code session logs¶
from galapagos import traces
traces.export_claude_code_traces(path, *, out_dir=None, recursive=True, include_thinking=False,
redact_paths=True, write_sharegpt=True, write_events=True) -> dict
traces.discover_claude_code_traces(path, *, recursive=True) -> list[Path]
traces.convert_native_session(trace_file, *, context=None, include_thinking=False) -> (row, events)
Convert native Claude Code session JSONL logs (e.g. from a claude_code scaffold run dir) into
derived artifacts for SFT and visualization: rows with OpenAI-style messages (plus ShareGPT
variants), a flattened event timeline, and an aggregate summary.
Cards¶
from galapagos.cards.registry import (
load_scaffold_card, load_task_card, available_scaffolds, available_tasks,
)
from galapagos.cards.schema import ScaffoldCard, TaskCard, ModelCard, VerificationCard
See Scaffold & Task cards for the schemas.