Guide

Vibe coding iteration taste lives in the commit graph, not in your vibes

Every essay on taste in vibe coding tells you to develop a clearer intent. Fine. None of them tell you the part the tool has to do first. Taste is the small judgment between two prompts. That judgment is impossible if the system cannot remember and restore the previous version exactly.

M
Matthew Diakonov
7 min
Direct answer (verified 2026-05-02)

Iteration taste in vibe coding is the small judgment you make between prompts: keep this output, roll back to two prompts ago, fork off in a different direction, or cancel mid-stream. The skill only develops when the underlying tool gives you a real commit graph (one commit per prompt) plus a definite undo (byte-exact restore from a SHA). Without that, every iteration is a guess against a memory of how it used to look, and the taste muscle never gets the reps.

Term coined by Andrej Karpathy, February 2025 (Wikipedia). The substrate claim is verified against the mk0r source at src/core/e2b.ts lines 1635-1814.

The thing every other guide leaves out

Karpathy coined "vibe coding" in February 2025. Every essay since then that uses the word "taste" in the same sentence treats taste as a property of the human: clarity of thought, sharpness of intent, ability to recognize good output. That is correct. It is also the easy half of the problem.

The hard half is what the tool has to do for the human eye to even sharpen in the first place. Photographers who shot film got good when contact sheets got cheap. They could lay six frames next to each other and pick. The eye learned by comparison. If your only feedback is "I think this was a touch better than what I had earlier", you do not converge on taste. You converge on the first thing that did not feel obviously bad.

For vibe coding, the equivalent of a contact sheet is the prompt-by-prompt history graph. The next sections trace what one looks like in mk0r, where it is implemented, and the small rule that makes the difference between a graph you can read and a graph you cannot.

The substrate: one commit per prompt, an active pointer, a fork-don't-merge rule

Inside the VM that runs each session, mk0r initializes a real git repository at /app on first boot. Every time the agent finishes a turn, commitTurn (src/core/e2b.ts line 1635) runs:

  1. git add -A picks up everything the agent touched.
  2. If git diff --cached --quiet passes (the turn produced no real change), the function prints NOCHANGE and exits without creating a commit. Cancelled and no-op turns leave no trace.
  3. Otherwise, git commit, capture the SHA, and append it to the in-memory historyStack on the session.
  4. Set activeIndex to the new tip and persist the session.

Source: src/core/e2b.ts lines 1635 to 1689. The same file declares the related undoTurn (1731), redoTurn (1744), jumpToSha (1757), and getGitHistory (1777).

The session keeps two pieces of state about history: historyStack (an ordered list of SHAs) and activeIndex (a pointer into that list). undoTurn decrements the pointer, runs git checkout against the previous SHA, and commits the restoration with --allow-empty and a message of "Undo to <short-sha>". The undo itself becomes a new commit, which is what makes redoTurn (and undoing the undo) possible.

The reason this matters for taste: every prompt you have ever issued in the session has a name. You can point at it. You can click Revert on it. You do not have to describe the previous version back to the AI from memory.

The slice-on-fork rule (the part most tools get wrong)

Here is the small piece of code that does the most work. When commitTurn fires and the session's activeIndex is not at the tip of historyStack (meaning: the user pressed Undo or Revert, and is now issuing a fresh prompt from a non-tip commit), the function does this before pushing the new SHA:

L1671

historyStack = historyStack.slice(0, activeIndex + 1); historyStack.push(sha); activeIndex = historyStack.length - 1;

src/core/e2b.ts lines 1671 to 1679

In English: the abandoned forward path is dropped from the timeline before the new commit is appended. If you undo two steps and then issue a new prompt, the two commits you walked back over disappear from your visible history. They still exist in git's reflog at /app inside the VM, but the surface the user looks at when deciding "what should I do next" is clean.

This sounds opinionated, and it is. The argument for keeping every branch alive is that you might want to come back to one of the abandoned attempts. The argument for slicing it is that a graph with seventeen visible branches is unreadable, and an unreadable graph kills the comparison instinct that produces taste. mk0r picks the second.

The user-visible consequence is the rule: you cannot iterate in two directions at once. You commit to a fork. If the new direction does not work out, the way back is not "switch branches", it is "click Revert on the SHA I came from". The shape of the timeline forces a sequential decision instead of a parallel one.

What "undo" has to mean before taste can exist

The bar is not "undo exists". The bar is that undo gives you the previous state byte for byte, fast enough that comparing it to the current state is cheap. Toggle the panel below to see the difference between the most common AI-tool pattern and what the system has to do.

Undo, two ways

You ask the model to undo. The model rewrites the file from its memory of how it used to look. Because the model does not have the previous version anymore, the new version is approximately the old version, with subtle differences (whitespace, a renamed variable, a comment that drifted). Each undo accumulates drift. After three undos you cannot tell whether you are on the original or a fourth-generation copy of it.

  • Approximate restore, not exact
  • Drift accumulates with each undo
  • No SHA to point at, no Revert button
  • Comparison becomes guesswork

Three taste moves the substrate enables

Once the timeline is real, taste collapses to three small decisions you make between prompts. None of them require you to be a developer.

Keep

The new output is genuinely better than the active SHA. You do nothing. activeIndex stays at the tip, the next prompt builds on it. The taste move is recognizing 'better' fast, not justifying it in a chat reply.

Fork

Your last two prompts went the wrong way. Click Revert on a commit two back. Issue a different prompt. The slice-on-fork rule prunes the dead branch from the timeline so the eye is not distracted by abandoned work.

Cancel

You can already see this turn going wrong while it streams. Hit cancel. commitTurn short-circuits because git diff --cached is empty, no commit is created, the timeline stays clean. The cheapest taste move there is.

The numbers that shape the loop

A few concrete budgets from the source. None of them are marketing numbers; they are values you can grep for in the repo.

0 commitPer agent turn (e2b.ts L1635)
0Default history depth (getGitHistory L1779)
0Max commit message chars (e2b.ts L1642)
0 charsShort SHA shown in UI

How this shows up in the UI

The Version History panel lives in src/components/version-history.tsx. It fetches /api/chat/history and renders one card per commit, ordered tip first. The active entry gets a teal background and an "ACTIVE" tag (line 134). Every other entry shows a Revert button (line 122).

The Undo and Redo buttons in the header track their own state: canUndo is true when the active index is not at the bottom of the list, canRedo when it is not at the top. Both fire PostHog events (version_undo_clicked, version_redo_clicked, version_revert_clicked) with the active and target SHAs, so the reps you put in are observable to you and to the team improving the loop.

The whole panel is around 140 lines of TypeScript. None of it is novel. The novelty is upstream: that the agent's turn cycle is the unit of commit, that the active pointer is real, and that the slice-on-fork rule is enforced. Once those three things hold, the UI to expose taste becomes trivial.

Honest limits

A few things this does not solve, on purpose, so you know where the substrate stops carrying the weight.

  • The history graph is per-session. If you destroy the VM session, the in-memory historyStack and activeIndex go with it. The git repo at /app is gone too. There is no cross-session merge of two attempts at the same prompt.
  • The slice-on-fork rule is opinionated. If you genuinely wanted to keep both branches and decide later, you cannot from the UI. You can read the reflog if you SSH into the VM, but that is a power move, not a taste move.
  • Taste is still on you. The system makes "go back" cheap and "compare" easy. It does not tell you which one is better. The eye still has to do the work.
  • The cancel-mid-stream move only works while the agent is still streaming. Once a turn finishes and commits, the cheap escape is undo, not cancel.

Want to see this loop end to end?

Hop on a 20 minute call. We'll walk through the history graph live, undo a turn, fork off a different prompt, and you can watch the slice-on-fork rule prune the abandoned branch.

Frequently asked questions

What does iteration taste in vibe coding actually mean, in one sentence?

It is the small judgments you make between prompts: which output is good enough to build on, which one to roll back, and when to fork off the current path instead of trying to patch it. Most posts on this topic frame taste as a vague meta-skill ('clarity of thought', 'a good sense of what you want'). That framing is correct but incomplete. Taste is also a function of the substrate underneath: you cannot develop a sense for which output to keep if the tool cannot reliably preserve and restore concrete versions of the work. With a definite history graph, the question 'is this better than what I had two prompts ago' becomes answerable. Without one, every iteration is a guess against a memory of how it used to look.

Why does the system substrate matter at all? Is taste not just the human?

Taste is the human, but taste is built by reps on tight feedback. A tool that lets you A/B two outputs by clicking between them produces a different builder than a tool where 'undo' means asking the model to please revert. With per-turn commits, you can A/B. Without them, you cannot, so you stop trying. The taste muscle never gets the reps. This is the same reason photographers got better when contact sheets were cheap: lower the cost of comparing your previous output to the current one and the eye sharpens.

What is the slice-on-fork rule and why is it interesting?

In src/core/e2b.ts lines 1671-1677, when commitTurn runs and the session's activeIndex is not at the tip of historyStack, the function slices the stack: historyStack = historyStack.slice(0, activeIndex + 1) before pushing the new SHA. Concretely: if you undo two steps and then issue a new prompt, the two abandoned commits drop off the visible timeline. They are still in git's reflog, but they leave the surface. This sounds like a small detail, and it is the rule that makes taste possible. It tells you the system has chosen for you: you are no longer trying both paths and merging later, you have committed to this fork. Tools that try to keep every branch alive end up with a graph the user cannot read, which kills decision-making.

How is undo wired so that I can undo the undo?

undoTurn in src/core/e2b.ts line 1731 does not rewrite history. It calls revertToSha (line 1691), which runs git checkout <previous-sha> -- . then commits the result with --allow-empty and a message of 'Undo to <short-sha>'. The new commit is appended to historyStack, and activeIndex is decremented. Because undo creates a new commit, undo itself shows up in your timeline and is itself undoable (which is what redoTurn at line 1744 walks back across). This is materially different from a tool that keeps undo state in memory, because if your session restarts you do not lose your timeline.

How do I see the timeline in the product?

The Version History panel in src/components/version-history.tsx fetches /api/chat/history and renders one card per commit, with the active SHA badged 'ACTIVE' on a teal background and every other entry shows a Revert button (line 122-133). Undo and Redo buttons sit in the header. Each click captures a PostHog event (version_undo_clicked, version_redo_clicked, version_revert_clicked) with the active and target SHAs, so you can later look back at your own behavior and see which prompts you kept and which you rolled.

What about cancelling a turn mid-stream? Is that taste too?

Yes, and arguably the highest-leverage one. /api/chat/cancel posts to the in-VM ACP cancel endpoint. The agent stops, no commit is created (commitTurn at line 1648 short-circuits with 'NOCHANGE' if git diff --cached --quiet), and you are still on the same active SHA. The timeline does not get polluted with a half-done turn. Cancelling early when you can already see the AI is going down the wrong path is the cheapest taste move there is: zero commits, zero pollution, full reset of the current prompt budget.

Does this actually change how people build?

It changes the cost of being wrong. When the cost of going down a bad path is one click of Revert and you are back on the previous SHA byte-for-byte, you take more swings. You try the weird redesign. You ask for a totally different style and compare. When the cost is 'now I have to describe back to the AI what the old version looked like and hope', you stop swinging. Builders converge to whatever the first acceptable output was. Per-turn commits raise the ceiling on iteration without raising the floor on risk.

Is this just git? Why is it special?

It is just git, and that is the point. The novelty is not the version control; it is the per-turn commit cadence and the surfaced active-pointer model. Most AI coding tools either do no version control at all, do it at file granularity (which fragments the timeline), or do it at session granularity (which makes individual prompts unrecoverable). Per-turn commits with an active-index pointer is the smallest unit of state that maps cleanly to 'one prompt, one decision'. That is the unit you make taste judgments at.

mk0r.AI app builder
© 2026 mk0r. All rights reserved.