Skip to content

Genome — the unit of evolution

Everything that flows through the loop is a Genome: one candidate solution plus everything needed to select, evaluate, and trace it. The six components are all defined over the Genome — the Population stores Genomes, the SelectionPolicy chooses among them, the Proposer produces a new one, and the Evaluator scores it.

from galapagos import Genome

g = Genome(content="def solve(): ...")

Fields

Field Type Purpose
content str The artifact being evolved — code, a prompt-set, an agent codebase, a config, an idea.
id str Stable unique id, auto-assigned (e.g. g000123).
parent_id str \| None The id of the Genome this one was derived from.
lineage str The ancestry chain, wired up automatically (e.g. g000001 → g000007 → g000042).
scores dict[str, float] Filled in by the Evaluator (e.g. {"combined_score": 0.87, "latency_ms": 12.0}).
metadata dict[str, Any] Whatever the active SelectionPolicy needs: feature coordinates (MAP-Elites), island id, generation, embeddings, per-instance success vectors.
artifacts dict[str, Any] Evaluator side-output (stderr, profiling, traces, text feedback) fed back into later prompts.

The lineage uses the human-readable arrow form a → b → c, so a Genome carries a self-describing trace of how it came to be — the backbone for crossover, backtracking, and island migration.

Fitness

A Genome's fitness is the single comparable number used for selection and admission, exposed as the fitness property:

g.scores["combined_score"] = 0.87
g.fitness        # -> 0.87

The rule is:

  1. if scores["combined_score"] is present, that is the fitness;
  2. otherwise, the mean of the numeric scores;
  3. otherwise, -inf (an unscored Genome).

The Evaluator's combined_score is therefore the headline metric the whole search maximizes — the first metric in the Task Card's metric list. An invalid candidate keeps combined_score == 0.

Lineage and descendants

Use Genome.child(...) to construct a descendant: it sets parent_id, extends lineage, and inherits the parent's metadata (overridable via keyword arguments). This is how crossover, backtracking, and migration trace ancestry.

parent = Genome(content="v1")
kid    = parent.child("v2", island=3)

kid.parent_id    # parent.id
kid.lineage      # parent.id   (the "a → b → c" chain extends each generation)
kid.metadata     # inherited, plus {"island": 3}

Genome vs. Memory

A frequent question: where does information live? Two stores split the answer.

Population (Genomes) Memory
Holds candidates free-form knowledge
Answers "what solutions exist?" "what have we learned?"
Examples code, scores, lineage, MAP-Elites coordinates notes, skills, strategies, a meta-scratchpad
Shape structured Genome records unstructured text / artifacts

Rule of thumb

Per-candidate data that drives selection or lineage lives on the Genome (in scores, metadata, artifacts). Cross-candidate free-form knowledge that guides generation lives in Memory. Memory is optional; a plain evolutionary loop leaves it empty (NullMemory).

See Core components for how each component reads and writes the Genome, and The evolutionary loop for the loop the Genome flows through.