Fitness-Driven Software Engineering

· Kate Meyer ·

11 min read Original article ↗

I am one of those engineers who find improving deployment strategies catnip. At a previous company I set up a system where we get a change from our machines to prod in under 15 minutes. That included reviews, packaging, full testing, smoke testing with production data, canary deployment, automatic rollbacks, etc. Most companies don’t seek that level of efficiency, but in the age of AI, they are discovering the bottleneck was never the code, but the friction of deploying it. There is a problem when an AI agent can write a fix in 4 seconds, but can only validate the fix in 4 days and get it to the customer for feedback in 4 weeks.

The Karpathy autoresearch loop showed how agents can write code at machine speed, but only in a sandbox. In contrast today's human-gated deployment pipeline was built to stop humans from breaking 2004-era Java monoliths. Fitness-Driven Engineering is a deployment architecture derived from first principles: minimize state-transition cost and complexity so iteration frequency can increase. This architecture, while faster for humans, is built for agents working at machine speed and unlocks the evolutionary dynamics needed for Recursive Self-Improvement.

A cited “best practice” for software deployment is the following:

  1. Wrap a change in a feature flag and test locally.

  2. Open a PR, get review/tests, merge to main.

  3. Branch main for release, build, test, and canary-deploy.

  4. Enable the flag, roll it out, and review metrics.

  5. Iterate if problems arise; otherwise remove the flag.

Unfortunately with this “best practice” for some teams it can take months to get a change out. Feature flags were made to reduce the failure blast radius, but they launder complexity and bring problems of their own, including:

  • Wrapping is manual, not always done, and rarely complete.

  • Flags become a shadow VCS with unbounded debt.

  • 2^N states make reasoning/testing impossible.

  • Three loops (PR, deploy, toggle) slow every change.

  • Flags reused over and over to bypass infrastructure overhead.

  • Some flags are permanently partially deployed or never removed.

  • The joke, but not joke: do you have a feature flag to remove a fully deployed feature flag?

  • The 2^N possible states turn database migrations and schema changes into high-stakes puzzles with no obvious safe path.

We evolved our way here. Code reviews, unit tests, reproducible builds, canary releases, each layer tried to reduce the cost of a change if it went wrong. Two metrics drove everything:

  1. Uptime: Deployments that bring down the site threaten revenue.

  2. Deployment frequency: Releases contain features; delayed releases decrease revenue and decreases understanding about customer usage.

Feature flags will always exist for business needs such as to delay a marketing campaign or regional laws. But when flipping a flag is faster than doing a release, pressure to wrap all changes in flags increases. This leads to Kate’s 15th software law:

Kate’s 15th software law: As deployment velocity approaches zero, reliance on runtime feature flags approaches infinity.

You could potentially engineer your way out of each problem such as designing a language with built-in revision control and concurrent phenotypes. But to find the actual solution, we need to return to first principles.

Every deployment is made up of three parts1:

  • Transition Cost (c): the cost of moving code from a developer’s mind to production execution. The literal CPU hours, Slack notifications, manual approvals to get a commit live, etc.

  • Complexity (K): the structural complexity, the number of microservices or interconnected state systems that a change could touch / break. The amount of manual effort to even use something like feature flags.

  • Frequency (f) how frequently changes go out.

A company releasing once per decade (f=0.1) will likely be outcompeted by one releasing yearly (f=1). The uptime and deployment frequency metrics map directly to c and f. Early Facebook iterated fast (f) because PHP was simple (K) and cheap to deploy (c) with hourly releases and automatic rollbacks.

Feature flags fail catastrophically: they increase c (flag-flipping, monitoring, cleanup) while masking K (burying 2^N complexity inside branches). The system appears to manage risk but accelerates internal cost, something only FANG companies can subsidize.

Engineers who improve the release process end up reducing c and K, improving f:

  • Before: A change waits a week for review because one person “has to” sign off. After: They wrote a test that exercises the condition, deleted the human gate, and the same change gets approved in twenty minutes.

  • Before: Flaky tests turn every CI run into a dice roll. After: They quarantined the flakes, fixed the root cause (a race in the test harness, not the code), and time from PR creation to merge drops by a week on average.

  • Before: “No Deploy Friday” became “only deploy Tuesday” became “batch for next month.” After: They added automatic rollback on error rate spike, deleted the policy, and found their incident rate didn’t change, but their feature velocity tripled.

  • Before: A dormant flag collided with a new change and caused a Sev-1. After: They set flags to auto-expire at 30 days; if no one extends it, the flag dies and the collision becomes impossible.

  • Before: Staging went down and fifty engineers went home early. After: They gave every engineer an ephemeral environment spun from their branch; “staging is down” became a non-event.

AI Agents can write code at machine speed. We need them to be able to deploy at machine speed too. From first principles we want to maximize delivery velocity while minimizing complexity and cost. What is the ideal version of these 3?

  1. Zero-Hysteresis Rollbacks (reduce c): A failed change simply ceases to exist. Zero cleanup, zero mutation, zero state pollution.

  2. Single-Variable Universes (reduce K): Test in absolute isolation. Exactly one variable in flight, not 2^N. No branching, no dead paths, no state debt.

  3. Single-Loop Iteration (increase f): Once a change is validated it is already in production. No separate PR, deployment, and flag-flipping cycles fragmenting f into discrete, expensive steps.

This is the architecture evolution already uses. In biology an organism expresses a variant; if it is fit, it propagates. If it is unfit, it dies. Organisms don’t validate in a simulation, but through increasing stages: can the DNA run? Can it find food? Can it grow? Then, can it mate?

Changes progress through ever-increasing exposure, from successfully building to responding to live traffic. Survival filters continuously monitors health and only successful changes persist and propagate.

The goal is to minimize cost and risk. Like most deployment systems, this means verifying the build, running tests, then using mirror traffic, then actual production traffic. Each step increases potential risk.

Here is where the Fitness-Driven Engineering diverges:

  • Promote on validation, not on schedule. Once a change passes pre-production validation, it doesn’t go in a queue to go out on a release date, it goes out immediately.

  • Deploy to minimal exposure. If the only fitness signal is “does not crash on start,” survival may require only a 1 instance and a ‘200 OK’ from the healthcheck endpoint, not deploying to 100% of production machines.

  • Leave successful changes alone. A passing change stays live at whatever fraction of deployment it earned even if this is only 0.1%. New deployments replace the oldest, so production becomes a continuously evolving fleet of experiments.

Fitness-Driven engineering does not shepherd releases. It collects signals to kill what should not exist, including binaries previously assumed fit. It can also be self-filtering: a binary that detects its own failure can self-select out before anything else acts.

Because every release must be measured for fitness, objectives are defined in a machine-evaluable form. Rather than deployment plumbing, they become the single source of truth for product requirements, replacing the scattered organizational memory of Jira, Slack, and OKRs. They should span the full lifecycle: build latency, test coverage, resource efficiency, correctness, and customer-facing behavior. More signals mean sharper selection. What counts as a signal is itself a design problem, but that’s a separate essay from the architecture that consumes them.

With this in place, a Red Queen can even be introduced: a co-evolving adversary that probes deployed binaries for novel failure modes such as security vulnerabilities, edge cases, resource exhaustion and records each discovery. For most this starts simple with fuzzing, something AI can’t hack around like it can with unit tests.

A change reproduces only after receiving enough positive signals. For example, if it is deemed okay it automatically merges into the main branch. Even if deployed to only 10% of machines before being tagged as fit, it eventually spreads to all of production as new changes incorporate and propagate it.

Newer, validated variants systematically replace the oldest changes. This bounds the half-life of any release without the latest changes, driving the population toward optimal fitness. This can even work without a centralized push: idle deployments check their own age and automatically die if too old forcing a replacement deployment.

Stateful components still need coordination, but there is no need to go back to release trains. There are many possible alternatives such as a candidate change declaring a prerequisite fleet state before it can be deployed.

Even if changes are merged into main, there is never a release from a branch called “main.” Deploying main is not only superfluous, but implies a gatekeeper that owns the release when every change is already owned.

Once we have no need for a central main branch release, we can explore more powerful paradigms. In evolutionary biology, Muller’s Ratchet shows that a single, asexual lineage is a genetic dead end, doomed to accumulate deleterious mutations until it collapses under its own entropy. Species survive through genetic recombination: beneficial mutations from different lineages combine and spread while bad changes are purged. Even if GitHub privileges one branch by default, Git itself has no special branches. The Linux kernel operates this way: module and distribution owners merge from each other, and no single branch is sacred.

For agents iterating at machine speed, we can take this further. At the simplest, new changes start from a branch based on two healthy branches running in production: start from Parent A, merge in Parent B.

On the Dwarkesh Patel podcast, Andrej Karpathy noted that LLMs currently have no equivalent to AlphaGo-style self-play, no innate mechanism for beneficial variations to recombine and compound. Without it, naive self-improvement loops inevitably suffer from model collapse: Muller’s Ratchet manifesting in weight space.

Karpathy’s own autoresearch loop illustrates the gap: it generates hundreds of experiments and retains winners, but nothing combines two independent winners into a third. It also validates against a fixed eval script rather than live signal, so it optimizes toward whatever the script rewards without a Red Queen. Autoresearch nails single-loop iteration, but misses recombination and adversarial pressure, the difference between fast and evolvable.

Fitness-Driven Engineering is required if you want to have evolvable software.

Once deployment cost is minimized, the system can modify, test, and deploy its own changes. If objective metrics live in a separate repository, an AI has explicit, measurable targets to optimize against.

Most discussions of Recursive Self-Improvement focus on models improving their own weights. This architecture can do that, but the mundane applications matter more. A new crash at 2am. Before the on-call engineer is notified, an AI writes a fix, deploys it to 10% of production, and verifies it survives. By 9am it had propagated to 50% of the fleet. The human reviews it over coffee. A human is no longer needed to shepherd a change through release.

When human signoff is required, it can happen at any point such as before promotion to live customer traffic. Once given, everything downstream still happens automatically.

Many repositories only need a few changes a month. The more time spent shepherding changes, the less time is free for work that matters. Human shepherding burns human attention and produces no fitness signal. Reduce the shepherding time, and the same engineer can respond to customer opportunities, sales opportunities, and the insights that only appear when you can iterate fast enough to discover what actually works.

FDE takes the small deployment team and puts them right in the middle of the organization. Product, engineering and everyone uses their tools to help define how the software should behave.

There are many reasonable ways to build this, but they will all have the same shape dictated by the math: minimize c, minimize K, maximize f.

Fitness-Driven Engineering isn’t a loop that can break or a loop that requires a human click ‘accept’ every 15 minutes. Instead code moves forward in a perpetual state of evolution, and the fitness suite acts as the automated pruning saw, removing bad implementations, dead paths, and silent regressions before they take root in production.

Fitness-Driven Engineering isn’t a loop that can break or a loop that requires a human click accept every 15 minutes. The human engineer’s job shifts from shepherding to constructing an aggressive, multi-layered fitness landscape. Specs, performance thresholds, security stress tests, and adversarial edge cases to guide the software to where it needs to be.

Discussion about this post

Ready for more?