The Software Factory
Is a Verification Problem

Everyone is talking about agents that write, test, and ship code with no human in the loop. The interesting engineering isn’t in the agents. It’s in everything that decides whether their output is allowed to exist.

11 min readAI engineering
A three-stage pipeline where every generated test passed but a property check blocked the change.Spec stage1/1Build stage2/3Verify stage1/3Read the ticketUser taskWrite the changeAgent taskGenerate 214 testsAgent taskUpdate the docsAgent taskRun test suitepassed214 of 214refund ≤ chargeblockedinvariantHuman reviewRequires approval
Fig. 1  Every test passed. The change was still blocked.
1.0On this page
  1. 1.1An old idea with a new workforce
  2. 1.2Generation is mostly solved. Trust isn’t.
  3. 1.3Why “just write tests” isn’t enough
  4. 1.4Anatomy of a judgment layer
  5. 1.5The loop, concretely
  6. 1.6What it actually costs
  7. 1.7Where factories fail
  8. 1.8What changes for engineers
  9. 1.9If I were starting Monday
  10. 1.10The takeaway

Sometime this year, “software factory” stopped being a metaphor from a 1980s management book and became something teams say they run. The pitch goes like this: specs go in one end, working and deployed software comes out the other, and the people in the middle stop writing code. In the most aggressive versions, they stop reading it too.

A lot of my work has been on the unglamorous side of shipping software: build systems, CI pipelines, flaky tests, and the telemetry that tells you why a build took forty minutes instead of four. From that seat, most of the software factory conversation is aimed at the wrong component. People argue about which agent writes the best code. In a factory, that is the least interesting question.

The interesting question is this: when nobody reads the diff, what decides whether it ships?

This post tries to answer that concretely. It covers what a software factory actually is, why verification is the whole game, which techniques seem to work, how they fail, and what I would build first if I were starting one on Monday.

An old idea with a new workforce

The term is old. Robert Bemer was writing about software factories in the late 1960s, and Hitachi opened a facility it called a software works in 1969. Through the 70s and 80s, Japanese firms like Toshiba and NEC ran factory-style programming shops built on standardized processes, reusable components, and heavy measurement. Michael Cusumano documented them in Japan’s Software Factories (1991).

That generation got real results on predictable, repetitive work and struggled with everything else. The factory could stamp out the hundredth variation of a banking module. It could not invent the first one. Keep that in mind, because it comes back later.

The 2026 version swaps out the workforce. Instead of hundreds of programmers following a standard process, a fleet of coding agents runs inside a loop. The clearest public description came from StrongDM’s AI team in February. They call it non-interactive development: specs and scenarios drive agents that write code, run validation harnesses, and converge on a working result without a human reviewing it. Dan Shapiro’s “five levels” framing puts this at the top of a ladder that starts at fancy autocomplete. Factory.ai and others now sell products under the same banner.

Strip away the branding, and every serious version has the same shape:

  • Intent goes in. A spec, a ticket, a bug report, a screenshot, or an existing codebase.
  • Agents produce candidates. Code, tests, config, migrations.
  • Something judges the candidates. Tests, scenario runs, evals, static checks, canaries.
  • Feedback loops back. Failures become context for the next attempt.
  • Something decides to ship. Merge, deploy, or roll back.

The agents are one box out of five. The other four are infrastructure, and that infrastructure is where the factory actually lives.

Generation is mostly solved. Trust isn’t.

Two years ago, the bottleneck was generation. The question was whether a model could produce a coherent change across ten files without breaking imports. For a large and growing class of tasks, the answer is now yes, and it gets cheaper every quarter.

When generation is cheap, the economics of software flip. On a normal team, writing code is expensive and checking it is comparatively cheap: a reviewer reads in twenty minutes a diff that took two days to write. In a factory, writing is nearly free, and you can produce fifty candidates before lunch. Checking becomes the scarce resource. Human review doesn’t scale to fifty diffs, so you either automate trust or you don’t have a factory. What you have instead is a very fast way to create a review backlog.

That reframing changes what good engineering means here. The factory’s real product isn’t code. It’s the judgment layer: the machinery that can say, with evidence, that this change does what we wanted and doesn’t break what we had. Build that well and the agents become interchangeable. Build it badly and better agents just fail faster.

Why “just write tests” isn’t enough

The obvious answer is tests: have the agent write them, run them, and ship on green.

The problem is that when the same system writes both the implementation and the tests, green only means the code agrees with itself. It doesn’t mean the code agrees with you. An agent under pressure to pass will find the cheapest route to green, and the cheapest route is often to weaken the check rather than fix the behavior. It loosens an assertion, mocks out the thing that’s failing, or special-cases the test input. This isn’t malice. It’s what optimization does, and anyone who has trained a model has seen it. It’s Goodhart’s law with a commit history.

StrongDM’s answer borrows directly from machine learning: treat scenarios like a holdout set. A scenario describes end-to-end behavior a user cares about, and it lives outside the agents’ reach. Agents can’t read or edit scenarios, so they can’t overfit to them. Success stops meaning “the tests pass” and starts meaning an estimate of how often realistic paths through the system actually satisfy a user.

If you’ve done ML evaluation, this feels obvious in hindsight, which is usually a sign it’s right. A factory is an optimizer, and the scenario suite is its validation set. Leak the validation set and you’re only measuring memorization.

Anatomy of a judgment layer

Here is how I’d break down the parts, roughly in order of how much they matter.

1. A spec a machine can be held to

Most specs are written for humans, who fill the gaps with judgment. Agents fill gaps too, but they do it confidently and differently on every run. A factory spec needs to say what must be true rather than how to build it, and it needs to be checkable. “Users can reset their password” is a wish. “A reset link expires after 30 minutes, a used link returns 410, and three failed attempts lock the account for 15 minutes” is something you can generate scenarios from.

The useful discipline: if you can’t write a scenario for a requirement, the requirement isn’t finished.

2. Hidden scenarios

Scenarios are the holdout set. A few practical rules:

  • Store them where agents physically can’t read them. Use a separate repo and separate credentials, and have the harness run them outside the agent’s sandbox.
  • Write them in terms of observable behavior. That means requests and responses, files on disk, and rows in a table, never internal function names. If a scenario breaks on a refactor that didn’t change behavior, it’s testing the wrong thing.
  • Grade with code wherever you can. Use an LLM judge only for genuinely fuzzy behavior, such as whether an error message is helpful, and calibrate that judge against human labels before you trust it.
  • Grow them continuously. Every escaped bug becomes a new scenario. Over time the suite becomes the most valuable artifact in the system, worth more than the code it checks.

3. A world for the code to run in

You can’t run thousands of scenarios against real payment, identity, and messaging APIs. You’ll get rate-limited, billed, or banned, and you can’t test the failure modes those services rarely show you.

StrongDM’s approach is what they call a Digital Twin Universe: behavioral clones of the third-party services their software depends on. The idea is old (fakes, stubs, contract tests), but the scale is new. When generation is cheap, building a high-fidelity fake of an API becomes a job you can hand to the factory itself, verified against recorded real traffic.

The underrated benefit is that twins let you inject failures on demand: timeouts, 429s, partial writes, out-of-order webhooks. Real services hand you those at 3 a.m. A twin hands them to you on every run.

4. Sensors you can believe

This part comes from my own scar tissue. I’ve spent a lot of time inside CI for large distributed Bazel builds, including building an LLM-based triage tool that reads failure logs and historical fixes to suggest likely root causes. The biggest lesson: a flaky test is a lying sensor, and a factory is only as honest as its sensors.

A human treats a flaky test as an annoyance: rerun it, shrug, move on. A factory treats it as signal. Suppose a test fails 3% of the time for reasons that have nothing to do with the change. Across thousands of agent runs, it sends agents chasing ghosts, “fixing” code that wasn’t broken, and occasionally “fixing” the test into uselessness. Now multiply that by every other source of nondeterminism in the build: unpinned dependencies, shared caches, network calls inside tests, wall-clock time.

So before you add agents, make the build hermetic and the tests deterministic, and track flakiness as a first-class metric. It’s boring work. It’s also the difference between a factory that converges and one that oscillates.

5. A ship decision with a limited blast radius

Even with good scenarios, you’ll ship bugs. The factory needs the same production machinery a careful human team uses, tuned for higher volume: canary deploys, health-checked rollouts, automatic rollback, and content-addressed artifacts, so that rolling back means flipping a pointer instead of rebuilding. I built a small bare-metal control plane as a side project mostly to understand this layer. The most useful test I ran was a deliberately broken release, to prove the canary caught it and the fleet rolled itself back. Run that test early. A factory that can’t undo its mistakes cheaply shouldn’t be allowed to make them quickly.

The loop, concretely

Here is a minimal version of the control loop, stripped down to the decisions that matter:

Terminal window
def run_factory(spec, budget):
context = load_context(spec) # repo, docs, prior failures
for attempt in range(budget.max_attempts):
candidate = agent.propose(spec, context)
# Cheap gates first: fail fast, spend little
if not static_checks(candidate): # build, types, lint
context.add_failure("static", candidate)
continue
# Agent-visible tests: useful signal, not proof
visible = run_tests(candidate, suite="visible")
if not visible.passed:
context.add_failure("tests", visible)
continue
# Hidden scenarios: the only result that can ship
score = harness.run_hidden(candidate, twins=digital_twins)
if score.satisfaction >= spec.threshold and not score.regressions:
return deploy_with_canary(candidate)
# Feed back the symptom, never the hidden scenario itself
context.add_failure("scenarios", score.summary())
if budget.spent() > budget.limit:
break
return escalate_to_human(spec, context)

Three details matter more than they look:

  1. Gates are ordered by cost. Type-checking costs pennies. A full scenario run against a dozen twins does not. Most candidates should die cheaply.
  2. Hidden failures are summarized, not leaked. If you paste the failing scenario back into the agent’s context, it isn’t hidden anymore. You’ve turned your holdout set into training data, one failure at a time. Return the category and the symptom, not the test.
  3. The loop has a budget and an exit. Without a cap, a factory facing an impossible spec will burn tokens forever. Escalating to a human isn’t a failure mode. It’s a designed output.

What it actually costs

StrongDM offers a provocative benchmark: if you aren’t spending around $1,000 on tokens per engineer per day, your factory has room to grow. Whatever you think of the number, the direction is right. Factories trade compute for human attention, and they burn a lot of it.

That makes cost an engineering concern, not a finance one. The levers are the same as for any CI system:

  • Fail cheap. Order gates by cost, as in the loop above.
  • Cache everything: builds, test results, twin state. A content-addressed build cache often does more for factory throughput than a smarter model.
  • Measure cost per accepted change, not cost per run. A cheap agent that needs nine attempts can lose to an expensive one that needs two.
  • Track attempts-to-converge over time. If it’s rising, your specs or your sensors are degrading, and the agents are paying for it.

Where factories fail

I’m optimistic about this direction, which is exactly why its failure modes deserve a plain list:

  • Spec drift. The factory faithfully builds the wrong thing. No loop fixes a bad spec. It just builds the bad spec faster.
  • Scenario rot. Hidden suites go stale while the product evolves, and the satisfaction score quietly stops meaning anything. Someone has to own the suite the way someone owns the database schema.
  • Evaluator capture. If an LLM judge grades fuzzy behavior and the same model family writes the code, they can share blind spots. Use a different model for judging, calibrate it against humans, and audit where they disagree.
  • Side effects. Agents with deploy credentials, database access, and outbound network access are a security surface. Scope credentials per task, run agents in sandboxes, and treat every external call as something a reviewer would need to approve.
  • Novel work. This is the same lesson as 1985. Factories are great at the hundredth variation and weak at the first. Architecture, product judgment, and deciding what a thing should even do still sit with people.
  • Lost understanding. If nobody reads the code, nobody builds a mental model of it. That’s survivable while the scenarios are excellent and the system is small. It gets scary during a 3 a.m. incident in a system no human has ever read. Budget time for people to read code on purpose. Review doesn’t demand it anymore, but operating something you don’t understand is a real risk.

What changes for engineers

The question I hear most is some version of “So what do engineers do now?” My honest answer is the same job, at a different layer.

The skills that matter more in a factory are mostly ones senior engineers already have: writing precise requirements, designing testable systems, knowing which invariants actually matter, building observability, and staying suspicious of green dashboards. Building a factory is a platform engineering problem involving build systems, CI, sandboxes, telemetry, and rollout safety. Running one is closer to ML engineering: datasets, holdout sets, evaluators, and drift.

What matters less is typing speed and memorizing APIs. That was never the hard part.

If I were starting Monday

I wouldn’t start with a factory. I’d start with a cell: one narrow, repetitive, well-specified class of change, such as dependency upgrades, new endpoints on an existing service, or data connectors generated from API docs. Then, in this order:

  1. Make the build hermetic and the tests deterministic. Measure flakiness and fix the worst offenders. No agents yet.
  2. Write ten hidden scenarios for that class of change, in behavioral terms, stored out of the agents’ reach.
  3. Build one twin for the external dependency that hurts most.
  4. Run agents in the loop with a human approving every merge, and log every case where the human disagreed with the harness. Those disagreements are your roadmap. Each one points to a scenario or a sensor you’re missing.
  5. Remove the human from the merge only after disagreement stays near zero across a few hundred changes, and only behind a canary with automatic rollback.

Then widen the cell. That’s the whole playbook, and none of it is glamorous. It’s the discipline good teams already apply to deterministic software, extended to a system with a non-deterministic worker in the middle.

The takeaway

“Software factory” sounds like a story about agents. It’s really a story about evidence. The teams that make it work won’t be the ones with the cleverest prompts. They’ll be the ones who can answer the oldest question in engineering for every change their system ships: how do you know it works?

If the answer is “the agent said so,” you don’t have a factory. You have a very confident intern with production access.


If you’re building one of these, or tried and watched it oscillate, I’d love to compare notes.

Keep reading

Three more posts to read next.