Vibe Coding: I Read Less and Less of the Code AI Writes Line by Line — But I Still Rely on Docs to Understand It

🇨🇳 中文版

Lately I've been leaning on AI for development more and more.

Inline completion in the editor, coding agents in the terminal, hosted models of every shape, locally hosted open-weight models — I've cycled through most of them.

In the beginning I was careful with AI-generated code.

After it wrote something, I felt I owed it a proper review.

Slowly that stopped being realistic.

Ask AI for anything moderately complex today and it touches a batch of files at once. The diff is easily several hundred lines, sometimes over a thousand.

I run several projects in parallel — Python, Go, Rust, Node, Java. If I opened up every change in every project and read it line by line, the throughput AI bought me would be spent again on my own review.

So my approach has changed substantially.

I no longer treat "reading what the AI wrote" as my primary way of verifying it.


There Is Simply Too Much Code to Read

When I wrote code by hand, the volume of code roughly matched my capacity to understand it.

Build a feature yourself and you know, more or less, why every variable and every function is the way it is.

That is no longer true.

AI turns an idea into working code very quickly.

One request can modify the frontend, the backend, the database, the config files, and the tests.

The codebase swells almost instantly.

If you still insist that:

once AI finishes, I must understand all of it.

then you are back in the old model.

And probably more exhausted than if you had written it yourself.

So now I only do a coarse review.

Look for anything obviously off.

Look for a file that suddenly got very long.

Look for a simple feature that got implemented in a baroque way.

If the structure is starting to slip, I tell AI to split it up and refactor.

But if nothing structural is wrong, I don't spend hours reading line by line.

Because I have a more direct way to verify.

Just use it.


UI Is a Test Harness to Me Now

I mean something different by "UI" than product teams usually do.

When I start a tool project now, I don't care much whether the first version looks good.

You could go further:

The UI is just an entry point that lets me actually operate the system.

What I care about is whether the whole stack runs end to end.

Whether the data model makes sense.

Whether the API makes sense.

How the frontend and backend cooperate.

How the agent calls tools.

How tasks execute.

How state flows.

How failures are handled.

None of that is easy to feel by reading code.

But wire it together behind something you can click, and the problems surface fast.

Click once and you know.

Run it once and you know.

Change a parameter and you know.

Deliberately walk an error path and you know.

So for me now:

UI is a functional verification tool first and a visual design second.


Get the Foundation Right; Make It Pretty Later

This is one of the biggest differences between how I build now and how I used to build.

For tool projects, I now write almost everything as a command line first.

And I deliberately cut it into stages: build, draft, publish, record, verify.

Each stage can be re-run on its own. Each writes its result to a file, and the next stage reads that file.

The benefit isn't elegance — it's locatability. When something breaks I know immediately which stage broke, and I can re-run just that one instead of tearing down the whole chain.

Only after I've used that chain for months and confirmed it does real work every day do I think about putting an interface on top.

And doing the interface late makes it more accurate.

Because by then I know:

which features are actually needed.

which actions nobody ever uses.

which steps can be merged.

what should be hidden.

what is worth building further.

So my habit has become:

Make it work, make it usable, then make it beautiful.


A Lot of Design Emerges From Use

This may be the biggest change Vibe Coding has made in me.

I used to think design had to be settled up front.

In reality, a lot of it cannot be settled up front.

Especially when you're building a tool for yourself.

You only find the friction by actually using it.

Two operations you thought should be separate turn out to merge perfectly fine.

A feature you thought belonged here turns out to be more natural somewhere else.

I have a contacts assistant I use every day. Each contact has a handful of actions under it: AI draft, AI persona, local notes, export.

The ordering of those four, the de-duplication rules, whether clearing needs a confirm step, how timestamps and scrollbars line up — not one of those was designed up front.

Every one of them came out of installing it on my phone and using it, one round of changes at a time.

Sitting at a desk, you cannot reason your way to "this button belongs second."

Sometimes new ideas show up mid-use:

Could this become a skill?

Should this flow be a task?

Could the agent do this step on its own?

Then you change it again.

Use it some more.

Find the next problem.

Change it again.

So my process no longer looks like the traditional:

design → build → test → ship

It looks more like:

design, implement, use, and test all interleaved.

A lot of the design isn't decided up front. It grows out of real use.


Boundaries Are Not Held by Reading Code

Some problems only show up once you run the thing. Reading the code, paradoxically, is where you reassure yourself.

Back to that contacts assistant. It's local-first, and the AI features get exactly three things: a name, the carrier region derived from the phone prefix, and what you're trying to do.

Phone numbers, email addresses, and the address book itself never leave the device.

That boundary is in the code. But the moment it became true was not the moment it was written — it was the moment the feature actually ran and you confirmed that those three things were all it took.

Reading the code, it's easy to believe you've held the line.

So my habit now is: run the checks first, then decide the next step.

If it can be checked automatically, don't check it with your eyes.

Running it is cheaper than reading it.

I've even turned this back on the AI itself. Inside the workbench I added a closed PSE loop — a Planner that breaks down the task, a Specialist that does the work, and an Evaluator that independently accepts or rejects the result (I wrote up the pattern in AutoGen PSE architecture). The Evaluator's constraints are the interesting part:

It has no write permission, doesn't "discuss" with the Planner, and only issues a verdict — PASS / PARTIAL / FAIL / BLOCKED — based on evidence it gathered itself.

Its ground rule: don't trust the Planner's account; only evidence you gathered yourself counts. Reading its role definition you'd almost laugh — "Planner said it's done is not evidence" and "looks fine, PASS" is forbidden. Isn't that just "don't read the code and call it good; run it" written as a directive for the AI?

A plainer example. The weekly-investment-review skill starts with a data health check: it runs the calculation pipeline and scans for distorted values like an annualized return of 1e19% before it goes any further. That kind of distortion used to be invisible by reading code — it's a tangle of CSV files and calculation scripts, no eye can track it. Run the check first and the problem surfaces immediately. Figuring out who should judge is worth more than reading the code to the end.


Running Isn't Understanding — But Understanding Can Be Built With Mechanics

This invites a fair question: so you don't read the code at all? In the short term you don't understand the project; what happens when the team asks you to share?

I'll be honest: verifying by running is not the same as grasping the whole.

Judging whether the system works by "running" it, and building a mental map you can recite by "reading" it, are two different things. The former verifies outcomes and boundaries. The latter is what lets you explain something — and that's exactly what team collaboration, mentoring, and sharing depend on.

My choice isn't to pretend I can read it all. It's to admit I can't, and then cover the "able to explain it" part with a lightweight mechanism.

Every time AI finishes a chunk of work, I have it produce three things:

  • An architecture doc. Not line-by-line code, but a digest of decisions — why this part is split this way, why this approach was chosen.
  • An updated README. So the project always has a self-describing entry point: what it is, how to use it, how to run it.
  • A TODO trail. Recording where "it might change next week," so the evolution path leaves a trace.

Together those three answer three questions: why (architecture doc), what (README), where it's going (TODO).

So "I don't understand the project in the short term" is real — but it's short-term, and this mechanism covers it. Running holds quality; the document trio holds "being able to explain it." I do both halves with different methods, rather than trying to prop it up by reading and memorizing.

When I have to talk face to face, I usually have AI generate a talk outline from those three: what changed this week, why it changed, what's next, what snags we hit. With a skeleton in hand, a share isn't stalled on "I can't recall the details."


I Am Increasingly a Test Engineer

This is an odd shift.

Before:

the programmer writes code, then tests it.

Now:

AI writes code, and I test it.

And I don't only mean unit tests.

More often, I just become a real user.

Walk the whole flow from the top.

Click.

Click again.

Try a different operation.

Deliberately type something wrong.

See what the system does.

When it's not what I expected, I tell AI:

this is wrong.

this state should be handled like this.

this interaction doesn't make sense.

there should be another action here.

And AI goes and changes it.

So these days I often don't need to investigate where in the code the problem is.

Describe the problem clearly and let AI locate and fix it.

One condition: describe the symptom, not the cause you're guessing at.

"Clicking export does nothing" is a symptom. "I think the file path is wrong" is a guess — and it sends AI off to fix a part that was never broken.

If it fixes the wrong thing, I give feedback again.

This is very close to a continuous development loop.


Writing Code Is No Longer the Hard Part

AI writes code fast enough now.

The hard part is judging whether what it produced is what you actually wanted.

Vibe Coding isn't about "not needing to understand code" — it's about not needing to spend all your understanding time reading line by line. What you actually need to master is system structure, boundaries, verification, and judgment.

But "verification" here doesn't have to mean manual line-by-line review.

At least not for the way I work. It mostly means:

run it, then actually use it.

Code is the implementation.

The running system is the result.

So my bar for code review has changed too.

Not "confirm every line is correct."

But "has the overall structure slipped?"

Is a file getting longer and longer?

Is a module getting more and more complex?

Is there obvious duplication?

Did a simple requirement drag in a pile of unnecessary machinery?

When I find those, I hand them back to AI.

Everything else is left to the run and to real use.


I Am Assembling a Workbench

Once these patterns repeated often enough, I started pulling them into one place.

A workbench for my own use (the one I'm tinkering on, resolve-studio): the LLM backend, the toolset, the agent loop, skill packs, MCP servers, approvals, the sandbox — all pluggable parts.

Under the hood it runs on Cordis, a dependency-injection container. The core idea: everything is a plugin. The LLM backend is a plugin. The tools are plugins. The agent loop is a plugin. Approvals are a plugin. Even the frontend — CLI or web — is a plugin, assembled by one YAML config.

So "swap in a different config and assemble a different runtime" is literally true:

Same parts, but one config gives you a web app and another gives you a CLI; one uses a hosted model and another runs offline against a mock.

The runtime code doesn't change, yet the capabilities are entirely different. A concrete tell is Fast Path — for deterministic inputs like plain arithmetic (3+47), it never touches the model; a pure rule-based preprocessor short-circuits and answers instantly. That's when it hit me: whatever can be computed deterministically shouldn't be left to AI to guess at. It's the same principle as "what can be checked automatically, don't check by eye" — except now applied to the AI itself, which gets to skip the model call entirely.

But what's more interesting is that none of these were "designed" — they grew out of building and thinking, emerging on their own. At first I wasn't setting out to build a "fast-path system"; I just felt "whatever can be computed deterministically shouldn't be left to AI to guess," and wrote Fast Path along the way. As I kept using it, the built-in matchers couldn't cover the long tail, so I had the model write its own detectors (codegen) for reuse. Then good little plugins shouldn't just sit in a data directory, so I naturally added a "graduate" step to promote them into source. That pipeline — develop → persist → reuse → graduate — surfaced through constant use and thought. The first time you ask "is 2024 a leap year," the model writes a detector on the spot; afterwards the same kind of question doesn't even call the model.

I don't really believe hard, goal-staring, stress-driven effort produces good ideas — that kind of pressure tends to squeeze out clichés. Instead, when you're holding onto something you genuinely care about and you're relaxed and open, good ideas surface on their own. Once Vibe Coding drove the cost of throwing things away low enough, I could afford to build slowly and let things grow, rather than rushing to get the thing in front of me done and over with.

I should be honest, though: this rhythm has a precondition — you have to have something that's yours to decide. When you're building your own tools, your own work, the pace is yours to control. If external metrics and deadlines are staring over your shoulder, it's hard to relax enough for ideas to grow; in that situation I'd probably fall back on a more efficient, but tighter, way of working. It's a different rhythm for the part of work you can actually own — not a one-size-fits-all prescription.

I go deeper into this "everything is a plugin, every variable stage pushed into config" architecture in a separate post, Cordis pluggable agent runtime.

It is nowhere near settled. Plenty of it may look different next week.

But one thing is certain: it did not start from a finished blueprint.

It grew out of using it the way I described above. The weekly-investment-review skill is a good example — I ran that data health check by hand for a while, and once it felt natural to automate it, I had AI build the chain, which in turn grew a PSE trio to cover quality.


My Vibe Coding, As It Stands

If I had to summarize how I build now:

Pick a direction.

Let AI produce a runnable version quickly.

Use it myself.

Hit a problem, hand it to AI.

New design ideas show up during use.

Keep building.

Keep testing.

Keep adjusting.

Once the architecture and the features settle, go back and make the UI/UX good.

So I don't spend a long time designing everything up front anymore.

And I don't read every line AI generates anymore.

One thing, though, has never really changed — I build one block at a time.

That's how I worked when I wrote code by hand: finish one feature, verify it passes, then write the next. Whether it's solving math problems or engineering, it all comes down to "go step by step, verify each step before the next" — and the problem stays manageable.

Vibe Coding continues that ground rule, only swapping in AI for each block's implementation: I hand AI one block at a time, confirm each one is good, then let it move on to the next — rather than dumping the whole requirement on it at once and letting it write everything in one shot.

The writing itself I hand to AI. But the rhythm of "finish one block, verify it, move on to the next" is something I've had all along — AI didn't change it.

design, build, test, and iterate all at once.

AI turns ideas into something real, quickly.

What I own is the ongoing judgment:

is this actually what I wanted?

That, I think, is the biggest change Vibe Coding brought me.

Not that I write code faster.

But that I can turn an idea into something runnable, operable, and testable at very low cost.

And then let real use grow the idea from there.


Finally

So if you ask me to explain Vibe Coding now, I wouldn't just say:

"it's getting AI to write code for you."

Because what AI really changed isn't typing speed. It's the cost of throwing it away and starting over.

Doesn't work well? Change it.

Design is off? Adjust it.

Wrong direction? Start again.

That workbench I'm assembling is a byproduct of exactly this process.

It isn't a design document that was finished before the work started. It's something that got shaped by using it, round after round.

Get the idea running first, then let it get better.

That's my Vibe Coding these days.


Related Reading

If you don't read AI's code line by line, how do you guarantee quality?

Two gates. The first is a coarse structural pass — a file that suddenly got long, a strange abstraction, a pile of dependencies dragged in for a small requirement. Spot those and have AI split them up itself. The second is verification by running: get the system up and walk the happy path and the failure path as a real user would. Together these answer “does it work, and will it break something” — not “is every line elegant.”

When you hand a problem back to AI, how should you describe it?

Describe the symptom, not the cause you’re guessing at. “Clicking export does nothing” and “tapping twice in this state gives me a blank screen” are symptoms. “I think the file path is wrong” is a guess, and it sends AI to fix something that was never broken. Give it plenty of symptom — the sequence of actions, the input, what you saw, what you expected — and let it find the cause. If it fixes the wrong thing, give feedback and go around again. That loop is far faster than reading the code yourself first.

Isn't it wasted effort to build a rough version and redo the UI later?

Polishing a UI before the requirements have settled produces more rework, not less. Once the data model, the task decomposition, or the state flow shifts, the pretty interface usually has to be torn down with it. A rough version costs almost nothing and its only job is to make the system operable so problems surface. After real use you know which features anyone touches, which steps should merge, and what should be hidden — and the UI you build then is based on facts instead of imagination.

What kinds of projects does this way of working suit?

Tools you build for yourself, internal systems, prototypes that need an idea validated fast. The test is simple: can you be the real user? If you use it every day, usage feedback is the cheapest test signal you’ll get. It fits less well where requirements come from outside, correctness demands are extreme, and nobody can keep exercising it in real use — there, “run it and see” doesn’t give you enough coverage, and you need to go back to formal tests and review.

AI can run shell commands and write outside the project directory. How do you keep that safe?

With boundaries rather than trust, and boundaries have to be enforced mechanically: commands run inside a sandbox with restricted writable paths and network access, file writes land in an isolated directory by default, and sensitive operations pause for human approval. Just as important is when you verify — a privacy or data boundary only counts once the feature actually runs and you’ve confirmed what it took. Reading the code, it’s easy to believe you’ve held the line.

Comments

Leave a reply

Your email address will not be published. Required fields are marked *

AI Engineering Practices & Open Source Projects

Shop Web Chat Nsbp About Privacy

@ 2026 ESN
沪ICP备2024079226号-1   沪公网安备31010502007082号