Skip to content

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

GalapagosModel.from_card(name=None, host=None, *, path=None, **kw) -> 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

class Proposer:                         # the variation operator
    def propose(self, prompt, env: Env) -> Genome: ...

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.