Where the experiment came from

At the beginning of May I wrote that vibe coding is a multiplier of competence, not a substitute for it. That the model is a lever, that the lever amplifies whatever you put into it, and that anyone who can't recognize a mistake produces rubble that looks like software. It was a thesis that had matured through everyday work, but it was still just a thesis. What was missing was the proof under measurable conditions.

So I picked a deliberately difficult test bed: building a complete system from scratch, the emulator of a real external system with official specifications to be followed to the letter, without writing a single line of code by hand. Conversation only. If the thesis was right, the result had to depend on how much technical judgment I could put into steering, not on the amount of code generated.

Three days, sixteen hours

The result arrived in three days, about 16 actual hours of conversation. Multiple services that cooperate and run together in Docker, REST and SOAP APIs faithful to the specifications, signed XML flows, a web console, a client library and over 80 automated tests that pass. It's not a demo prototype. And it isn't perfect either. A few bugs are certainly still there, as in any software at this stage, because no system is born finished in three days.

The AI wrote all of the code. I talked. And the story of those three days proves the June thesis better than I had hoped, because it proves it in reverse. Every critical step was a point where, without technical judgment, the experiment would have failed silently.

The first crack

At first everything goes smoothly. You describe the domain, the AI generates entities, endpoints, contracts. It compiles, it runs, the pages respond. This is the phase where you convince yourself that it works. And it's the most dangerous phase, because everything you see is plausible and nothing you see has been verified.

The first crack came from exception handling. Whatever I got wrong in manual tests, the system always answered with the same generic error code. Always that one. But the official specification describes a precise structure. That code is only the envelope, and the real cause travels nested in dedicated fields. The AI read the envelope and threw away the content. The code was clean, it compiled, and it lied. I asked whether it was really possible that the error was always the same one. It wasn't. It was the comparison with the specification that brought the problem to light. Without that comparison the system would have shipped that way, and whoever used it would have learned to handle errors that don't exist in reality.

Is that what the contract says?

That bug changed the question. From "does it work?" you move to "is it what the contract says?". And with the right question, the cases surfaced one after another.

The specification provided for a flag that changes an object's behavior after a deadline, and an inclusivity rule on the date itself. The first version got both wrong. What exposed it wasn't a failing test, since the tests were written by the same AI that had written the bug. It was the comparison with the specification, which spells those behaviors out in black and white.

Then a required field. In a scenario of allocation among multiple parties, the specification requires an identifier for each of them. The interface didn't ask for it. You filled in the form, the object was created, everything seemed fine. It's the bug that someone who knows the domain sees, not someone who checks whether the page responds.

Then a core feature reduced to decoration. A fundamental piece of the flow was implemented minimally and ignored by the frontend. Everything ran and everything looked complete. The part the system exists for was missing.

And finally, coverage. The AI had implemented the "relevant" fields of the contract and skipped the others. In the June article I called it the middle-of-the-road solution, the one that's good for the generic case, which is never your case. Here it is at work. Coverage was extended to the entire contract, because a system that handles only the convenient cases prepares you for nothing.

These errors all share the same nature. No linter flags them, no compiler stops them. They compile, they run, they look correct. They are false. They are domain errors, and the AI doesn't grasp the domain on its own.

The decisions you can't type

In the meantime, decisions kept coming that have nothing to do with typing, but everything to do with programming.

The first one was costly, at least on the surface. The AI had set up the frontends its own way, and I had it throw them away and redo them following the right pattern for that context. Discarding working code over an architectural matter seems like a luxury. It isn't. It's the difference between a demo and a foundation that others can read and build on.

The second is a stance. The clients validate nothing. The user can make a mistake and the error comes from the backend, with the real structure, just as it would against the real system. It's the direct consequence of the founding principle of the experiment: code written against the emulator must run identically against the official environment, changing only the configuration.

Then the boundaries. When a new feature arrived for the console, I chose to work only in the frontend, without touching the core that had to remain stable. And from the full data reset I excluded the data belonging to another service, because in a distributed system every piece of data has an owner. Patterns, boundaries, data ownership. These are architectural choices, and the AI gladly goes along with them but never imposes them.

Demanding the truth

In June I wrote that the model is compliant, that it walks you smiling straight into a wall. I saw it happen.

At one point the AI reassured me that the system was identical to the real one. I asked it whether it was really reassuring me or telling me the truth. The truth was a list of simulated parts and shortcuts. That list is now written in plain sight in the interface, because software that declares its boundaries is worth more than software that lies.

The same goes for method. Faced with problems, I stopped the writing of code and shifted the work to diagnosis. The work proceeded in phases, with an inventory of what existed before touching anything and a plan before execution. And statements that things worked weren't enough. End-to-end checks in the real environment were needed, along with tests added where they were missing.

What I didn't do

Honesty applies in the other direction too, otherwise this becomes the self-congratulatory piece I don't want to write.

I didn't write a single line of code. I read the delivered code, because without reading you're blind and that remains the first rule, but I didn't debug it line by line and I didn't design the implementations. Mappings, serializations and endpoints are the AI's work. The tests were written by the AI; I asked that they exist and that they actually run. Some of my yeses were ratifications of its proposals, not my decisions, and approving with judgment counts but weighs less than deciding. I also did a fair amount of visual QA: duplicate icons, wrong contrasts, spacing. Real bugs, but an attentive user can find them without knowing how to program, and I don't count them as proof of competence.

The numbers

A retrospective estimate on the scope says the same work would have cost a senior programmer between 200 and 350 hours, one to two months full time. The parts that burn hours are the least visible ones: serializations, namespaces, signatures. The hours of conversation were 16. In June I spoke of an expert being ten times faster. In hindsight, on this project, the multiplier is around 15. Take it as an order of magnitude, not as a measurement.

And the residual bugs? They exist, it's inevitable. They'll emerge with use and be fixed with the same cycle, sessions of minutes, not days of development. What doesn't change is the condition. Those bugs, too, will need someone who recognizes them as wrong before they can be fixed.

Because the multiplier holds on one condition, and only one. Multipliers amplify what they find. If they find someone able to recognize the error envelope, the forgotten flag, the missing required field, they multiply competence. If they find zero, they multiply zero. Worse, actually. They produce a plausible and wrong system, shipped in good faith, that blows up when the error is costly.

The June concession still stands. The tools improve fast and part of this guarding work will be absorbed by the models, so the experiment will have to be redone in a few years and will give different numbers. But today the thesis comes out of the experiment confirmed and even strengthened. Vibe coding doesn't ask you to type. It asks you to recognize when the output is wrong and to direct the correction. With the same AI, change the guidance and the result changes.