How we built Libretto with AI agents
A CMS written largely by agents, the limits we added along the way, and the times they did not hold.
Libretto is a CMS for design studios and their clients. We built it with AI agents: from the first commit, on 21 April 2026, to today there are 3,665 commits, and 2,642 of them carry an agent as co-author. This note is about how the work changed once the agents were many and fast, which limits we added, and where those limits did not hold.
What we measured
Sources: .github/workflows/ci.yml, scripts/merge-gate.mjs and git log in Librettoโs repository
Where we started
For the first months Libretto grew the way a prototype grows: quickly, and forwards. There were 662 commits in April, 90 in June, a few hundred a month over the summer. The code worked, the site built, the editor opened. What was missing was a way to tell whether a change made it better or worse.
On 6 October we counted. The type checker found 636 errors in the application and 549 in the tests: 1,185 in all. knip, which looks for files, exports and dependencies nobody uses, reported 483. None of those numbers stopped anything. An agent could add ten type errors and its work reached production like everyone elseโs.
The first limits
The first limit was a ratchet. The idea is simple: todayโs count becomes the maximum allowed. A change that exceeds it does not pass; a change that lowers it has to lower the limit too, in the same commit. The number can only go down, and whoever brings it down fixes it there for everyone who comes after. It does not ask for everything to be fixed in a day. It asks that nothing ever gets worse.
The layer boundaries came the same day. Libretto has four parts, the admin, the shared code, the site themes and the server, and not every part may depend on every other: a theme must not import the admin, and the server must import neither. On 6 October the rules were a snapshot; on the 7th they became errors. Today there are 17, and the list of allowed exceptions is empty.
The third limit is about data, not code. A Libretto page is a tree of nodes, and since 7 October every write to that tree goes through a single check, validateTree. If a node lands in the wrong place, the write is refused with the reason, and the tree stays as it was. That holds for the editor and for the agents, which since 13 October also work on sites through an MCP interface.
More agents, one door
With the limits in place we picked up the pace. On 15 October we decided to run three jobs in parallel, and no more: each in its own copy of the code, with its own database, so that two agents would not get in each otherโs way. The next day there were 216 commits, and thirteen branches were merged into main.
Every merge brought the numbers down: the ratchet comment in CI keeps the tally, line by line. On 16 October, the day of the thirteen merges, type errors went from 491 to 262. By 20 October they were 234, 80 per cent fewer than on the 6th. Over the same period the issues knip reported went from 483 to 37.
The chart below is knipโs ratchet. It never climbs: each step is a change that took something away and lowered the limit with it.

| Date | Step | knip |
|---|---|---|
| 6 Oct | baseline | 483 |
| 14 Oct | M1a | 115 |
| 14-15 Oct | M2a | 108 |
| 15 Oct | M3 G14 | 85 |
| 15 Oct | M3 review | 73 |
| 15 Oct | M3c-b | 72 |
| 16 Oct | M4b-0 | 71 |
| 16 Oct | ADM | 68 |
| 16 Oct | M6b | 67 |
| 16 Oct | M9 | 39 |
| 16 Oct | M7 | 38 |
| 20 Oct | P5 | 37 |
| 25 Oct | catalogue trim | 35 |
Agents as users too
The agents do not only write Librettoโs code: they use it. Through the MCP interface an agent reads a site, looks up the operations it needs and sends them in batches. The same rules apply here as well, because a site built by an agent has to hold as well as one built by a person.
A batch can be tried first: the dry run runs every check and then rolls back, and publishing in a dry run is always refused. When a batch is applied, the answer holds the revisions and the call to undo it. Publishing a site is a separate operation that has to be confirmed explicitly, and the agent asks for it only once a person has given the go. Since 25 October, when a tool does not allow something, the agent does not look for a shortcut: it opens a ticket for whoever maintains the platform, saying what is missing.
What broke
The limits held almost every time. The times they did not taught us more than the rest.
A count taken in the wrong place. On 21 October an agent lowered the applicationโs type-error limit to 107. The number was right on its machine and wrong in CI, which counted 111: it had been measured right after a build, when the generated types differ from the ones CI checks. The change was reverted and the limit went back where it was. No error had been added, but for a moment the limit asked for something the code was not.1
A check that mistook silence for success. The type check counts the error lines the compiler prints. When the compiler runs out of memory and stops, it prints no error lines at all: the count reads zero, and zero is under every limit. We saw it on 23 October. Since then a compiler that stops makes the check fail instead of pass.
A release that did not wait. Until 25 October a push to main started two things at once: the checks and the release. The release waited only for the image build, not for the checks. That day we found a commit in production with part of its end-to-end tests red. Since then a release starts only when CI is green.
A door that could not be shut. The obvious answer would have been to protect main on GitHub. On the free plan, for a private repository, GitHub says no. So the gate became a script. It takes a lock, so that one branch merges at a time; it merges the branch into main in a dedicated copy, with its own database; it runs the same fourteen steps as CI, cheapest first, stopping at the first red one; and only at the end does it push the result, without force. If main has moved in the meantime, the push is refused and the gate starts again from the new point.2
The numbers that remain
On 25 October the ratchet reached complexity too: the functions above complexity 15 are on a list, by name, that can only get shorter. There were 149; today there are 143. On 26 October the applicationโs type-error limit went down to 100. These numbers are still high, and we say so: a ratchet tells you things are not getting worse, not that they are already good.
What it taught us
A limit is only as good as the count beneath it. If the number is measured in one place and checked in another, sooner or later the two drift apart. Now we count where CI counts, and a script reads the limits straight from the CI file, so there are never two versions of the same number.
A check has to tell silence from success. Zero errors and no answer are two different things, and a check that cannot tell them apart lets through exactly the worst case.
Checks have to run on what will really be published. A branch that is green on its own says nothing about what happens when it meets everyone elseโs work: that is why the gate merges first and checks after, and why the release waits.
And the agentsโ speed is useful only inside these bounds. Three jobs in parallel, thirteen merges in a day, 274 commits on 25 October: none of it would have held without one door, the same checks for everyone, and a person who decides when something gets published.
The method that came out of it is written down, in general form, on The method. Librettoโs numbers are on the case page.
Notes and sources
- The commit that reverts the drop to 107 gives the cause: the types generated after a build differ from the ones CI uses.
- The gate is
scripts/merge-gate.mjs; its fourteen steps are the same commands as CI, with the limits read from the CI file. - Every figure comes from Librettoโs repository, read on 27 October 2026:
git log,.github/workflows/ci.yml,scripts/merge-gate.mjs,.dependency-cruiser.cjsanddocs/metrics/ccn-over-15.json.
More notes
- 28 July 2026
Publishing with AI in five languages: what actually breaks, and how you catch it before the reader does
The defect log of an AI transcreation pipeline in five languages, and how to catch the defects before the reader does.Building with agents14 min read - 24 July 2026
The Italian SMEs already using AI (and what they built)
Some Italian SMEs already run AI in production: what they built, according to public sources.Evidence5 min read - 23 July 2026
Is AI-written code secure? What the independent studies say โ and what to put in the contract
What independent studies say about the security of generated code, and what to put in the contract.Building with agents8 min read
If something here is useful and youโd like a hand making it your own, weโre at info@innestidigital.it. If not, thatโs fine too.