Discuss one product area

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.

By Loft Published

Summary

What the build shows

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
Commits per day, July 10 to October 16
Below the line, the private V1 server: nearly 2,000 commits over three months, merges excluded, peaking above 100 a day on September 22 and 23 while spec quality tooling was built. The empty stretch from August 15 to 24 was a vacation. Above it, the public format and command line: about 350 commits in six days, over 100 of them on October 12.
Part 1

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.

Our working notes, July 14
Size of Loft's own specification, July 13 to October 9
Nodes at each of the roughly 90 versions the repository pinned, out of nearly 140 published: about 300 on July 13 and about 5,000 on October 9, with about 1,000 requirements, 3,500 acceptance criteria, and 50 architecture decisions. The October jump is the whole-spec review's fixes plus this website's specification, added to the same document.

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.

One Claude Code session
  1. Asks for a plan, and for clarifying questions first
  2. Maps the code and the spec with Explore agents
  3. Asks its questions in batches, with a recommended answer for each
  4. Answers, then approves the plan or sends it back
  5. Writes the spec change first, on a spec branch paired with a git worktree of the same name
  6. Ratifies the published version of the spec
  7. Writes the code against it, with gates and reviewers on every commit
  8. 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.

CTO

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.

The performance loop
  1. Start from a real symptomWrites timing out in production; a gate too slow to run often
  2. Record a baseline on real dataA copy of the production spec; the gate timed cold and warm
  3. Find where the time goesStatements counted and every phase logged
  4. Fix one causeWrite only what changed; reuse prepared statements and template databases
  5. Measure against the baselineSame data, numbers written down next to the old ones
  6. Record the decision in the specThen take the next slowest thing
Each pass ends in the spec, so the next agent working on speed starts from the decisions already made.

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.

What V1 cost, and what the open format does instead
In the V1 serverIn the open format
Reading the spec needs the server, a sign-in, and a live MCP connectionThe 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 secondsWrites are file edits, and git holds the history
The server mints labels, and a rejected create burns its numberEach node carries its own key, and labels are assigned when a version is published
No text form; the repository holds exportsOne canonical text form, one canonicalization, one versioned hash
Tests need Docker and PostgreSQLNo services to run
Branching and merging live inside the serverBranches 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?

Part 2

Above the waterline: the open format, October 10 to 16

Commits to the open format, October 11 to 16
Cumulative commits, merges excluded, with the six releases: 2.0.0 on October 13 at 20:25, 2.0.1 an hour later, 2.0.2 and 2.0.3 overnight, 2.0.4 on October 14, and 2.1.0 on October 15. The flat stretches are nights.

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.

How the Software Plant tests the command line
loft binarythe release build, unchanged
Simulators
gitanswers from a scenario, refuses anything else
Clockfixes the publish time
Telemetry collectoraccepts, rejects, fails, or stays silent
Release feeda release, a draft, an error, or a rate limit
Contract suites
Real giton the developer's path
Real collector and GitHubrun on demand

One exception: git calls Loft as a merge driver, so merge scenarios run under a real git merge with nothing simulated.

A simulator that drifts from the real system would make the gate lie, so each one is held to a contract suite that runs the same scenarios against the simulator and the real thing.

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.

  1. First commit

    An empty repository. The 53 hours start here.

  2. Corrections become rules the same day

    1. A person corrects Claude
    2. The rule goes into memory or the agent guide
    3. 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.

  3. The spec moves in, and every change runs one loop

    1. spec: commit
    2. loft fmt gives keys
    3. loft check on its own spec
    4. Code commit
    5. loft publish assigns labels
    6. loft 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.

  4. Build, review, break, commit

    1. Explore agents map the code
    2. A Plan agent stress-tests the design
    3. Stage agents build
    4. /simplify and /code-review: reviewers in parallel
    5. Break the code on purpose, watch the right test fail
    6. 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.

  5. 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.

  6. 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 reports
    The person steeringAsked about the one git merges without a word

    Claude'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 publish settled a stopped merge or rebase, renumbered the newcomer, and rewrote prose that cited the old label.

  7. The agent skill

    loft init installs the loft-spec skill for Claude Code and Codex, so the agent that writes a spec gets the rules for writing one.

  8. 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.

  9. 2.0.1 on npm as @ask-loft/loft

    The 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.

Evidence for the format's own spec, October 14, before the gaps were closed
  • Linked to a test judged to check it: about 75%
  • Cannot be checked by a test, reason recorded: about 10%
  • Partly covered: about 5%
  • No test: about 10%
A link is a reviewed judgment that a test checks a criterion, and it expires when the criterion changes.

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.

Loft engineer

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.

For a team

Six practices

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Simulate every system your tool calls. Run the real binary against simulators, and hold each simulator to a contract suite against the real system.
  6. 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.
Method

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.