"Code must not be written by humans."
"Code must not be reviewed by humans."
Those are the two founding rules of the AI team at StrongDM, as reproduced by Simon Willison in February 2026. Willison found the second one the most interesting, and so did I. Every instinct I have from 15 years of shipping software says that unreviewed code is how incidents start.
And yet the idea won't go away. Coding agents now run for hours, open pull requests in parallel and fix their own failing tests. Somewhere between "autocomplete" and "nobody reads the code anymore" there is a line, and most teams are crossing it without deciding where it is.
That line has a name now. A dark factory is a software pipeline where AI agents write, test and ship code that no human has read. A light factory (Addy Osmani calls it a "lit" factory) is the same pipeline with humans kept at the points where judgment matters.
This post covers three things:
- What the dark factory vs. light factory distinction really is, and where the terms come from.
- Why the deciding factor is verification, not model intelligence.
- A practical way to decide, per loop and not per company, which work can run in the dark and which must stay lit.
Where the dark factory idea comes from
The name is borrowed from manufacturing. A "lights-out" factory runs with no people on the floor, so nobody needs the lights on. FANUC has been running robots that build robots this way for years, and Osmani's essay cites Xiaomi's 2024 facility as a recent example.
In software, the framing took off in early 2026:
| Source | Contribution |
|---|---|
| Dan Shapiro's five levels (Jan 2026) | A ladder of AI-assisted programming modelled on self-driving levels. The top rung is the "dark software factory". |
| StrongDM's software factory (Feb 2026) | A real team operating with "no human writes, no human reviews" rules, verified through hidden scenarios. |
| Addy Osmani, "Software Factories, Light and Dark" (Jul 2026) | Defines loop, harness and factory, and argues that each loop needs its own light switch. |
Osmani's vocabulary is the most useful one I have found, so I'll use it for the rest of this post:
- Loop: one agent doing one job repeatedly. Gather context, act, check, repeat until a condition is met.
- Harness: everything around the loop. Sandbox, tools, memory, and the gates that define "done".
- Factory: many harnessed loops fed by a work queue and drained through a review gate into production.
The dark vs. light question is only about that last gate. Who, or what, decides that a change is good enough to ship?
┌──────────── HARNESS ────────────┐
work queue ──▶ │ agent loop: context → act → check │ ──▶ REVIEW GATE ──▶ production
└──────────────────────────────────┘ │
light: a human approves
dark: only machines check
The real problem: generation is cheap, verification is not
Agents made writing code nearly free. They did not make understanding code any cheaper. That gap is what Osmani calls comprehension debt: the growing distance between how much code exists and how much any human still understands.
A dark factory takes on comprehension debt as fast as it can produce code, and the tests stay green the whole time. Nothing looks wrong until something breaks and nobody on the team knows how that part of the system works.
The failure modes are specific, and they are different on each side:
| Dark factory failure | Light factory failure | |
|---|---|---|
| What goes wrong | Plausible code with "inhuman" mistakes ships because every check passes | Humans become the bottleneck and rubber-stamp 40 PRs a day |
| How it shows up | Late, quiet incidents in code nobody has read | Review queues, slow delivery, reviewer fatigue |
| Root cause | Checks are weaker than the agent's ability to satisfy them | Review effort is spent on the wrong layer (lines instead of plans) |
| Cost | Painful manual debugging of an unfamiliar codebase | Throughput, and eventually the reviews stop being real |
That last row on the light side is the one people underestimate. A light factory where humans click "approve" without reading is a dark factory with extra latency. You get the risk of the dark version and the cost of the light one.
There is also a direct money cost on the dark side. StrongDM's benchmark, as Willison reports it, is that a factory should spend around $1,000 per human engineer per day on tokens. Willison points out this is about $20,000 per engineer per month, which turns the approach into a business-model question for most teams.
The deciding factor: can a machine verify this cheaply and honestly?
Shapiro's levels and most of the commentary around them point to the same conclusion: autonomy is gated by verification, not by how smart the model is. A loop can go dark only when its checks are:
- Cheap: they run on every change without anyone waiting.
- Fast: the agent gets feedback inside its own loop.
- Hard to fake: the agent can't make them pass without doing the actual work.
The third property is where most setups fail. If the same agent writes the code and the tests, a green suite proves very little. Agents are very good at satisfying whatever check you give them, including by weakening the check.
StrongDM's answer is worth studying because it targets exactly this. They keep end-to-end scenarios outside the codebase, where the coding agents can't see them, similar to a holdout set in model training. They test against a "Digital Twin Universe" of behavioural clones of third-party services. And instead of pass/fail, they measure "satisfaction": the share of scenario runs that likely satisfy a user.
You don't need their budget to apply the principle. A minimal version of a holdout gate looks like this:
# .github/workflows/holdout-gate.yml (illustrative)
name: holdout-scenarios
on: pull_request
jobs:
scenarios:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Scenarios live in a separate repo the coding agent has no access to
- uses: actions/checkout@v4
with:
repository: acme/billing-scenarios
token: ${{ secrets.SCENARIOS_READ_TOKEN }}
path: scenarios
- run: npm ci && npm run build
- run: npx playwright test --config scenarios/playwright.config.ts
The important part is not Playwright or GitHub Actions. It is the boundary: the agent that writes the code cannot read or edit the checks that judge it. Without that boundary, "dark" just means "unverified".
How to decide: a light switch per loop
The most practical idea in Osmani's essay is that going fully dark or fully light both fail, in different ways. The decision belongs to each loop, not to the organisation.
I find it helps to score each loop on four questions before giving it any autonomy:
| Question | Dark is reasonable when... | Keep it light when... |
|---|---|---|
| How is it verified? | Type checks, property tests, holdout scenarios, linters | "Looks right", or tests written by the same agent |
| What is the blast radius? | One file, one service, easy rollback | Auth, payments, data migrations, public APIs |
| How long does the decision live? | Days (a lint fix, a dependency bump) | A year or more (schema, architecture, contracts) |
| How reversible is it? |
git revert and redeploy |
Data loss, leaked secrets, customer emails sent |
Here is the same rubric as code, so it can live in your factory's router instead of in someone's head:
type Verification = "machine-hard-to-fake" | "machine-self-written" | "human-judgment";
type BlastRadius = "local" | "service" | "critical";
interface LoopProfile {
verification: Verification;
blastRadius: BlastRadius;
decisionLifetimeDays: number;
reversible: boolean;
}
type FactoryMode = "dark" | "light-at-plan" | "light-at-merge";
const LONG_LIVED_DECISION_DAYS = 365;
export function chooseFactoryMode(loop: LoopProfile): FactoryMode {
// Irreversible or critical work always gets a human before merge
if (!loop.reversible || loop.blastRadius === "critical") return "light-at-merge";
// Long-lived decisions need human judgment on the plan, not on every line
if (loop.decisionLifetimeDays >= LONG_LIVED_DECISION_DAYS) return "light-at-plan";
if (loop.verification !== "machine-hard-to-fake") return "light-at-merge";
return "dark";
}
Notice that there are two kinds of "light". This matters more than the dark/light split itself.
Light at merge is classic code review: a human reads the diff. It is expensive and doesn't scale to agent output volumes, so reserve it for the small set of changes where a mistake is truly costly.
Light at plan moves human judgment upstream. The agent writes a short plan (approach, files it will touch, risks, how it will verify), a human approves or redirects it, and then the implementation runs with machine checks. Reviewing a one-page plan is far cheaper than reviewing 2,000 generated lines, and it catches the mistakes that tests can't: building the wrong thing.
Putting it into practice
Let's walk through a hypothetical team running agents on a mid-sized SaaS product. These are the loops they might have, and where each one lands:
| Loop | Verification | Mode |
|---|---|---|
| Nightly: fix one lint warning, open one small PR | Linter + type check + existing tests | Dark |
| Patch-level dependency bumps | Lockfile diff + full test suite + holdout scenarios | Dark |
| New endpoint in an internal admin tool | Agent-written tests only | Light at merge |
| New billing feature | Holdout scenarios exist, but it touches money | Light at plan + merge |
| Database schema change | Migration dry run | Light at plan + merge |
| Changes to auth, sessions or secrets handling | Anything | Light at merge, always |
The pattern is that dark loops are small, boring and well-guarded. That is not a limitation; it is the point. Osmani cites Dex Horthy's rule of thumb that agents hold up for roughly three to ten steps and start losing the thread past twenty. Short loops with strong gates are what make darkness affordable.
The agent side of this also needs to be written down, because an agent won't infer where the lights are. I keep this kind of rule in CLAUDE.md so every session starts with it:
## Autonomy boundaries
- You may open PRs without asking for: lint fixes, patch-level dependency
bumps, test additions, docs. Keep each PR under 200 changed lines.
- Before writing code for anything else, produce a plan (approach, files,
risks, how you will verify it) and STOP for approval.
- Never modify: auth, session handling, payment flows, migrations, CI
workflows, or anything under /scenarios. Propose the change instead.
- Never weaken an existing test or assertion to make it pass.
The last line is there because it is the most common way an unattended loop "succeeds" while failing.
Finally, instrument the dark loops. If no human reads the code, humans must at least be able to see what the factory did: which loop produced which change, what checks ran, how often each loop escalates or gets reverted. A rising revert rate on a dark loop is the signal to turn its light back on.
Trade-offs and when the dark factory is the wrong choice
I'm sceptical of going fully dark on any system that people depend on, and I think the sceptics have the stronger argument today. But a blanket "never" is also wrong.
| Dark factory | Light factory | |
|---|---|---|
| Throughput | Limited by compute and budget | Limited by reviewer attention |
| Main risk | Comprehension debt, silent wrong behaviour | Rubber-stamp reviews, slow delivery |
| Upfront investment | Very high: holdout scenarios, service twins, observability | Moderate: plan templates, review discipline |
| Token cost | High (StrongDM's benchmark: ~$1,000/engineer/day) | Lower |
| Best fit | Well-specified, reversible, heavily tested work | Ambiguous, long-lived, high blast radius work |
Don't run a loop in the dark if any of these are true:
- You can't write the checks independently of the agent. If the only tests are the ones the agent wrote, you are trusting the agent to grade itself.
- The domain is regulated or safety-relevant. "No human reviewed this" is a hard answer to give an auditor.
- The system is a legacy codebase with weak tests. Agents amplify whatever is there. In a poorly documented system, they amplify the confusion.
- Nobody on the team could debug it. If the code breaks at 3 a.m., someone has to understand it. If that person doesn't exist, you are borrowing against comprehension you don't have.
And don't keep a loop light just out of habit. Asking senior engineers to line-review a dependency bump that a solid test suite already verified is how review stops being meaningful for the changes that need it.
FAQ
Is a dark factory the same as "vibe coding"?
No. Vibe coding skips verification. A serious dark factory replaces human review with heavier machine verification: hidden scenarios, service twins, conformance checks. The absence of review is only defensible when the verification is stronger than review would have been.
Who coined the terms?
"Dark factory" comes from lights-out manufacturing. In software, Dan Shapiro's five levels and StrongDM's published approach (both covered by Simon Willison) popularised it in early 2026. The light/dark pairing for software factories is laid out in Addy Osmani's July 2026 essay. The terms are still informal and people draw the lines differently.
Does a light factory mean reviewing every line the agent writes?
It shouldn't. The most effective place for human judgment is the plan and the architecture, before the code exists. Line-level review is for the small set of high-risk changes.
Can a team move from light to dark over time?
Yes, one loop at a time. When a loop has strong independent checks and a low revert rate over a sustained period, reduce the human gate. When the revert rate rises, add it back.
Does architecture still matter if agents write the code?
More than before. Clear module boundaries, good types, dependency injection and test seams are what make machine verification possible. Weak architecture forces you to keep everything light.
Takeaway
- The dark vs. light choice is about the review gate, not about how much code the agent writes.
- Autonomy is gated by verification. A loop can go dark only when its checks are cheap, fast and hard to fake.
- Separate the checker from the doer. Keep holdout scenarios where the coding agent can't read or edit them.
- Move human judgment upstream. Reviewing a plan is cheaper and catches more than reviewing generated lines.
- Decide per loop. Lint fixes can run in the dark; auth, payments and migrations stay lit.
- Watch the revert rate. It tells you when a dark loop needs its light back on.
Which loop in your team runs unwatched today, and what check do you trust to catch it when it's wrong? I'd like to hear it in the comments.
I'm Diogo Soares, a Staff AI Engineer working on AI agents and legacy modernization. If this was useful, follow me here on DEV.to, let's connect on LinkedIn, or check out diogosoares.dev.
Top comments (2)
the 'light at plan' split is the part most people skip, and it is the one that actually catches the expensive failures - building the wrong thing survives any amount of green tests, and a one page plan review catches it in two minutes. one thing i would add to the rubric: holdout scenarios rot too. ours silently stopped covering a flow after a rename and the gate stayed green because it was exercising a dead route, so the gate needs its own check that it still touches what it claims to. and a revert rate only means something against a baseline, otherwise a loop that reverts 2 percent forever looks the same as noise.
Thanks for this, really good points. Agreed on light at plan: green tests prove the code does what it says, not that it's the right thing to build.
The rotting holdout is a great catch. A gate that stays green while exercising a dead route is worse than no gate, because it looks like safety. Checking that the gate still touches what it claims to makes total sense.
And you're right about the revert rate: without a baseline it's just a number, not a signal.