Darwinian Evolver: Evolve Prompts, Regex, SQL, and Code
Evolve prompts/regex/SQL/code with Imbue's evolution loop.
Written by Neura Market from the official Hermes Agent documentation for Darwinian Evolver. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationThe Darwinian Evolver skill gives Hermes Agent a way to run Imbue's darwinian_evolver, an LLM-driven evolutionary search loop, against a prompt, regex, SQL query, or small code snippet. You supply a starting candidate and a scorer, and the loop proposes mutations, evaluates them, and keeps the better ones. Reach for it when you have a measurable fitness signal and want the machine to search the space of variants for you.
What it does
The skill is a thin wrapper around the upstream tool. It installs darwinian_evolver into a cache directory, then walks the agent through writing a Problem definition: an organism (the artifact being evolved), an evaluator (the scorer), and a mutator (the thing that proposes changes). From there it drives the loop either through the upstream CLI or through a small custom Python driver.
The practical shape is always the same. You have something that can be scored, such as a regex that should match a known set of strings, a prompt that should produce a known answer, or a SQL query that should return a known result. You also have a starting version of it. The evolver generates variants, scores each one, and uses the scores to decide which variants become parents for the next round. Over enough iterations the population tends to improve, and you get a lineage you can inspect.
This is not a general-purpose optimizer. It is a search loop over discrete artifacts, driven by an LLM as the mutation operator. That makes it useful for the fuzzy, non-differentiable stuff that gradient methods cannot touch, and wasteful for anything a human could just write by hand.
Before you start
The skill is optional and installed on demand. Its path is optional-skills/research/darwinian-evolver, version 0.1.0, authored by Bihruze (Asahi0x) and Hermes Agent, under the MIT license. It runs on linux and macos.
You need Python 3.11 or newer, plus git and either uv or pip. You also need one of three API keys: OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY.
Which key you need depends on how you drive the loop. The skill ships a small parrot_openrouter.py driver that uses OPENROUTER_API_KEY through the OpenAI SDK, so any model available on OpenRouter works. The upstream CLI itself hardcodes Anthropic and needs ANTHROPIC_API_KEY.
One licensing point matters before you install anything. The upstream tool is AGPL-3.0. The skill only ever invokes it through the upstream CLI or a subprocess/uv run call, which is mere aggregation. Do not import upstream classes into Hermes itself.
Install (One-Time)
Run this through the terminal tool. It creates a cache directory, clones the upstream repository shallowly if it is not already present, and syncs dependencies with uv.
mkdir -p ~/.hermes/cache/darwinian-evolver && cd ~/.hermes/cache/darwinian-evolver
[ -d darwinian_evolver ] || git clone --depth 1 https://github.com/imbue-ai/darwinian_evolver.git
cd darwinian_evolver && uv sync
Then verify the install:
cd ~/.hermes/cache/darwinian-evolver/darwinian_evolver \
&& uv run darwinian_evolver --help | head -5
If the help text appears, the CLI is wired up. If it does not, the most common cause is a missing or broken uv environment, so re-run uv sync inside the cloned repository.
Quick Start: The Built-In Parrot Example
The upstream tool ships a tiny parrot problem that serves as a smoke test. It requires ANTHROPIC_API_KEY because it goes through the CLI.
cd ~/.hermes/cache/darwinian-evolver/darwinian_evolver
uv run darwinian_evolver parrot \
--num_iterations 2 \
--num_parents_per_iteration 2 \
--mutator_concurrency 2 --evaluator_concurrency 2 \
--output_dir ~/.hermes/cache/scratch/parrot_demo
The run writes two things. First, ~/.hermes/cache/scratch/parrot_demo/snapshots/iteration_N.pkl, a pickled population for each iteration. Second, a per-iteration JSON log inside ~/.hermes/cache/scratch/parrot_demo/, whose path is printed at the end of the run.
To see the evolutionary tree, open ~/.hermes/cache/darwinian-evolver/darwinian_evolver/darwinian_evolver/lineage_visualizer.html in a browser and load the JSON log.
Quick Start: OpenRouter Driver (No Anthropic Key)
If you do not have an Anthropic key, the skill ships scripts/parrot_openrouter.py. It runs the same parrot problem, but the LLM call goes through OpenRouter, so any provider works.
# From wherever the skill is installed:
SKILL_DIR=~/.hermes/skills/research/darwinian-evolver
DE_DIR=~/.hermes/cache/darwinian-evolver/darwinian_evolver
cd "$DE_DIR" && \
EVOLVER_MODEL='openai/gpt-4o-mini' \
uv run --with openai python "$SKILL_DIR/scripts/parrot_openrouter.py" \
--num_iterations 3 --num_parents_per_iteration 2 \
--output_dir ~/.hermes/cache/scratch/parrot_or
Inspect the result with scripts/show_snapshot.py:
uv run --with openai python "$SKILL_DIR/scripts/show_snapshot.py" \
~/.hermes/cache/scratch/parrot_or/snapshots/iteration_3.pkl
Expected output: 7 evolved prompt templates ranked by score, with the best landing around 0.6, 0.8 (the seed Say {{ phrase }} scored 0.000).
Defining a Custom Problem
The skill ships templates/custom_problem_template.py. Copy it, edit it, run it. Three things you must define.
Organism
A Pydantic BaseModel subclass holding the artifact being evolved. Depending on your target, that might be a prompt_template: str, a regex_pattern: str, a sql_query: str, or a code_block: str. Add a run(*args) method that exercises it.
Evaluator
A .evaluate(organism) -> EvaluationResult(score=..., trainable_failure_cases=[...], holdout_failure_cases=[...], is_viable=True) method. Four fields matter.
scoreis in[0, 1]. Higher is better.trainable_failure_casesis what the mutator sees. Include enough context (input, expected, actual) for the LLM to diagnose.holdout_failure_casesis kept out of the mutator's view. Use these to detect overfitting.is_viable=Trueunless the organism is completely broken (raises, returns None, etc.). A 0-score viable organism is fine. It just gets down-weighted in parent selection.
Mutator
A .mutate(organism, failure_cases, learning_log_entries) -> list[Organism] method. Typically you build an LLM prompt that includes the current organism, a failure case, and an ask to propose a fix. Then you parse the LLM's response and return a new Organism. Return [] on parse failure. The loop handles it.
Once those three are defined, write a driver script that wires Problem(initial_organism, evaluator, [mutators]) into EvolveProblemLoop and iterates over loop.run(num_iterations=N). The shipped scripts/parrot_openrouter.py is the reference.
Hyperparameters That Actually Matter
| flag | default | when to change |
|---|---|---|
--num_iterations | 5 | bump to 10, 20 once you trust the evaluator |
--num_parents_per_iteration | 4 | drop to 2 for cheap exploration |
--mutator_concurrency | 10 | drop to 2, 4 to avoid rate limits |
--evaluator_concurrency | 10 | same; evaluator hits the LLM too |
--batch_size | 1 | raise to 3, 5 once your mutator handles multiple failures |
--verify_mutations | off | turn on once mutator is wasteful (>10× cost saving on later runs per Imbue) |
--midpoint_score | p75 | leave alone unless scores cluster |
--sharpness | 10 | leave alone |
Pitfalls
Initial organism must be viable, setis_viable=Truein yourEvaluationResulteven on a 0-score seed. The loop refuses non-viable organisms because they imply the loop has nothing to evolve from.- Provider content filters kill runs. Azure-backed OpenRouter models reject phrases like "ignore previous instructions" with HTTP 400. Wrap the LLM call in
try/exceptand returnf"", the evolver will just score that organism 0 and move on. loop.run()is a generator, calling it doesn't run anything until you iterate. Usefor snap in loop.run(num_iterations=N):.- Snapshots are nested pickles.
iteration_N.pklcontains a dict withpopulation_snapshot(more pickled bytes). To unpickle you must have theOrganismclass importable under the same dotted path it was pickled at. - Concurrency defaults are aggressive. 10/10 will hit rate limits on most providers. Start with 2/2.
- CLI is hardcoded to Anthropic.
uv run darwinian_evolverreaches forANTHROPIC_API_KEYand uses Claude Sonnet. To use any other provider, write a driver likeparrot_openrouter.py. - AGPL. Never
from darwinian_evolver import ...inside Hermes core. Custom driver scripts under~/.hermes/skills/...are user-side and fine. - No PyPI package.
pip install darwinian-evolverwill pull the wrong thing. Always install from the GitHub repo.
Verification
After install and a parrot run, exit code 0 from this command is sufficient:
DE_DIR=~/.hermes/cache/darwinian-evolver/darwinian_evolver
ls "$DE_DIR/darwinian_evolver/lineage_visualizer.html" >/dev/null && \
cd "$DE_DIR" && uv run darwinian_evolver --help >/dev/null && \
echo "darwinian-evolver: OK"
When not to use it
Skip this skill when the optimization target is differentiable. Gradient descent or DSPy will be cheaper and more direct. Skip it when you only need to try two or three variants, because writing them by hand is faster than setting up a loop. Skip it when the fitness signal is purely subjective with no measurable criterion, because the loop has nothing to optimize against.
Limits and gotchas
The cost profile is worth planning for. A typical run is 50 to 500 LLM calls. On gpt-4o-mini that is pennies. On Claude Sonnet it can be a few dollars. The concurrency defaults of 10/10 will hit rate limits on most providers, so start at 2/2 and raise once you know the provider tolerates it.
The biggest conceptual trap is the scorer. If you do not have one, stop and define it first. That is the hard part of the work, and the evolver cannot do it for you. The second trap is viability: a seed that raises or returns None will be rejected outright, so mark it viable even at score 0.
Snapshots are nested pickles, which means you cannot unpickle them without the Organism class importable under the same dotted path it was pickled at. Keep your driver script stable if you plan to revisit old runs.
Finally, the CLI is hardcoded to Anthropic and reaches for ANTHROPIC_API_KEY with Claude Sonnet. For any other provider, write a driver like parrot_openrouter.py. And never pip install darwinian-evolver: there is no PyPI package, and that command will pull the wrong thing. Always install from the GitHub repo.
What pairs with it
The related skills are arxiv and jupyter-notebook. The first is useful when you want to read the research behind evolutionary prompt search, including the Darwin Gödel Machines and PromptBreeder papers linked from the upstream references. The second is useful when you want to prototype an evaluator or inspect snapshots interactively before wiring them into a driver.
For the underlying tool, see the Imbue research post, the ARC-AGI-2 results, and the imbue-ai/darwinian_evolver repository (AGPL-3.0).