Skip to content

What is vibe coding, and why it breaks on the third feature

The demo works in the first hour. The trouble starts when the agent forgets why the rule exists. What separates the two is context architecture.

By OttherSeptember 04, 20269 min read
Capa do artigo: à esquerda, barras brancas empilhadas em ordem sobre fundo claro; à direita, as mesmas barras espalhadas e tortas sobre fundo coral

If you have ever asked an AI agent for a whole screen and it came back working, you have done vibe coding. The practice caught on because the first hour is impressive: you describe the intent in plain language, the agent writes, you run it, it works.

What almost no content on the subject covers is week three. That is when the agent proposes the fourth variation of the same bottom sheet, imports the database client inside a UI component, and reintroduces a library you removed two months ago because it stalled scrolling on Android. None of that is a model failure. It is a context failure.

This article is about the layer that separates the demo from the project that survives: how to structure a project’s knowledge so the agent decides correctly without you re-teaching the same rules every session. The numbers come from a real app, Gaveta, with five months in production, 726 commits, 38 versions published to the stores, and one developer.

Diagram: thirty-one variations of the same component collapsing into a single block

What vibe coding actually is

Vibe coding means writing software by describing the outcome in plain language and letting the AI agent produce the code, reviewing by behavior rather than line by line. The term caught on in 2025 and became an umbrella for everything from a throwaway prototype to a product in production.

The useful distinction: vibe coding is a working mode, not a skill level. And it has two versions with different fates.

In the throwaway version, you accept the code with no contract. The prototype gets discarded next week and none of this matters.

In the production version, the code stays. And the cost shifts: writing stops being the expensive step, because the agent writes fast. What becomes expensive is that every session starts from zero.

Diagram: on the left a dashed block coming apart, on the right a solid block that stays

Why the agent forgets, and why you do too

The agent does not know why that screen hook carries a sub-view-model for the sheet. It does not know the blur library was swapped out because it recomposed every frame and stalled the list. It does not know Apple already rejected a release over a single word in the store note.

You know. But you forget too, and three months from now you are a different person reading your own code.

Without somewhere that answers those questions at the moment of the decision, two outcomes follow, and both cost:

  • Architecture rots by accretion. Every new screen invents its own variant of the pattern. At Gaveta, that process produced 31 hand-written bottom sheets before anyone counted.
  • You become the agent’s human compiler. Every PR turns into a session re-teaching the same five rules. The speed gain evaporates in review.

The way out is to treat context as an architectural artifact, with the same discipline you apply to code: explicit boundaries, one source of truth per subject, and automatic enforcement wherever it fits.

Diagram: every row restarts from a coral marker, showing context rebuilt each session

One sovereign document per boundary

The first predictable mistake is writing one giant document that tries to cover everything. Neither humans nor agents read it: it blows past the attention window and becomes noise.

The alternative is applying information architecture to the repository itself. At Gaveta, the root document runs 212 lines and opens by declaring what it is not: it describes how the packages connect, never how to code inside one of them. That belongs to the package document, and the rule is declared inviolable.

The root becomes a routing table:

Area you are touchingSovereign document
mobile/**mobile/CLAUDE.md (1,259 lines)
web/**web/CLAUDE.md (1,853 lines)
supabase/**docs/SUPABASE.md
Product and designPRODUCT.md and DESIGN.md

Document scope follows problem scope. The smallest one runs 116 lines and lives inside the blur component folder, because the decision it holds only matters to whoever is writing blur. The root knows nothing about blur, and that ignorance is deliberate.

It closes with the instruction that prevents the worst kind of damage: if your change does not fit any recipe in the sovereign document, stop and ask, do not improvise architecture.

Diagram: one wide document at the top routing to five smaller ones, one per boundary

Contract and cookbook answer different questions

Each package document splits into two halves with different natures.

The contract answers "what am I not allowed to do". Principles, a layer table, naming conventions, and the list of lint rules. It fits in a few short sections because a rule that does not fit does not get followed. The Gaveta layer table has five rows and answers ninety percent of "where does this go" questions:

LayerMayMay not
ServicePure async, database, RPC, fetchReact hooks, JSX
Data hookCache key from the central registry, invalidationsPage JSX
Screen hookCompose hooks, navigate, derive the view modelImport the database client directly
ViewTyped props, JSXQueries, mutations, data access
Routeconst vm = useXScreen() and render the ViewBusiness logic, inline queries

The cookbook answers something else: "how do I build the thing I need right now". Numbered steps for a feature from scratch, and a set of patterns that already repeated, each linking to the real example in the code.

The cookbook grows by repetition, not anticipation: a pattern only enters after showing up in two places. An anticipated cookbook is decoration.

The detail that saves the most time is a table of "look at this feature as a reference", tying each kind of work to an existing file. Documentation that points at real code ages better than documentation that describes real code.

Diagram: five short uniform bars on the left, many bars of varying length on the right

A rule without lint is a suggestion

Every normative document has one enemy: the third PR. On the first feature you follow the rule, on the second you do too, on the third you are in a hurry and the document does not complain.

Lint complains. At Gaveta, four things break the build at error level: the database client and the caching library imported in the route layer or in any View, a cache key written as a literal outside the central registry, and a modal written by hand instead of the shared primitive.

The part that makes the difference sits in the error message. It does not only forbid, it teaches the way out: "a bottom sheet is not written by hand, use the primitive, which already brings drag-to-dismiss, safe area, keyboard and tokens".

The same logic applies to the design system. Tokens documented in prose are a suggestion. Tokens documented alongside a rule that forbids literal hex in JSX are a contract. The difference shows up in month three, when there are forty screens instead of four.

Diagram: lines crossing a vertical coral barrier, some passing through and others stopped

The document that admits it is not true yet

The most useful part of this structure is the most uncomfortable to write.

Gaveta’s web document opens by saying it is the target architecture, not the current state: part of the dashboard still has data access inside the page, hardcoded strings, and hand-rolled caching. And it publishes the phase plan rather than pretending the rules already hold, with phase 0 being what is actually active in lint today.

That changes reader behavior, human or agent. A document claiming a hundred percent compliance makes the agent copy legacy code thinking it is the standard. A document that names the debt makes the agent ask before copying.

Diagram: four phases numbered 0 to 3, with the active rules filled in coral at each phase

Every rule carries the scar that produced it

Tracing Gaveta’s history, each normative change lands right after an incident:

  • July 28. Apple rejected version 1.5.2 for inaccurate metadata. The cause was one line of the release note starting with "Android:", because the same file feeds both stores. The rule that followed is not advice, it is a verifiable command: before committing, a grep for platform names has to come back empty. And it carries the reason: it cost a full review cycle over one word.
  • August 31. A test link on the splash shipped visible to production. It became a release checklist item, with the explicit condition of confirming it sits inside the development-only block before building.
  • July 22. The 31 bottom sheets became a single primitive, in a commit spanning 41 files. The lint rule forbidding hand-written modals was born the same day. Without it, sheet number 32 would appear within two weeks.

The pattern is the same in all three: the rule gets written after the pain, and the text carries the pain with it. That is what makes it survive. A rule without a story becomes bureaucracy the next person in a hurry deletes. A rule that says "this cost an Apple review cycle" nobody deletes.

Diagram: a timeline with three coral markers, one per incident that produced a rule

The Otther take: context is a deliverable, not a byproduct

When Otther steps into a product already running with a team and an agent, the first thing we measure is not test coverage or code debt. It is how long it takes someone to make the right decision without asking another person. That number predicts velocity better than any output metric.

What we apply, in order:

  1. Map the boundaries before the patterns. Who decides what, and where that decision lives. Without this, every document becomes a dumping ground.
  2. Keep the contract short and the cookbook long. The contract has to fit in your head. The cookbook can be extensive, because it gets consulted, not memorized.
  3. Tie every normative rule to a lint entry. The ones without it get marked aspirational, with the phase plan published. A rule without enforcement is a suggestion.
  4. Tie maintenance to the workflow, not to goodwill. "A new event enters this document in the same PR that instruments it" works. "Keep the document updated" does not.

The same reasoning applies to the handoff between design and engineering, and it explains why so many handoffs fail: when the spec document repeats design system values instead of citing it, it is born out of date. A handoff that points at the source survives the first token change.

Diagram: four equal columns, each topped by a coral bar

The honest ledger

This method has a price, and it shows in the history. Of Gaveta’s 726 commits, 146 touch a markdown file, and 31 of those touch nothing else. One in five commits edits text nobody compiles.

The documents accumulate debt too. A diagram at the root still says "33 migrations" when the folder holds 105, because the number landed in May inside a block nobody revisits. And the lines about "CI blocks merge" describe intent, because lint runs on the developer machine, not on a runner. The system ages exactly where it lacks enforcement, as it predicts about itself.

What the ledger buys: opening the repository after two weeks away, in a package you never touched, and shipping a feature without rebuilding the mental model. The agent too. Five months, 38 releases, and the architecture layer still has centralized cache keys, six-line routes, and Views with no data access, because lint does not let it through.

Diagram: 726 dots, of which 146 in light coral and 31 in solid coral along the bottom

Conclusion

Vibe coding does not fail because of the model. It fails because the knowledge justifying each project decision lives in someone’s head, and neither that person nor the agent can reach it when the decision happens.

The question that resolves this is not which tool to use. It is where each kind of knowledge lives, who owns it, and what happens when someone breaks the rule. Answering that with markdown, ADRs, a design system or a handbook is a formatting detail. The test does not change: when the next session needs to decide, will it find the answer where it is going to look?

If your team is gaining speed with AI and losing product consistency at the same rate, that is the problem. Talk to Otther about structuring the context layer of your product.