Three months under a 53-hour release: building Loft with Claude Code
The open Loft format and its loft command line went public 53 hours after their first commit. That pace rested on three months of work underneath it. From July to October a team of three built Loft V1 with coding agents, for most of that time Claude Code, around one idea: agents need a plant, a production line of deterministic gates that turns away work that doesn't hold, more than they need a faster factory.
What the build shows
- The speed came from decisions already made. When V2 started on October 11, the data model, the review rules, and the habit of writing the spec first were three months old, and a 5,000-node specification was ready to test the new format against. Claude wrote V2 quickly because little was left to decide.
- Agents made a full specification cheap to write, so we wrote one. Loft has specified itself since its fourth day. Every change to V1 went into its own contract first, across nearly 140 published versions, and the open format took over its own specification eleven hours after its first commit.
- The gates mattered more than the agent's speed. A plant, in our vocabulary, is a line that turns away work that fails a check, every time and on its own. Every failure was caught by gates, simulators, fresh reviewing agents, trial runs in empty folders, and questions from people, and none of them by the agent that caused it, in the moment it caused it.
- People kept the decisions and every irreversible step. Claude planned, built, reviewed, and asked questions. People ratified each version of the spec, approved merges, made the repository public, published the packages, and deployed.
- Cheap building made throwing work away cheap too. We built and later deleted an evidence protocol, a telemetry pipeline, and an early task board. Each removal went through the spec like any other change.
Below the waterline: Loft V1, July to October
A plant, not a factory
In July we wrote down the argument Loft rests on. Coding agents made a first version cheap, but they left the cost of acceptance where it was: showing that what was built is what was agreed. People and models both produce fuzzy output. A tired person half-finishes an edit; an agent fills a gap with a confident guess. The decision lands in code and stays there, and within a year the team reads the code instead of the document.
Our answer was deterministic gates. Tests have gated behavior for decades; we wanted the same gate on the specification. Our July deck framed it as factory against plant. A factory is agents, prompts, and orchestration. A plant is a controlled line where building and testing run on their own and work that fails a gate is turned away automatically. As we put it in a talk on July 20, "Coding agents are not enough."
Loft is the first pillar of that Software Plant. It keeps a product contract, the ratified specification that people and agents build against, and runs each change through the Loft loop: Author → Ratify → Implement → Reconcile → Decide. Yard, for coordinating production, and Berth, for operational feedback, remain directions.
July 10: a server in 32 minutes
V1 began as a spec-first rewrite: a thousand-line Markdown spec of an earlier prototype, down to three of its bugs not to repeat. The first commit landed at 13:41 and canonical hashing at 13:46. At 14:13 a server offered about 50 tools to coding agents over the Model Context Protocol (MCP). By evening it ran on a home Kubernetes cluster with an authoring skill. Deterministic static checks were in place from that first afternoon: hard limits on function length and complexity that fail the build, so sloppy agent code is turned away at the gate and the agent has to refactor instead of piling on.
OpenAI's Codex wrote most of the Rust core in July and August. Claude Code made its first commit on July 17, on the web interface, and by mid-September wrote nearly all of the code: over 90% of V1's October commits carry a Claude co-author line, and every commit of the open format does.
July 13: Loft becomes its own contract
On day four the Markdown spec was retired. Loft's specification, written into Loft through its own MCP tools, became the contract: about 300 nodes, pinned in the repository by a lock file. Every change took the same route: branch the spec, stage and review the edits, publish an immutable version, move the lock, write the code.
Loft is not the spec, Loft is the systems design floor.
Agents made a full specification affordable. Claude wrote nearly all of it; people corrected and ratified it. Corrections that recurred, such as criteria bundling two checks or a color choice filed as a requirement instead of a decision, became lint rules or agent guidance.
How a session ran
Every session ran the same loop, and the person steering held only its decision points. Anything corrected twice became a standing rule, so the loop got stricter with each pass.
- Asks for a plan, and for clarifying questions first
- Maps the code and the spec with Explore agents
- Asks its questions in batches, with a recommended answer for each
- Answers, then approves the plan or sends it back
- Writes the spec change first, on a spec branch paired with a git worktree of the same name
- Ratifies the published version of the spec
- Writes the code against it, with gates and reviewers on every commit
- Corrects what is wrong; a correction made twice becomes a standing rule for the next session
Built, kept, and thrown away
Features cost days. Spec branching went from proposal to working code in two days; structured architecture decisions took one. Spec quality tooling took over 200 commits on September 22 and 23. Multi-tenancy shipped as v1.0.0 on October 2, followed by twelve patch releases in six days. The server reached about 300,000 lines of Rust and 2,000 tests.
Coding agents are extremely smart, fast code generators. We should experiment more, and not shy away from cutting whatever doesn't fit the product.
Cheap building made deleting cheap. A measurement-evidence protocol, an early step toward Berth, grew to about 20 MCP tools, 20 tables, and as many prototypes over ten weeks, then was deleted on October 4. A telemetry pipeline lived less than a month. Yard, an early task board, went from domain model to release in four days and was parked. Each removal went through the spec first.
The MCP surface grew from about 50 tools to nearly 90 in six days, until some agent clients stopped showing them. A compact surface of nine tools, later eleven, fixed that, while the full catalog grew to about 160.
Gates at five thousand nodes
Reconciliation, the check of an implementation against the published contract, ran across the whole server in late September: about 2,700 items and 1,800 proposed issues, worked through in severity waves by parallel agents. The first wave broke the build. Agents sharing one git index committed each other's staged changes, and since then agents commit named paths only.
In early October we turned the spec quality tooling on the spec itself. Lint findings had grown from about 300 to nearly 5,000. The review found nearly 800 issues with fewer than 500 root causes, nearly 30 of them decisions only we could make, and the fixes shipped as eleven published spec branches on October 7.
Sometimes the bug was in the spec. On October 9 we asked Claude why agents kept losing their connection to Loft, starting with a test that reproduced it on a simulated clock. The published spec gave every sign-in grant a 32-hour absolute lifetime. The fix was a spec change, then a release.
Performance followed one loop, whatever was slow. A one-edit write ran over 100 seconds in production because it re-inserted the document's whole history: about 75,000 SQL statements. Measured on a copy of the production spec and rewritten to store only the difference, it took about 100 statements and a third of a second. The same loop cut the everyday test gate from over 40 minutes to under 10.
- Start from a real symptomWrites timing out in production; a gate too slow to run often
- Record a baseline on real dataA copy of the production spec; the gate timed cold and warm
- Find where the time goesStatements counted and every phase logged
- Fix one causeWrite only what changed; reuse prepared statements and template databases
- Measure against the baselineSame data, numbers written down next to the old ones
- Record the decision in the specThen take the next slowest thing
What the server cost
By October a lot stood between an agent and the spec: a live, signed-in MCP connection to read it, a 60-second lease to write it, server-minted labels that a rejected create burned and a replayed branch renumbered, and no text form to edit or diff.
| In the V1 server | In the open format |
|---|---|
| Reading the spec needs the server, a sign-in, and a live MCP connection | The spec is a folder of YAML files in the repository, read by one static binary |
| Writes go through a 60-second lease and once took over 100 seconds | Writes are file edits, and git holds the history |
| The server mints labels, and a rejected create burns its number | Each node carries its own key, and labels are assigned when a version is published |
| No text form; the repository holds exports | One canonical text form, one canonicalization, one versioned hash |
| Tests need Docker and PostgreSQL | No services to run |
| Branching and merging live inside the server | Branches are git branches, merged by a git merge driver |
The question that started V2 was about two people.
If we choose git for a single user, will two separate engineers be able to work on the spec in the repo in parallel?
Above the waterline: the open format, October 10 to 16
October 10: stress-testing the format
A question about an open-source edition of the server turned into a different one: what if the format itself were open, with the server as one implementation of it? We switched to Claude Fable 5.1 for a second view and asked Claude to stress-test the Loft document format against that idea. Six invariants came out of it: the content address covers meaning only; identity travels in the file; structure is stored rather than re-derived; process state stays out of the content; there is one text form, one canonicalization, and one versioned hash; and every version stays readable.
Claude also set the order of work. The files are the store, so the first release needs only one static binary. The converter from V1 comes first, because a 5,000-node specification is the best test a new format could ask for. And five properties of the text form let two engineers merge one spec in git: no derived data in files, identity in the file, order taken from file order, one file per unit of work, and a canonical formatter.
Ending the bootstrap
The next morning we turned down Claude's first plan. It wrote the new format's specification with V1's tools and left it there, so the open command line would have stayed specified by the private server it was meant to replace. The plan, Claude agreed, "forgot to end the bootstrap." The new one wrote the spec in V1 first and moved it into the repository as soon as the format could hold it.
A relay of about twenty subagents wrote that spec in an afternoon and evening, one set of changes each, in turn, because V1 allowed one writer at a time. Review agents read each branch, and fix agents worked through their findings, while the main session wrote the model and hashing code against the latest checkpoint.
At 21:06, five and a half hours after the first commit, the converter gate passed: V1's whole specification, converted to the new format and back, reproduced all of its roughly 5,000 node hashes. By 02:00 the format's specification lived in the repository as YAML, checked by the command line it specifies.
Then we asked "how are we going to make sure the V1 and V2 do not diverge where they should not?" Claude added a parity guard: every intended difference must cite the spec node that explains it, and any other difference fails a test. It caught its first one within the hour.
The Software Plant in one command-line tool
On the second day Claude brought the plan for publishing: loft publish, loft verify, the lock, and label allocation, all of which call git. We turned that plan down too: "git should be an external component; for tests you should create a git simulator that encapsulates the happy path and the errors we anticipate." The simulator was to be a new product in the spec, called the Software Plant.
That is the plant from July at the size of one tool. The Software Plant is a product in the format's own specification, and its job is to stand in for every system Loft depends on. Tests run the real loft binary, unchanged, against simulators instead of those systems: a git executable that answers the calls Loft makes from a scenario the test writes, such as a clean repository, uncommitted changes, a detached HEAD, or a merge stopped on a conflict, and refuses every other call; a clock that fixes the time of a publish; and, later, a telemetry collector and a release feed on loopback ports.
loft binarythe release build, unchangedOne exception: git calls Loft as a merge driver, so merge scenarios run under a real git merge with nothing simulated.
The plant earned its place within hours. After the first publish, continuous integration went red on GitHub's runner and nowhere else. Since git 2.46, every git commit starts background maintenance that writes a lock file, and a test checking that reading the repository changes no file caught it in the test setup's own commits. The fix was one flag in the test setup. Claude declined to teach the simulator that behavior: Loft never runs that command, and the simulator answers only the calls Loft makes.
Fifty-three hours to public
By October 16 the format's own spec held over 550 requirements and 2,000 acceptance criteria, against about 70,000 lines of Rust, more than half of them tests. Nearly 300 subagents and about 17,000 tool calls got it there.
-
First commit
An empty repository. The 53 hours start here.
-
Corrections become rules the same day
- A person corrects Claude
- The rule goes into memory or the agent guide
- Every later session starts from it
"You should first propose, and then separately accept an ADR." "Each lint rule must become a separate RQ with its own ACs." "I mean, ProductID, FeatureGroupId, etc." Each one changed every decision, lint rule, and node kind that followed.
-
The spec moves in, and every change runs one loop
spec:commitloft fmtgives keysloft checkon its own spec- Code commit
loft publishassigns labelsloft verify
Labels are assigned only on the main branch. After each publish, a second one must report that nothing changed. Nearly 80 of the first 400 commits are spec commits.
-
Build, review, break, commit
- Explore agents map the code
- A Plan agent stress-tests the design
- Stage agents build
/simplifyand/code-review: reviewers in parallel- Break the code on purpose, watch the right test fail
- Commit on a green gate
Plan agents found real flaws in Claude's own merge design. The reviewers caught a formatter that reported success on a tree the reader then rejected.
-
The person steering keeps the decision points
"Do not merge into develop without my approval for V2 from now on." Plans, answers, ratified spec versions, merges, and later every release stay with a person.
-
A person caught it
"Do we have a case where the conflict is because there are two different items under the same label?"
Claude's merge designCovered the conflicts git reportsThe person steeringAsked about the one git merges without a wordClaude's design had missed it. Within minutes Claude reproduced it in two throwaway clones: two branches could publish the same requirement number, and git merged them cleanly. Two hours later
loft publishsettled a stopped merge or rebase, renumbered the newcomer, and rewrote prose that cited the old label. -
The agent skill
loft initinstalls theloft-specskill for Claude Code and Codex, so the agent that writes a spec gets the rules for writing one. -
53hours from first commit
Public: version 2.0.0 on crates.io
A person changed the repository's visibility and ran the publish; Claude Code's permission classifier stopped Claude from doing either, and Claude left it there.
-
2.0.1 on npm as
@ask-loft/loftThe repository's README (github.com, opens in a new tab) states the workflow: "You write a specification by talking to a coding agent, not by editing YAML."
"Only half of the ACs?"
On October 14 the command line gained loft reconcile and a trace file that links each criterion to its tests. Each link stores the criterion's hash at review, so rewording a criterion makes its links stale. The first seeding, from old test names, linked about half the criteria. We asked: "OK, so we only have evidence for half of the ACs in the tests?"
Claude sent eight agents, one per area of the code, and kept only the links they were sure of. When we asked "Why is this taking so long", it moved the remaining stages into parallel git worktrees. By mid-afternoon all but a handful of nodes were linked, and seven defects were fixed along the way, each with a test that failed first.
That evening a second Claude Code session ran the same process on this website. It imported the site's roughly 1,000 nodes from V1 with loft import v1, keeping every label, and seeded a trace. Ten reviewer agents read its roughly 750 links: nearly 600 held, about 150 were partial, and a few pointed at the wrong test. Fix agents closed the gaps, fresh agents reviewed the fixes, four real bugs in the live site came out, and tests grew from under 700 to over 800. It took under two hours, including a deploy a person started by hand.
What the gates caught, and what got past them
The build went wrong in ordinary ways. What matters about each failure is where it was caught.
Caught before release
Where/simplify on a merge branch
What went wrongA three-way merge treated identical extension values as different, so every merge of an imported node conflicted.
Caught byReviewers
What changedFixed before the merge, with a test.
WhereFour reviewers before a render merge
What went wrongRendering could delete files in hidden directories and follow symbolic links under --force.
Caught byReviewers
What changedSix bugs fixed before the merge; Claude: "No, don't merge yet."
Got past the gates
WhereRelease 2.0.2
What went wrongThe npm binary shipped without its execute bit. The Codex agent in the release trial fixed it by hand and carried on, so the trial looked clean.
Caught byClaude, afterwards
What changedReleases come from packed tarballs, and a smoke test runs the binary before a person approves.
WhereA gate under git rebase --exec
What went wrongTests that drive real git inherited git's environment and acted on the developer's repository: commits on main, stray branches, a broken checkout.
Caught byPermission check and a person
What changedTest helpers strip every repository-local git variable.
WhereA Stage 3 commit
What went wrongIt went in after clippy failed, because the command did not wait for the gate's result.
Caught byClaude, afterwards
What changedCommits go through a script that commits only on a green gate.
WhereA design choice
What went wrongClaude dropped a scope from the format without asking.
Caught byA person's question
What changedClaude named it when asked: "it was never raised with you as a decision." It became an explicit decision in the spec.
Each failure was caught by something other than the agent that caused it: a reviewer, a trial transcript, a permission check, or a person. That is the case for the plant.
After the release
Release 2.1.0 on October 15 taught the command line to read OpenSpec, the format from our study How good are OpenSpec specs? loft import openspec turns a project's main specs into a Loft spec, and loft render --openspec writes them back. Every one of the 60-odd public projects with main specs survived import, render, and re-import.
Loft today is the first pillar: the product contract, coding-agent work against it, reconciliation, and a person's decision whether to accept the result. The worked example takes one change through all five stages, and the open format and command line run that loop in a single repository for a small team. Yard and Berth remain directions. What the three months under the 53 hours suggest for them is the same thing they showed for Loft.
Agents produce plausible work faster than anyone can read it. The line has to turn work away on its own before people can afford to accept what the agents make.
That line is easiest to judge on a spec you already know. If yours lives in OpenSpec, one import brings it into Loft, the first loft check shows what the line would turn away, and loft render --openspec takes it back out whenever you like.
Six practices
- Write the spec first and pin it. A spec commit before every code commit, a lock that names the version the code is built against, and a check in the gate that the lock matches the spec.
- Gate the spec like the code. Run the spec's own format check and lint in the same pre-commit gate as the tests, so a bad requirement fails the commit the way a failing test does.
- Link criteria to tests, and let the links expire. Store the criterion's hash with each reviewed link, so rewording a criterion sends its links back for review.
- Review with fresh agents, then review the fixes with other ones. Split by lens or by area, have each reviewer write to its own file, and verify findings before acting on them.
- Simulate every system your tool calls. Run the real binary against simulators, and hold each simulator to a contract suite against the real system.
- Keep irreversible steps with a person, and try every release as a stranger. Run it with a fresh agent in an empty folder, then read the transcript, because the agent may have worked around the bug.
How we counted
- Repositories
- The private V1 server repository, develop branch, July 10 to October 15: nearly 2,000 commits, with or without merges. The public format repository, Ask-Loft/loft (github.com, opens in a new tab): about 400 commits on develop by October 16; about 350 commits without merges across all branches, including one branch still in review.
- Sessions
- Claude Code transcripts of the sessions that worked on the open format from October 10 to 16, including the website's move to it: nearly 300 subagents and about 17,000 tool calls, which also include the research behind the OpenSpec study. Claude Code transcripts of V1 survive from September 23; earlier months come from git, the repositories' documents, and Codex transcripts.
- Spec sizes
- V1: nodes in the export pinned at each of the roughly 90 lock changes in the repository. Open format: labels in its spec folder on October 16.
- Code and tests
- Lines in Rust source files, and test functions counted by their test attribute, so both are approximate.
- Times
- Western European Summer Time, UTC+1.