DEV Community

Médéric Hurier (Fmind)
Médéric Hurier (Fmind)

Posted on • Originally published at fmind.Medium on

Agent Evolutions: Stop Guessing the Design — Evolve It

Most coding-agent tasks have one right answer hiding behind a workflow. “Add password sign-in.” “Fix the off-by-one in the pagination cursor.” “Refactor this handler to use the new client.” The job is to converge — pick the path, drive it to done, ship the diff. That is the world agent-levers lives in.

Some tasks aren’t shaped like that. “Which prompt wins?” “Which caching strategy is fastest?” “Which library shape is friendlier to call from a worker?” There is no single correct path. There are five plausible ones, the differences are not obvious from inspection, and the only honest way to pick is to measure. The shape of the work is divergent  — branch out, evaluate, keep what works.

I kept reaching for that second shape and finding nothing on the shelf. So I built agent-evolutions — a small set of Agent Skills that turn a coding agent into a budgeted genetic search over a design space, with verifiable scoring at every step.

This is the fourth post in a thread on the shared .agents/ ground, after supagents, agent-docs, and agent-levers. Same bet, different question: levers ask “did we build it right?”, evolutions ask “did we pick the right thing to build?”


Agent Evolutions: Genetic exploration of solution spaces, with verifiable scoring — https://github.com/fmind/agent-evolutions

The design-by-intuition tax

Three failure modes I keep watching teams pay for — including my own:

The five-minute eyeball test. Two prompts on the screen, both look reasonable. One person prefers the terser one; another prefers the structured one. The team picks whichever was authored by the loudest reviewer. Three weeks later a quiet regression appears on a slice of traffic nobody A/B’d against.

Taste vs. taste. “Which client shape should we expose — fluent builder or plain options bag?” Six people, six opinions, zero numbers. The argument lasts a week. Whatever ships is then defended for years because rolling it back is more expensive than living with it.

The benchmark that becomes a vote. “Which caching strategy is fastest?” Real, mechanical, measurable. Then someone runs one manual test, on one workload, and reports a winner. Nobody asks how sensitive the result is, nobody runs the loser to confirm, nobody bothers with a second seed.

These all share a common shape: a design question with multiple plausible answers and at least one mechanical measure — and we answered it with intuition anyway. The cost of guessing wrong is rarely visible at decision time. It shows up months later as a refactor.

What I wanted was a tool that made the right move — generate variants, measure, evolve, pick — cheaper than the wrong move. That is the gap agent-evolutions is aimed at.

Convergent and divergent loops, side by side

The clearest way I can frame the relationship to agent-levers is by the shape of the loop.

Levers is convergent. One workspace, one diff, one verifier set, plan → do → check → act in a sequential chain. The question being answered is how do we drive this single task to done. The whole machine is tuned to finish.

Evolutions is divergent, then convergent. N parallel workspaces (one per variant), N independent results, batched generations that learn from the previous batch, a final pick at the end. The question is which of several plausible designs measures best. The machine is tuned to explore first, then collapse.

Both loops live on the same .agents/ ground, both use file-based state, both let any compliant coding agent drive them. They are not competitors — they are complementary tools for different question shapes. Most non-trivial work, in my experience, is mostly levers and occasionally evolutions. The trick is knowing which kind of question you’re holding.

Genetic search, not random sampling

The first thing people picture when I say “spawn variants in parallel and pick the best” is brute-force Best-of-N: fan out thirty prompt mutations, score them, keep the top one. That is sampling. It is blind, and it scales badly — doubling the budget barely improves the answer because every sample is drawn from the same flat distribution.

Agent-evolutions runs a genetic loop instead:

  • Generation 1 seeds a small, diverse set of hypotheses — qualitatively different approaches, not minor variants of one idea.
  • Generation 2+ mutates the top survivors, crosses pairs over, and injects a small share of fresh exploration variants to avoid premature collapse.
  • Batches are sized to budget.parallel , deliberately, so the loop can learn between batches instead of fanning out blindly.
  • Stopping is automatic: budget cap, wall clock, score plateau across N generations, or no survivors.

Each generation conditions on what worked in the previous one. That is the qualitative difference — and the reason a small directed search routinely beats a much larger random one. The skill’s documented optimize cli startup walkthrough finishes in 24 evaluated variants across 4 generations, with the winner from generation 2 — not generation 1.

Three commands, three phases

The whole surface area is three slash commands, one per phase:

  • Capture  — /new-agent-evolution discusses the brief (objective, gates, rubric, scope, budget) and commits it when you agree.</li> <li><strong>Run</strong>  — /run-agent-evolution <id> plans a batch, spawns variants in parallel, ingests each result.json, scores, evolves, and stops on a condition.</li> <li><strong>Apply</strong>  — /apply-agent-evolution <id> diffs the winner workspace against the live repo, confirms with you, copies it in, and optionally re-runs gates against the repo.</li> </ul> <p>Diff winner workspace vs the live repo → confirm → copy in → optionally re-run gates against the repo.</p> <p>The state machine is the one design choice I find myself most pleased with. There is no step counter, no pause flag, no “current phase” field. Phase is <strong>derived from the presence of fields</strong> in evolution.yaml:</p> <ul> <li>empty variants → <strong>ready</strong> to run</li> <li>has variants, no winner → <strong>running</strong></li> <li>has winner, no applied → <strong>evaluated</strong></li> <li>has applied → <strong>done</strong></li> </ul> <p>Each skill reads the yaml on entry, infers what phase the evolution is in, and refuses to run outside its own. Capture won’t recreate a brief that already exists. Run won’t re-rank a finished evolution. Apply won’t run without a winner. The skills are stateless; the disk is the source of truth. Crash mid-batch, restart, and the next invocation picks up exactly where the files left off.</p> <h3> <a name="the-contracts-that-make-the-scoring-honest" href="#the-contracts-that-make-the-scoring-honest" class="anchor"> </a> The contracts that make the scoring honest </h3> <p>A genetic loop with sloppy scoring is just an expensive random walk. Agent-evolutions leans hard on three contracts that keep selection grounded.</p> <p><strong>Gates are binary.</strong> Each gate is a shell command. Exit 0 means pass, anything else means fail. A variant that fails any gate is <em>excluded from ranking entirely</em> — no partial credit, no negotiation. Gates are how the framework refuses to compare “fast but broken” against “correct and slow”.</p> <p><strong>The rubric is numeric.</strong> Each rubric axis has a direction (minimize or maximize) and a weight. The composite score is a rank-normalized weighted mean across axes — robust to outliers, well-defined under partial data, and crucially <strong>recomputed on read by the run skill, never persisted</strong> in the yaml. The yaml stores raw rubric values; the leaderboard view derives from them. You cannot accidentally enshrine a stale score because there is no stored score to enshrine.</p> <p><strong>Criteria are frozen up front.</strong> This is the load-bearing rule. Gates and rubric axes are agreed during the Capture phase and then locked. The run skill refuses to add a new rubric axis after results come in. Without that rule, the evaluator silently turns into a rationalization engine — adding the axis on which the variant the agent likes anyway happens to win.</p> <p>The unstated cousin of these contracts: no silent retries, no narrative scoring. A failing gate stays failed. A missing measurement is missing, not interpolated. The leaderboard is allowed to be ugly.</p> <h3> <a name="the-filebased-subagent-contract" href="#the-filebased-subagent-contract" class="anchor"> </a> The file-based sub-agent contract </h3> <p>Each variant runs as its own sub-agent, in its own workspace (a git worktree by default), starting from HEAD. The contract between the parent and each child is not chat — it is a file.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/my1vunokqhl3xrdf98ma.png"/><br> <em>Relationship between Agent Evolutions Concepts</em></p> <p>Every variant sub-agent writes variants/v<n>/result.json, validated against a <a href="https://github.com/fmind/agent-evolutions/blob/main/result.schema.json">JSON Schema</a>. The parent reads files, not chat output. Four properties fall out of that decision:</p> <ol> <li><strong>Crash-survivable.</strong> A child that finishes its work before the parent gets a chance to read its reply still leaves its result on disk. Partial batches don’t disappear.</li> <li><strong>Audit trail.</strong> Every variant’s exact result lives in git. You can re-rank a historical evolution from its files alone, weeks later.</li> <li><strong>No JSON-in-chat parsing.</strong> The classic failure mode where a sub-agent’s reply is “JSON-ish” — with a friendly paragraph in front, or three backticks, or a stray comma — never enters the pipeline. The schema validates a file.</li> <li><strong>Parallelism stays clean.</strong> Six sub-agents writing to six different files do not pollute one shared transcript. No interleaved thinking, no merge step.</li> </ol> <p>I would not bother spelling this out if I had not lost an afternoon, more than once, to the JSON-in-chat trap on a previous prototype. File-based sub-agent I/O is the kind of detail that looks pedantic on slide one and saves the whole loop by slide four.</p> <h3> <a name="a-walkthrough  optimize-cli-startup" href="#a-walkthrough  optimize-cli-startup" class="anchor"> </a> A walkthrough — optimize cli startup </h3> <p>The shipped example trims a Node.js CLI’s cold-start time on — version. The brief pins one gate set (build green, tests green) and one rubric axis (median of five hyperfine runs, minimize).<br> </p> <div class="highlight"><pre class="highlight console"><code><span class="gp">$</span><span class="w"> </span>/new-agent-evolution <span class="s2">"optimize cli startup"</span> <span class="go"> → discusses objective, gates, rubric, scope, budget → on "looks good", commits .agents/evolutions/1-optimize_cli_startup/ </span><span class="gp">$</span><span class="w"> </span>/run-agent-evolution 1 <span class="go"> → gen 1: seeds 6 diverse hypotheses — lazy-load, build-time inline, plugin-registry map, polyfill strip, prebundled arg-parser, dotenv short-circuit </span><span class="gp"> → spawns 6 sub-agents in parallel;</span><span class="w"> </span>each materializes a worktree, <span class="go"> implements its variant, runs gates, writes result.json → ranks: v2 (build-time inline) → 142.0 ms · v1 (lazy-load) → 184.2 ms v4 fails G2 (test regression) → excluded → gen 2: mutates v2 → v7 (v2 + tree-shake help chunk), mutates v1 → v8, crosses v2×v3, adds 1 explore → ... → plateau at gen 4 — top score unchanged across 2 generations </span><span class="gp"> → stops at 24/30 evaluated;</span><span class="w"> </span>winner v7 at 98.6 ms <span class="go"> </span><span class="gp">$</span><span class="w"> </span>/apply-agent-evolution 1 <span class="go"> → diffs variants/v7/workspace vs live src/, tsdown.config.ts, package.json → "Confirm to apply, or reply abort" → "looks good" </span><span class="gp"> → copies files in;</span><span class="w"> </span>re-runs G1 + G2 against the live repo<span class="p">;</span> both green </code></pre></div> <p></p> <p>The moment I find the most instructive is the winner. v7 came from generation 2 — it is <em>v2 plus a tree-shake tweak that only made sense once v2 had won the first round</em>. Nobody on the team would have written v7 from scratch in generation 1. Evolution found it because the loop conditioned on v2’s win before composing the next batch. That is the entire pitch of genetic search over sampling, in one trace.</p> <h3> <a name="where-this-skill-set-should-not-run" href="#where-this-skill-set-should-not-run" class="anchor"> </a> Where this skill set should not run </h3> <p>Three patterns where I deliberately do not reach for evolutions, and the skill refuses to seed a brief when it spots them:</p> <ul> <li><strong>The task is a one-line bug fix, rename, or doc edit.</strong> Just do it. The overhead of a genetic loop is paid in seconds; the task is paid in seconds. The ratio is wrong.</li> <li><strong>There is no mechanical measure — not even a proxy.</strong> “Make this prose more engaging” has no shell exit and no number. Use a normal review.</li> <li><strong>The user has already decided the design.</strong> “Implement X.” is a levers job, not an evolutions job. Don’t manufacture a search space just to use the tool.</li> </ul> <p>If a brief tries to start without a gate or a rubric axis, the Capture skill pushes back: <em>“we need at least one mechanical check before variants make sense.”</em> Either we co-author one, or the evolution doesn’t get a folder. That refusal is on purpose — it is the load-bearing reason variants ranking <em>means</em> something at the end.</p> <h3> <a name="the-leverage-axis" href="#the-leverage-axis" class="anchor"> </a> The leverage axis </h3> <p>Agent-levers makes one hour of human attention move more — clearer briefs, typed verifiers, lessons that compound, the same fulcrum logic <a href="https://fmind.medium.com/agent-levers-a-plan-do-check-act-loop-that-makes-coding-agents-finish-what-they-start">I wrote about before</a>. The asymmetry is <em>human effort in, shipped work out</em>.</p> <p>Agent-evolutions sits on a different axis. The input is not human attention, it is <em>compute</em>: six or twelve or twenty-four sub-agents running in parallel, each spending tokens on a variant of the same design question. The output is a defensible answer to <em>which design to ship</em>. The fulcrum is the same shape — small effort on one end multiplies into directed work on the other — but the lever is bolted to a different wall.</p> <p>Most teams, today, are spending compute the way a 1990s team spent CPU time on a build: cautiously, one job at a time. The price has dropped, the parallelism has arrived, and the workflows have not caught up. Genetic search on coding tasks is one of the places that gap is widest.</p> <p>The practical nudge I’ll leave you with: next time a <em>which design wins?</em> thread starts on Slack, count the design points raised against the design points measured. If the ratio is wildly skewed toward opinion — and it usually is — that is the shape of a question agent-evolutions was built to swallow.</p> <h3> <a name="the-three-install-paths-mirror-the-three-coding-agents" href="#the-three-install-paths-mirror-the-three-coding-agents" class="anchor"> </a> The three install paths mirror the three coding agents: </h3> <p></p> <div class="highlight"><pre class="highlight shell"><code><span class="c"># Claude Code</span> /plugin marketplace add fmind/agent-evolutions /plugin <span class="nb">install </span>agent-evolutions@agent-evolutions <span class="c"># Gemini CLI</span> gemini extensions <span class="nb">install </span>fmind/agent-evolutions <span class="c"># Antigravity CLI</span> agy plugin <span class="nb">install </span>https://github.com/fmind/agent-evolutions <span class="c"># GitHub Copilot</span> copilot plugin marketplace add fmind/agent-evolutions copilot plugin <span class="nb">install </span>agent-evolutions@agent-evolutions </code></pre></div> <p></p> <p>To run these skills in <a href="https://opencode.ai/">OpenCode</a>, place or clone the skill directories under <code>.agents/skills/</code> (for project scope) or <code>~/.agents/skills/</code> (for global scope).</p> <p>Then, inside a project:<br> </p> <div class="highlight"><pre class="highlight shell"><code>/new-agent-evolution <title> <span class="c"># capture the brief (Session 1)</span> /run-agent-evolution <<span class="nb">id</span><span class="o">></span> <span class="c"># drive the genetic loop (Session 2)</span> /apply-agent-evolution <<span class="nb">id</span><span class="o">></span> <span class="c"># land the winner (Session 3)</span> </code></pre></div> <p></p> <p>The repository is at <a href="https://github.com/fmind/agent-evolutions">github.com/fmind/agent-evolutions</a>. The <a href="https://github.com/fmind/agent-evolutions/tree/main/examples/evolutions">examples/evolutions/</a> directory ships the full optimize cli startup walkthrough — brief, variants, scored leaderboard, applied diff — so you can read what the artifacts look like before running anything yourself.</p> <p>Pick a real question. Give it gates and a rubric. Let the loop run while you make coffee. The answer it comes back with will not be the one you would have written on a whiteboard — and that, more often than not, is the point.</p>

Top comments (0)