· revised 24 Oct · Builders

An assembly line for applications

We did not set out to start a company. Millwright is what happened when we did it on purpose the second time.

A schematic of a generic system, drawn in white on black: four stations connected left to right, two decision gates between them, inputs feeding in from above, outputs below, and a return path running back along the top. The labels are not legible.

Paul called. We were not starting a company and we were not starting a product. He had a problem he could not get an answer to, and we were talking about it.

His half was a conviction with no solution. There has to be a number, he said. I just do not know what it is. Maybe there is a mat, something I can stand on. That part of the story, and the man it belongs to, is in Where do I stand?

The mat never got made. It was not the answer, but it was the right instinct, because it said the thing out loud. The distance is knowable. Somebody just has to work it out.

The other half came back the other way. If it is a number then it is mathematics, and if it is mathematics then it belongs in the thing already in his pocket. Put the mat in the phone. That is what the application became.

Then he took it to the course and told us what was wrong

The first prototype went to Paul and Paul went to play. He would come back with it. I would like it to do this. This does not work right, can you fine tune it.

He was using it in the real world every day. We were making the changes from two thousand miles away. That loop, a man on a course with a problem and a fix in the app a few days later, is how the app actually got built. Nearly everything in it arrived that way.

What we were doing without calling it anything

Here is the part that matters more than the golf.

Alejandro Acosta has spent fifteen years in operations and runs them globally for a technology company. That is a structured way of thinking and it does not switch off at five o'clock. There are rules and there are processes. Before you turn a system on you have to know what you expect from it, and you have to have decided what you will measure, because the data and the analytics and the metrics are not reporting. They are what decides whether the thing you deliver is any good.

He had also been circling a separate idea, about going out and consulting. Not in one industry. An operator who takes a problem and solves it, on experience.

Which is exactly what had been happening with Paul for months. He had a problem. The goal was to solve it. Neither of us called it anything at the time.

So we did it on purpose

Once the app took shape and it was clear we had something, the question stopped being about golf. If we can do this with one problem, we can do it with others. That is why there is a company at all.

And building a company meant bringing the structure with us. Not a new one. The same one, pointed at a new thing.

That structure is Millwright

Millwright is a repeatable process for taking an application from an idea to something a person can actually use. It is an assembly line in the plain sense of the words: a fixed order of stations, each with its own checks and balances, so that what comes off the end arrives in a known state instead of whatever state it happened to reach.

The idea is more than a century old and it is not ours. What is ours is the decision to apply it here, because software mostly does not get built this way. Software usually gets built by remembering.

What it buys is what operations always buys. The parts that are the same every time stop costing anything, and the things that used to go missing stop going missing.

The rest of this note is how it works, as it stood in the middle of October: the stations, the rules that hold them together, and what the line has already caught in itself. Each technical section ends with a short version in plain terms.

Ten stations

A job enters at S0 and the stations run in a fixed order. Each one reads what the stations before it wrote, and writes its own.

StationDoesWrites
S0 IntakeTurns a raw idea, in whatever form it arrives, into a structured brief: the problem, who it is for, what it is not, the constraints, and how we would know it workedbrief.json
S1 RequirementsWrites the product requirements from the brief and the house rulesprd.json
S2 FeasibilityNovelty, prior art, effort, running cost, and what could kill it. Loops back to S1 until the requirements survive it or the job is killedfeasibility.json
S3 House rulesSelects which of the lessons we have already learned bind this apprules-applied.json
S4 ScreensEvery screen, its states and its flows, including the empty and error statesscreens.json and images
S5 Brand and legalName, voice, color, type, icons, the privacy points, and the copy for every store fieldbrand.json, legal.json
S6 BuildWrites the app and its tests from the requirements and the screensthe app's repository
S7 Web appMakes it installable and able to work offline, runs gates 0 and 1, and puts it on a preview addressa live preview
S8 Store gatesGates 2 to 5, on the store track onlygate reports
S9 HandoverOne document: what was built, what was decided and why, what is unfinished, and what a person has to decidehandover.md

Only the front of the line loops. S1 and S2 argue with each other until the requirements hold or the job is killed. After S2, everything runs straight through.

Files in, files out

The line is not a conversation. Each station is an agent working from a versioned prompt, and the only thing it can see is the job's folder. Six rules make that hold.

RuleWhy
One job, one folderEverything about a job is in one place on disk. If a working session ends at S5, a fresh one reads the folder and resumes at S5. No session has to remember anything
Stations never talk to each otherEvery station reads files and writes files. Any one of them can be re-run, replaced, or moved to a different model without the others noticing
Every artifact is JSON with a readable twinThe JSON is what the next station reads. The markdown twin is what a person reads. Both are generated from the same source, so they cannot disagree
A schema closes a stageAn artifact that does not validate against its schema is a failed stage, not a judgment call. A station that cannot produce a valid artifact has failed rather than improvised
Provenance on every artifactWhich agent, which model, which prompt version, a hash of every input, and the time. This is what makes an output defensible afterwards
A staleness graphEach artifact names its inputs by hash. When the requirements change, everything built on the old version is marked stale, so nothing quietly inherits an answer to a question that moved

The runner does not think

The program that moves a job along the line calls no model. It creates the job folder, validates each artifact against its schema, records provenance and input hashes, writes the readable twin, and marks every downstream stage stale when an input moves. The judgment happens inside the stations. Deciding whether a station's output is acceptable does not.

That split is deliberate. Anything that can be deterministic is a script. Prompts are versioned files that are never edited in place, and the model is configuration rather than something written into a prompt, so changing either one does not change the line.

The runner has its own self test, which checks the contract rather than any one app. Six of its checks cover graduation and the preview alone, including one that proves a refused deploy leaves the preview exactly as it was. Measured on 13 October: the runner was a little over 3,000 lines with 18 commands, alongside 10 artifact schemas, 20 prompts and more than 240 house rules, and the self test ran 209 checks, all passing. Two days earlier it ran 12.

What we already learned, applied by rule

A house rule is one lesson, written as a file: what to do, and the evidence from the record it came from. The first set was extracted from our decision log, the written record of what had gone wrong and what we decided about it. By 12 October there were 229: 159 from the decision log, 61 from the checks inside our store gates, and 9 stated directly by Alejandro.

Extracting them surfaced something worth recording on its own. Five constraints we had treated as house rules all along had no written source anywhere in the decision log. They were instructions given in conversation and nothing more. They are now files whose evidence says plainly that they are standing instructions rather than lessons from an incident. If one of them is wrong, it is now wrong in one place instead of in every future build.

At S3 the line selects the rules that bind the app by scope: the universal rules, plus the rules for each platform the product will eventually run on. Every rule in scope has to appear in the job's record as either applied or excluded, and a stage where a rule appears in neither fails.

Scope matters more than it sounds. On the first job, recording the platforms the product would eventually run on, separately from where that run would stop, brought 36 iOS and Android rules into scope that four earlier runs had never read. The first thing they caught was every text field in the app sitting under 16 pixels, the size below which iOS zooms the page in when a field is tapped.

Gates at the end, built for from the start

The gates are the checks an app has to pass to get into the App Store and Google Play: whether it qualifies, whether the web app is sound, the native wrapper, compliance, packaging, and submission. Gates 0 and 1 run at S7 on every job. Gates 2 to 5 run only on the store track, and only when a founder says so.

The gates sit at the end, but they are written for from the beginning. Read backwards, every gate check is a requirement that some earlier station owns, so the stations upstream build what the gates will ask for, and the gates find a passing app rather than a list of omissions.

On the first job, five store requirements pulled upstream this way caught problems in the build before any gate ran: a privacy page that did not exist yet, an app name that had already drifted between the manifest and the page title, a missing launch screen size, padding for the phone's rounded corners that did nothing because the setting that turns it on was absent, and store copy 31 characters too long for its field. Each one is something a store finds at submission. Each one was cheap to fix between S4 and S7.

A gate result has to show its work. The record for gates 0 and 1 carries a verdict (pass, fail, partial, or not run), what it ran against (the files on disk, a local server, or the live address), the evidence, and any check it could not make there, by name. The runner refuses a pass with no evidence, a partial that defers nothing, a not run with no reason, and a pass that also defers checks, because that is a partial. A pass with no evidence is a claim.

Where a job can end

Besides finishing, a job can leave the line three ways, and each one is a document rather than a status.

EndingWhat it must carryWhat it keeps
KillThe reason, naming the specific finding, and what would have to be true for the same idea to come back and be judged differentlyNothing. A resubmission starts again at S0
ParkWhy, and the condition for picking it back up. A park with a reason and no condition is an abandonment nobody has admitted toEvery artifact, every hash, and its place in the line. It also records the code's latest commit against what S6 wrote down, because work done outside the line does not stop being work, it stops being recorded
GraduateA founder's decision that the app is worth keepingThe app gets its own private repository with its full history and tests, a verified backup copy, and its own address, and the preview is freed for the next job

Graduation exists because of something we measured. The preview, the one address where a new app is shown for the first time, held a flattened copy of the build: no tests, no history, and every job that reached S7 overwrote it. The first app through the line had no copy anywhere but one laptop. Its own S8 record already listed a remote copy as a step still to do. The line had found the problem and nothing acted on it, because no command existed to act.

Now the deploy reads which job is in the preview and refuses to overwrite one that has not graduated or been killed. The graduate command, run on its own, only prints what it would do. It acts when told to, because the decision that an app is worth keeping belongs to a person.

The order after that is fixed. Every job goes to a live web app first. Then comes the human pass, where we use it on a real phone and refine it. The stores come last, and only on a founder's word. What gets deployed always shows the whole plan: anything not built yet appears as a marked placeholder, so the person looking at it can see the shape of the full product, and one switch removes every placeholder from the store build, because both stores reject apps that are visibly incomplete.

What it has caught in itself

The most useful thing the line has produced so far is a list of its own defects, each one now covered by a check.

The first came on the first day. A hygiene audit, a script with no model and no network, reported three problems with one of our apps: no manifest, the manifest not linked, and no service worker. All three existed, in a subfolder rather than at the top of the repository. The checks were right. The script had assumed where the app lived instead of finding out. A stage that invents an input it was not given produces confident, wrong work that every stage after it inherits, and that is the risk the whole design exists to manage.

The second was in the runner. A job carried an exit field, recording where the run stops, that the runner wrote once and never read: one match in 807 lines. So every station judged against the delivery in front of it rather than against the product. A risk that feasibility had scoped correctly, as a feature that could not be delivered as a web app at all, arrived in the requirements as a note about one browser, with nothing recording that the reason goes away inside a native app. A job now records the platforms the product will eventually run on separately, and every exclusion in the requirements carries what is excluded, why, which platforms that holds for, and when to revisit it.

Then a pattern, seven times by 13 October: a fact with no field does not go unrecorded, it goes somewhere worse. The clearest case was the record of what data an app collects, which had no place for evidence, so the agent put the evidence in the purpose. The artifact held, word for word:

Stored in the browser on this device and sent nowhere Evidence: app/core.js addActor

That is a broken sentence headed for a privacy notice. The field exists now, and the runner refuses a data claim with no evidence behind it. That includes "we collect nothing", which is the easiest sentence in the document to write and the hardest to prove. The pattern itself is now a house rule.

The last one changed our own policy. Adding a new house rule, and recording it against an app that had already graduated, flagged every stage after S3 as out of date. The flag was right for a job in progress and wrong for a shipped one. Lessons keep arriving, and treating each one as invalidating every shipped app would quietly rewrite the record of what shipped. Now a graduated job records the exact rule set it shipped under, the runner refuses to reopen it except to correct the record of what actually shipped, and a separate command lists every rule learned since. Drift is information, not a defect.

It does not build the app

This is where people expect the story to turn, with the machine doing the work. It does not.

We pick the colors. We design the interface, decide how it should feel, choose which feature comes next and why that one instead of another. None of that is automated and none of it is going to be.

The line is built to know that about itself. Store submission, signing keys, moving money and legal filings are never automated. There is a field recording who decided the price, and it is left empty on purpose, because a price is a founders' decision and the field should point at that decision rather than hold a number a model chose. And there is one thing no check can do. On the first app, three color palettes passed the contrast test in both light and dark themes, and all three were rejected by the person looking at the screen. A measurement proves a floor. It never proves a judgment.

An assembly line never decided what the car should be.

Millwright gives us a foundation. A solid one, checked the same way every time, so that our hours go where they actually matter. Which is the same place they went when it was one man calling from a golf course to say this does not work right, can you fine tune it.

Questions we expect

Is this just a chain of prompts? No. The prompts do the work inside each station, but they do not decide when a station is done. A program with no model in it decides that, against a schema, and it refuses work that does not validate. The prompts can be swapped. The contract between the stations is the part that matters.

Does it replace the people who build? No. It does the parts that are the same every time, and it stops and hands over at the points that need a person: whether an idea is worth building, whether the app is any good on a real phone, whether it lives, what it costs, and pressing submit.

Is any of this new? The assembly line is more than a century old, and checking each step's output against a schema is ordinary practice in software. What we are doing is applying both to the whole path from an idea to an app, with the lessons we have already learned built in.

Where this sits

11 October: Millwright is named and the stations are designed. 12 October: the first job goes through all ten stations, and the store gates run for the first time and stop at compliance, which is what that gate is for. 13 October: the refinement pass runs twice on that app, and a job can now be parked. That app is graduated, refined, deployed to its own site, and parked, waiting on decisions that are not the line's to make. 24 October: Castings, the inventory of every part we have built, which Millwright will read before it writes anything.