Solution · Autonomous software delivery
The Dark Software Factory
Specs in, pull requests out, people only at the gates. A written specification becomes a merged pull request with no person in the loop except where the design puts one, and the system is built so that a run which did not do what it claimed cannot report success.
Live orchestrator v0.2.97, read from the running containers on 4 September 2026. Four production projects, one Compose stack.
What goes dark, and what does not
A software factory with the lights off changes one thing first, and it is not the software. It changes who does what. When the machines write the code, review it, test it, deploy it and document it, the person who used to do those things does not disappear; they move to the gates, and the gates become the job. The question a buyer should ask of any ‘autonomous’ pipeline is therefore not how clever its agents are but where its gates sit, what a gate can see when it opens, and whether the system can tell the truth about what it did on the far side of one. Most cannot, and the ones that cannot are not dark factories but dark rooms with an optimistic status light on the door.
This one turns a written specification into a merged pull request with no person in the loop except at the gates. A spec, a markdown document describing one unit of work, is handed to an orchestrator built on DBOS and Postgres, which dispatches a sequence of Claude Code agents, each with one role: one plans, one writes, one reviews, one audits for security, one tests, one deploys, one documents, and an eighth, off the main line, turns a plan into a dependency graph of specs and authors them. Each agent hands the next a structured JSON message with mandatory outcome fields, and every step is checkpointed, so a container restart resumes the run at the last completed step rather than paying for the same model call twice. Four projects run this way today, on isolated worker containers, from one Compose stack, and one of them is the quoting engine at eBatt.ai.
The hard part was never calling a model seven times. It was making the sequence durable, so that it survives restarts, redeploys, expired credentials and agents that fail in creative ways, and making it honest, so that a run which did not do what it claimed cannot report success. The second is harder than the first, and the system’s own documentation is candid about how often the first version of a signal said the opposite of what had happened. A DBOS workflow row reading SUCCESS means the workflow returned, which a halted spec also does; the on-disk marker is the only authority on outcome. A deployer once wrote ‘success’ while the sprint’s code never reached the remote, because the builder skipped the push and a reset discarded a correct local merge, so the orchestrator now checks the remote tree itself and trusts none of the agent’s proof fields. Four consecutive green rehearsal runs once reported writing a file that never existed. Each of those is a trap that looks like success, and each has a named check in front of it now.
I wrote the first version of this as a set of terminal panes and a polling loop, and it lost state every time the laptop slept. But the rebuild on a durable workflow engine did not remove the person from the system; it moved the person to a shorter list of better questions, and it made the machine answerable for the rest. That is the whole design in one sentence: the automation is allowed to be fast, and it is not allowed to be sure.
The pipeline, with the gates drawn as gates
Eight agents, two lanes and three locks. The security stage cannot be named as a successor by any agent, so nothing can reach it and nothing can skip it. The deployer’s claim of success is checked against the remote before the docs agent is allowed to run. Click any node for what it does and what it is not allowed to do.
One attempt, told honestly
The beats below are lifted from the orchestrator’s own dispatch log for 28 July 2026, with the project anonymised. The first twenty seconds are what a good run looks like. The last seven are what a false signal looked like on the same morning, and why the release that followed added an attempt number to every idempotency key.
Live demo
Lights off. Gates on.
One real attempt from the orchestrator's own log: eight agents, one human gate, one check the agent cannot fake, and the honest coda where a cancelled worker kept running. Timestamps are real; the project is anonymised.
- DBOS
- Postgres
- Claude Code
- Docker Compose
- GitHub
- Cloudflare
- ntfy
- Honcho
agent.trace
Live tool calls
The evidence, including the failures
- 424 of 533
- merged eBatt.ai pull requests from factory branches, since 26 April 2026
- 141
- merged pull requests on the orchestrator itself, the last on 3 September
- 3,011
- structured handoffs across 255 specifications in the handoff store
- 444
- of those handoffs addressed to a human rather than the next agent
- 13
- named halt reasons, derived from the code, not from a grep
- 15 to 25 min
- wall-clock for a full run of the pipeline
The numbers above are counts I can point at, not estimates, and none of them is a promise about the next run. The orchestrator carries 39 documented API operations, nine scheduled monitors and a test suite that stood at 1,768 passing on 30 August 2026. Of the handoffs in the store, 2,522 ended in success, 54 failed, 63 were blocked and 72 were partial. Those last three numbers are the ones a brochure would leave out.
The failures are the part of the record worth reading, and the documentation keeps them rather than tidying them away. A workflow engine’s own status row can say SUCCESS for a run that halted, because a halt is a normal return; the on-disk marker is the authority and anything reading the engine alone reports halted specs as successes. Four consecutive green rehearsal runs once reported writing a file that never existed, which is why rehearsals are now said to prove the shape of a prompt and nothing about an agent’s behaviour. A deployer once wrote ‘success’ while the code never reached the remote, so the orchestrator now reads the remote’s tree for itself. On 28 July an attempt was dead in 86 seconds having never launched a model, because an idempotency key had no attempt number in it and a cancelled worker that was never told to stop landed its handoff into the wrong attempt. On 30 July a telemetry lane went silent for 53.67 hours and the dead-man switch, taking the maximum over three witnesses, could not see one of them die; the diagnosis records that this gap ‘would not page today’. And a configuration variable set in the environment file with no matching stanza in the compose file is a silent no-op that has bitten twice. Every one of those now has a named check in front of it. That is what an evidence section is for.
What still needs a person
The list is written down in the operating documents rather than implied by a diagram, and it is short enough to print. The watchdog that detects a stalled dispatch alerts but does not restart anything, after a false positive once cancelled a healthy pipeline and threw away six paid-for dispatches. Stranded workflows are detected and reported, not forked back to life. And there is no migration tool: tables create themselves with idempotent statements at boot, which works and cannot express a destructive change, so the first destructive change will need a real tool and a person holding it.
Deploy approval
The one place a person is required by default. A durable wait of up to twenty-four hours that survives an orchestrator restart, answered with a one-tap notification that carries a low-privilege token rather than the API key. A destructive database migration forces the gate on regardless of any appetite setting.
Reading the spec
In programme work an agent authors each specification from the plan and the facts harvested from specs already done. Nothing advances until a person has read the staged spec and approved it; a gate pending for a week costs nothing and cannot be stranded by a redeploy.
Plan revision
Every change to a programme's plan is operator-initiated, and there is no way to auto-apply one by construction. The gate is hard on.
Persistent disagreement
A reviewer may send work back to the builder twice. The third disagreement halts the run for a human read, not a fourth attempt.
Want a factory of your own?
The runtime is Apache 2.0 and self-hostable. What I bring is the eighteen months of failure record above, and the judgement about where your gates should sit.