The Path from Vibe Coder to Engineer

AI lowered the barrier to making software appear, not to understanding it. Seven habits, learned the hard way, for crossing from vibe coding to engineering.

Go Slow Ask Questions Have a Vision Drive Everything Document Everything Trust Nothing Never Give Up

AI has made building software astonishingly accessible.

That is a good thing

Someone with an idea can now sit down with a coding agent and create in days what might once have required months of skilled work to prototype. People who never thought of themselves as programmers are building software right now, this second.

For many people, this starts with vibe coding.

You describe what you want. The AI writes something. You run it. Something breaks. You paste the error back in. It fixes it. Eventually the thing works.

There is nothing shameful about starting there. I started there.

AI has dramatically lowered the amount of knowledge required to make software appear. It has not lowered the knowledge required to understand whether that software is reliable, secure, maintainable, useful, or even doing what you think it is doing. In some ways, that knowledge matters more now. Software can be produced faster than a human can fully understand it, even for the most experienced engineers.

A multi-agent workflow can change twenty files, add dependencies, rewrite tests, alter configuration, and confidently report success before you have manually reviewed even the first of those changes.

The hard part is knowing what should be built, recognizing what you do not understand, asking the right questions, preserving what you learn, and deciding when the available evidence deserves your trust.

At first: the AI designed and built this at the user’s request.

Eventually: the user directed the AI to build their envisioned idea.

The AI may still have written most of the code, but you chose what it was supposed to become. You understood enough to direct it, discovered what you needed to learn, exercised the finished system, preserved lessons from failures, and demanded evidence instead of reassurance. You became willing to take responsibility for it. That transition does not happen all at once.

It happens through the course of the build.

The Best Course You Can Take Is the Course of the Build

There are excellent books, courses, documentation, lectures, and teachers.

However, there is a kind of knowledge you only acquire when something you care about breaks. You can read about version control for an afternoon and understand why it is useful. Accidentally destroy three days of work and you will understand it differently.

You can read about checkpointing in a machine learning system. Lose a long training run and discover that your checkpoint code saved the model but not the state required to resume correctly, and the lesson becomes permanent.

You can learn that automated tests are important. Intentionally break the behavior a test claims to protect, watch the test remain green, and you will never look at a passing test suite the same way again.

The build gives you context. It gives your learning somewhere to attach. It creates problems you genuinely need to solve instead of hypothetical problems you are trying to remember for an exam. That does not mean blindly building until you become competent through pain. Failure alone teaches nothing.

Failure becomes useful when you investigate it.

Something broke.

  • Why?
  • What assumption was wrong?
  • Why didn’t existing tests catch it?
  • Could we have detected it earlier?
  • What should change so this exact lesson does not have to be learned again?

A good engineering process turns failures into durable improvements.

Maybe the lesson becomes documentation. Maybe it becomes a regression test or architectural rule. Maybe it becomes a hook that prevents an unsafe action or promotes a wanted one.

Maybe it simply changes the questions you ask before performing similar work again.

Try to pay for every lesson only once. Your repository should know more tomorrow than it knows today, not merely contain more code, know more.

That is the deeper learning loop:

Build, then Encounter reality, then Fail, then Investigate, then Learn

Encode the lesson, and build again.

The rest of this paper is about making that loop work.

Go Slow

This sounds ridiculous at first. The tool is fast. Why would the first lesson be to slow down?

Because generation speed and understanding speed are no longer coupled. That creates a dangerous illusion of progress. Ten minutes ago, you understood most of the project. Now agents have modified thirty files.

The build passes, tests are green, an agent reports that the task is complete. You continue. Another feature lands, then another.

Eventually something becomes impossible. A subsystem refuses to behave correctly. The obvious fixes fail. Every local piece seems reasonable, yet the whole thing no longer fits together.

Think about a difficult Sudoku puzzle:

You can make choices that look perfectly logical for a long time. Then you reach the final few boxes and discover that the puzzle cannot be completed by swapping any available numbers around. The problem is not in those final boxes.

Somewhere earlier, you built the wrong foundation.

Now you have to backtrack until you find where your entries and the puzzle’s answer diverge. Software can fail the same way.

A mistaken assumption becomes an implementation. Tests are written around that implementation. Documentation describes it. Other modules begin depending on it. New tooling protects it. Future work treats it as established architecture. Eventually the project reaches an impossible square.

None of this is new, I just met it the hard way. The closest named idea is technical debt, a metaphor Ward Cunningham introduced for borrowing against your future self. The usual telling is about shortcuts you take knowingly, with some sense of what you are deferring. What I am describing is worse, because the loan is invisible. You never chose it, you cannot see the balance, and you find out the size of it at the moment you can least afford to.

The serious damage comes from building on top of being wrong before anyone notices. That is why going slow does not mean typing slowly or refusing automation, but controlling the rate at which uncertainty accumulates.

You can let the AI work quickly; you should be much more careful about letting unverified assumptions compound quickly. Make bounded changes. Understand what changed. Check important assumptions before stacking new assumptions on top of them. Keep the system close enough to known-good states that backtracking remains possible.

In one of my early vibe code projects, I would zip my repo, upload it to a chat window, ask for review, then generate snippets of code and manually paste them into the repo open in an IDE.

After a while, I got really fast at this. I had custom mouse mappings on a ten-button mouse, and the whole replacement collapsed into one sequence I could run without looking: copy, paste, enter, copy, paste, undo, redo, save. I had ChatGPT create an anchor system where the new code block was listed along with the line above and the line below, and presented against the original block being replaced.

Copy the anchor. Paste it into the IDE search and press enter, which snaps to that exact block and leaves it highlighted at the right indentation. Copy the new code, paste it over the selection. Undo, to see the old version back in place. Redo, to confirm the new one sits at identical spacing. Flick between the two until nothing moves but the code, then save. Three to five seconds a replacement, with the muscle memory of a musician and an avid gamer. Every one of those steps was careful. Not one of them protected me from what was actually happening. Eventually, the codebase got big, and to speed up, I would upload the zip to five or ten instances, scoping each one to a task, like checking test accuracy, building new systems and such. With ten chat windows working independently in the same repo, you can imagine how fast the codebase was changing. After maybe a week of this workflow, it was ruined. Each of these instances was working in a vacuum, building independent function names, recreating the same tool countless times, breaking dependencies. To make matters worse, in my speedy copy/paste workflow, certain instances’ fixes would overlap, and it would be a Frankenstein of two systems, different key:value names, different approaches mixed and broken. The foundation was weak, and then I built a glass castle on top of it.

The truth? I didn’t even know what a key:value mismatch was. I didn’t think AI would reinvent tools that are already available to it. I didn’t realize that the different instances would be making these types of errors at all. I didn’t know what I didn’t know. Instead of giving up, I studied. Why did this happen? Now how do we stop it from happening again?

The goal is durable progress. Only go fast when you have earned the right to go fast.

Never let the code get so far ahead that one bad square requires rebuilding half the puzzle.

Ask Questions

A beginner often approaches an AI coding system primarily through instructions.

“Build this. Fix this. Add this. Refactor this. Make this faster.”

There is nothing wrong with instructions. Eventually someone has to perform the work. But one of the largest improvements you can make is learning to spend more time investigating before modifying.

Something is broken.

The instinct is: fix it.

Try instead: determine exactly what is failing.

Those two approaches create very different workflows. The first encourages movement. The second encourages understanding.

Before making a change, seek to understand what the change’s purpose truly is. What symptom is it treating? As the AI explains it to you, it is also explaining it to itself by adding this info to the context window.

You are educating yourself, and your system, at the same time.

An important AI skill is finding out what to find out. You will encounter questions you know you cannot answer. Those are manageable because you can investigate them. The more dangerous gaps are the ones you do not realize exist.

AI is extremely useful here. It can help map unfamiliar territory, explain concepts, identify terms worth researching. It can challenge an interpretation and propose failure modes.

The purpose of asking questions is not to replace one form of blind confidence with another. Questions help you build a model of the system that is richer than the immediate task. Over time, your questions change.

Early:

“Why is this error happening?”

Later:

“Why do errors like this happen and how can we prevent them? Is this a single piece of a class of similar errors, or a single-instance issue? Explain in plain terms exactly the symptom this error causes, and also explain how the error causes that symptom in technical terms, explaining any jargon. Dispatch a subagent to check the class/instance issue and continue with the rest.”

Early:

“Are the tests good?”

Later:

“Are the tests we built to guard these symptoms scoped properly to report truthful and reliable data? Are we missing anything that could cause a false reading? Did you prove the test by mutating the target to observe the test going red?”

Do not be embarrassed by what you do not know.

Be afraid of becoming so focused on the current task that you stop looking for it.

Have a Vision

Have a reason to build something other than “I want to use AI.”

Wanting to use AI is a perfectly good reason to experiment. It is not enough to direct a serious project. At some point, the idea must come from you. Not necessarily the exact implementation, but you need an objective you understand well enough to recognize when the project is moving toward it and when it is merely moving.

This matters because AI can generate plausible next steps forever. What should we build? What feature should we add? What should we work on next? Each answer can sound reasonable.

A project can therefore remain in motion while losing any coherent destination. Having a vision means retaining decision authority. Use AI to improve your decisions, not to relieve you of having to make them. Ask it for alternatives. Ask it to challenge your assumptions. Ask it to investigate tradeoffs. Ask it to find weaknesses in your plan. Ask it to show you approaches you did not know existed. Then decide.

The difference is subtle but important.

AI systems are very good at improving the thing in front of them. That can become dangerous in a long project.

Suppose the current system is X and the desired system is Y. Development may look like:

X → X1 → X2 → X3 → Y

But every intermediate state becomes its own reality. Without a strong conception of the destination, the project can turn into:

X → X1 → improved X1 → refactored X1 → documented X1 → highly polished X1a

The work may be excellent. The project may still be wrong. A good agent can make the wrong thing magnificent.

Vision gives you a reference point outside the current repository. If you cannot picture what the architecture is trying to become, every local architectural decision becomes harder to judge.

That does not mean defending your initial plan forever. A vision is not a commitment to your first solution. New evidence should change the route. Sometimes it should change the destination.

Vision means preserving intentionality while learning. The original architecture can be wrong and you can still have a vision. You can delete months of work and still have a vision. You can discover that the product should become something very different and still have a vision.

You cannot steer toward a destination that does not exist.

Drive Everything

You built the car, now drive it.

This principle is simple: never accept the existence of an artifact as evidence that the artifact actually works. Use what you build. Operate it, exercise it, break it. Make it encounter the real conditions it claims to handle.

AI-generated software often reaches a convincing intermediate state. The function exists. The test passes. The interface looks correct. The agent says the feature is complete. Those are useful signals.

They are not reality.

A training script can run successfully without batching data correctly. A checkpoint feature can write checkpoint files without being capable of resuming a real interrupted job. A backup process can complete without producing something you can actually restore.

A report of success is not an observation of success. This is one of the easiest traps in AI-assisted development: the same system writes the code, writes the tests, updates the documentation, reviews the result, and reports that everything is working. All of those artifacts may agree with one another. They can still be wrong in the same direction. The escape is contact with reality.

If the test matters, intentionally break the behavior it claims to protect.

Does the test turn red? Fix the behavior. Does it turn green again? Now the test has demonstrated something much more meaningful.

  • If checkpoint restoration matters, start a real job. Stop it. Resume it. Inspect what actually resumed.
  • If clean installation matters, install from a genuinely clean environment.
  • If a rate limiter matters, hit the limit.
  • If failure recovery matters, cause the failure.
  • If a system claims to support a workflow, perform the workflow.

Do not only test what the system does when everything is friendly. Drive it over potholes. This matters because some unknowns cannot be discovered through reasoning alone. You do not always know which question is missing. Operating the system creates collisions with reality.

Those collisions reveal assumptions you did not know you were making. The test assumed a dependency would always answer. The training pipeline assumed a batch shape that only worked with your first dataset. The recovery code assumed a file existed. The user flow assumed an ordering nobody specified. The interface assumed that a value would never be empty.

You may not have thought to ask about any of those things. Driving the system makes them visible. This is also why “Did that work?” is often a weak final question.

Trust means tying important claims to evidence that corresponds to reality.

Document Everything

Every meaningful project develops knowledge that does not live cleanly inside the code.

If the answers exist only inside a chat session or one person’s memory, they are already halfway to being lost. That doesn’t mean bury the project under thousands of pages of prose. Bad documentation can become another source of confusion.

Document the things future work needs in order to avoid repeating the past. The code can often tell you what the system currently does. It is much worse at telling you why the system ended up that way.

That knowledge is precious.

AI-assisted development makes this particularly important because context is disposable. One session discovers a subtle constraint. A later session sees only the code. The constraint looks unnecessary. The agent helpfully “simplifies” it, quite possibly during a cleanup you asked for.

The old failure returns, and nobody knows why.

The problem was that the project failed to preserve what it had learned. A strong repository develops institutional memory. Important decisions have a durable home. Known limitations are visible. Rejected approaches have explanations when rediscovering them would be expensive. Architecture has enough rationale that a future contributor can distinguish deliberate complexity from accidental complexity.

Failures produce artifacts. If you spend six hours discovering that something breaks under condition X, do not end the work by simply fixing X. Capture the lesson. Write the regression test. Record the assumption. Add the check. Create the hook.

Make the repository harder to fool in exactly the same way tomorrow. Documentation is therefore not separate from engineering. It’s how engineering compounds.

Without memory, every new AI may rediscover the same lessons repeatedly. It may confidently retry abandoned ideas. It may remove protections whose purpose is no longer visible. It may treat current code as though current code represents original intent. Durable project knowledge reduces that drift.

There is another benefit: writing forces clarity. If you cannot explain why a design decision exists, you may not understand it as well as you think. If the reason for a security boundary sounds weak when written down, investigate it. If nobody can define what a test is meant to guarantee, perhaps the test needs reconsideration. If two documents describe incompatible versions of the future architecture, you have discovered a problem before code had to discover it for you.

Documentation is not merely history. It is an instrument for thinking. Document success, failure, assumptions, uncertainty, important decisions, what is temporary, and what must not be casually changed.

A repository should become more knowledgeable as the project advances. Every difficult lesson should leave something behind.

Stale documentation can cause problems if categorized wrong. Historical Records and “cognitive documents,” meant for the AI to reason from and store knowledge in, should be separated and clearly labeled. Make your agent aware of what can go stale, and build systems hardened against it.

If you do this, you have the start of what is called a “Harness.” You can organize and scope the AI immensely with documents and folder structure tied to model tools and systems like hooks and custom skills and subagents. All of these “AI tools” are documents with specific formatting, in a specific place.

That reframes what documentation is for. It used to be a courtesy you paid to the next human, and it was always the first thing dropped when the work got busy. Inside a harness it is not a courtesy, it is infrastructure. The documents are what the model reads, what scopes what it may touch, what it reasons from before it writes a line. The writing became the tooling.

I wrote this section before I found out that Peter Naur had written it in 1985. His essay “Programming as Theory Building” argues that the real product of programming is not the code but the theory in the programmers’ heads: why the system is shaped this way, what was tried and rejected, which constraints are real. His harder claim is that this theory can never be fully written down, and that when the people holding it disperse, the program is effectively dead even though every line of it still compiles.

That was an argument about teams. It reads differently now. Every context window that closes is a team dispersing. The theory gets built and lost several times a week, and the code left behind looks exactly like code that still has a theory attached to it.

There is a live conversation about what Naur means for agentic development, and it is worth finding. I do not think documentation solves his problem, since he was explicit that nothing can. But it is the best instrument we have for slowing the loss, and the repository is where the slowing has to happen.

Periodic review and upkeep of these documents is an ongoing task.

Trust Nothing

Shocking.

We just talked about building a workflow that is essentially “trust the AI to build as I direct, and own the system myself.” Just earlier, I said that software can be produced faster than a human can fully understand it. That means that to use AI is to trust AI. To trust AI to teach you the correct information. Trust in AI that when it builds the code, it’s taking the best path. Trust that when something appears to work, and the AI says it works, the system actually works.

This trust, like all trust, must be earned. A lesson that is important to learn early:

You earn trust by building a driven, tested, red-teamed, repeatedly exercised system. Strong repo structure and internal tools make a big difference as well. Even then, it’s imperative you never trust it completely.

Not the AI, the test, the benchmark, the error message, your own memory: nothing. Not one thing gets blind trust.

Everything can provide evidence; however, the evidence may be incorrect.

A test passes. Good.

  • What behavior does that test actually exercise?
  • Could the implementation be badly broken in another way while the test remains green?
  • Could the test itself be testing a mock rather than the real behavior you care about?
  • Could both test and implementation share the same mistaken assumption?

A security scanner reports no findings. Useful.

  • What classes of issue does it detect?
  • What does it not inspect?
  • What configuration did it run under?
  • Does the scanner check against a static list that is stale?
  • Can the scanner see new material?

A benchmark improves. Promising.

  • Does the benchmark correspond to the behavior you actually care about?
  • Did anything leak?
  • Did another capability regress?

An AI reports: the issue has been fixed. Maybe it has.

  • What happened outside the model’s explanation that demonstrates this?

This is where Trust Nothing and Drive Everything meet. Trust Nothing says claims require evidence. Drive Everything creates evidence.

Even evidence should be interpreted carefully.

Engineering confidence is cumulative. One test may tell you something; a second, independent kind of observation may tell you more. Operating the real system may tell you even more. A failure injection may tell you something the happy path never could. Multiple pieces of evidence can build a justified picture.

This protects you from a subtle trap: self-consistent wrongness.

Code, tests, documentation, and agent commentary can all agree because they inherited the same assumption. Agreement is not independence. Sometimes you need to reach outside the current frame, break something deliberately, use a different tool, or inspect the raw output.

This requires humility. Strong engineering often sounds less certain than weak engineering because strong engineers understand more ways the claim could fail.

“Looks good”

becomes:

“The relevant tests pass, the failure case was reproduced before the fix and no longer reproduces afterward, the regression test fails when the fix is removed, and we exercised the real workflow.”

That is earned confidence, from a system that has earned trust, but was never fully trusted.

Never Give Up

This is not a depressing list. It is not a list of reasons you are unqualified to build software. It is a path, a method, learned through trial and error, deep research, persistent effort, and stubborn focus.

Many people will begin with almost none of these habits. I bet more than half the vibe coders reading this go right back to the same method that has worked for them up until now. However, I’m sure they will land on these tenets of AI engineering through trial and error.

  • You build something, it breaks, you learn logs.
  • You deploy something badly, you learn environments.
  • A test lies to you, you learn how to test the test.
  • A project becomes impossible to modify, you learn architecture.
  • An agent forgets an important constraint, you learn project memory.
  • You repeat the same instruction fifty times, you realize the instruction should be tooling.
  • A dependency surprises you, you learn dependency management.
  • You discover that you have spent a week solving the wrong problem, you learn to ask better questions.

One mistake at a time, your relationship with software changes. Eventually you notice that you are no longer thinking the way you used to.

You investigate first. You recognize dangerous uncertainty. You know when you need to understand a concept before accepting an implementation. You ask for evidence. You maintain known-good states. You preserve architectural intent. You know how to backtrack.

You recognize when the final three Sudoku squares are probably not the real problem.

Once you can recognize these issues, an efficient path to mitigating them appears: Go Slow, Ask Questions, Have a Vision, Drive Everything, Document Everything, Trust Nothing, and Never Give Up.

I learned many of these lessons the hard way, by the ultimate teacher: failure.

If you are a vibe coder, you will fail.

Read that again and internalize it.

Never give up.

Learn from each failure, don’t get discouraged, and fight through the bouts of fear and uncertainty that undoubtedly enter your mind from time to time. The feeling of dread as you reach a problem you don’t understand. The absolute shock of seeing “I have damaged your files” from your agent. Reading about labs independently reinventing things and turning whole projects into features. Seeing someone else building the same product as you for the first time. It can shake you to the core.

Never give up.

That does not mean never abandon anything. Good engineering requires abandoning things constantly. Delete bad implementations. Discard bad assumptions. Replace bad architectures. Kill features that should not exist. Start over when the foundation is wrong. Do not confuse persistence with loyalty to sunk cost. Sometimes refusing to give up means “Try it again, but this time we know more.” Sometimes it means “This is not the answer. We proved it. Log it and move on.”

Failure is where much of the engineering education happens. Use it, investigate it, document it, change the process, then continue. You are not trying to reach a point where you never make mistakes.

That point does not exist.

Become the kind of builder who discovers mistakes earlier, understands them more deeply, prevents them from silently compounding, and converts each one into better judgment.

That takes repetition. It takes curiosity. It takes humility.

And sometimes it takes stubbornness.

The Path

AI removes ignorance as a barrier to action. That is one of its most extraordinary properties. You can now attempt things long before you possess all of the knowledge traditionally required to attempt them.

The barrier did more than stop you. It also told you when you were out of your depth. Removing it removed the warning too. That is what these principles are for.

  • Go slow enough to keep uncertainty from outrunning understanding.
  • Ask questions, especially when you are not yet sure which questions matter.
  • Have a vision so the project has a destination outside whatever happens to exist today.
  • Drive everything so claims collide with reality.
  • Document everything worth learning so the project does not forget.
  • Trust nothing without understanding what the evidence actually establishes.
  • Never give up on learning what lies underneath the thing you are trying to build.

You do not need to know everything before you begin. You should begin. Build something slightly beyond your understanding. Find the edge of what you know. Study it. Use the AI to teach you. Read when you need to read. Break the system intentionally. Inspect what happened. Improve the tools around the work. Write down what you discover.

Over enough iterations, something changes. The AI may still write nearly every line, but your questions are different. Your standards are different. Your repository is different. Your ability to detect weak evidence is different.

That is the path from vibe coder to engineer.

Addendum

If you find yourself standing at the edge of a decision: Should I try to learn to use AI? Am I good enough? Am I smart enough? What if it’s too hard? What if I fail?

Would I just be another hyped-up vibe coder?

Know this: it doesn’t matter.

Society has made it seem as if certain accomplishments are “out of reach” for certain individuals. AI is proving otherwise: when the odds are even, we can all create great things. The barriers are starting to come down.

Vibe code like this for twelve months, and you will surprise yourself. You will be able to speak from a place of knowledge, of lived lessons, of intuition and experience.

Write that book you always wanted to. Make that funny cartoon you and your friends laugh about. Explore that business you thought of. Make that song you always hear in your head. Create the app you wish existed. Push the boundaries of knowledge. We have a very powerful, newly obtained opportunity to change things in this world by deciding to change them and putting in effort.

Revisit the things that you wrote off for yourself, “for another life.” You’re worth more than your labor; it’s your intellect that is unique and irreplaceable. It’s the thing that only your mind can create. AI is a tool that helps you reach the vision only you can see.

The only thing standing between you and these creations is the willingness to step up to the easel. It used to take a lifetime to even get the paints and canvas set up. Now, we go in and “will our ideas” into existence. If the painting is in your mind, you created it.

Use it, really try, and you will see: this invention will change everything.

This is antibiotics. This is steam. This is metalworking. This is agriculture.

Shit. This is fire.

And we just found out how to make it by rubbing two sticks together. From that moment on, the human experience was radically different. “Society” was radically different. Everything changed, in a way that made things that once seemed important worthless, and things once seen as worthless important.

From the fire we discovered by rubbing those sticks together, we built the impossible.

The first people to master the friction fire technique must have seemed like wizards.

You are holding the sticks in your hands right now, as you read this.

Go rub them together.