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.
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:
The rule is:
- if
scores["combined_score"]is present, that is the fitness; - otherwise, the mean of the numeric scores;
- 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.