A generator-agnostic genetic algorithm that evolves SKILL.md files. The GA never imports a course-generation product. You plug in a Generator (turn a genome + request into a course directory) and an Evaluator (score that directory). Selection uses the evaluator's aggregated score.
The previous proof-of-concept (LLM scores skills 0–9, no course generation) lives on the skill-based-fitness branch and is not this API.
pip install -e .from pathlib import Path
from ga_skill_evolution import Genome, FitnessResult
class MyGenerator:
def generate(self, genome: Genome, request: str, output_dir: Path) -> Path:
"""Return an opaque course directory."""
class MyEvaluator:
def evaluate(self, course_dir: Path) -> FitnessResult:
"""Return scores and aggregated fitness. Missing/failed course -> aggregated 0."""Genome.files is a list of (relative_path, text) pairs, typically SKILL.md bodies. Your generator decides how to apply them (for example, writing a skills/ tree that your pipeline reads).
Load plugins from the CLI by import path (module.path:ClassName):
ga-evolve \
--generator my_pkg.plugins:MyGenerator \
--evaluator my_pkg.plugins:MyEvaluator \
--seed path/to/request.md \
--skills-dir ./skills \
--population-size 6 \
--generation-limit 10There are no product-specific defaults. A stub plugin is included only for tests and dry runs:
ga-evolve \
--generator ga_skill_evolution.plugins.stub:StubGenerator \
--evaluator ga_skill_evolution.plugins.stub:StubEvaluator \
--request "A beginner JavaScript course" \
--skills-dir ./skills \
--generation-limit 1Create / crossover / mutate call a thin OpenAI-compatible client. Set one of:
GA_LLM_API_KEY+ optionalGA_LLM_BASE_URL/GA_LLM_MODELOPENROUTER_API_KEY(default modelgoogle/gemini-2.5-flash-lite)GROQ_API_KEY(default modelllama-3.1-8b-instant)OPENAI_API_KEY
ga-evolve --resume ga_output_YYYYMMDDHHMMSS --generator ... --evaluator ... --seed ...ga_output_<timestamp>/
state.json
results.jsonl
report.json
gen_000/ind_000/skills/...
courses/gen_000/ind_000/...
best_ever/skills/...