Vibe Coder #4.3: Iterative Development – Eating the Elephant One Bite at a Time
With a coding agent the message bends in two places: stacked corrections and context compaction. The cure is to cut the work into one-sentence slices that each have a runnable check and end in a commit, and to keep the plan on disk instead of in the chat.
This post is also available in Turkish →
Remember the tenth of the eleven habits in Code Mediumship (in Turkish): the broken telephone loop. The agent writes code, the code throws, you paste the error back, the agent fixes it and breaks something else. By the fifth round the original logic is gone; you asked for a login page and you now own a login page that shows the weather. That post named the illness and moved on. This chapter is about the cure: iterative development with an AI agent, which means handing the work over in small slices you can verify.
In the previous chapter we put the rules on disk: the constitution at the root, by-laws in the folders, a guard at the door. Now we look at how to cut up the work itself underneath those rules. The metaphor is an old joke: how do you eat an elephant? One bite at a time. An agent that tries to swallow the whole elephant does not choke. It does something worse. It believes it swallowed it and tells you it is done.
Where does the broken telephone start?#
In the children's game the message bends a little at every ear, and the last kid says a completely different sentence. With a coding agent the distortion has two sources, and both are written down in the tools' own documentation.
The first is the correction loop. Claude Code's best practices page turns it into a rule: if you have corrected Claude more than twice on the same issue in one session, the context is cluttered with failed approaches; run /clear and start again with a sharper prompt that includes what you learned. The same page puts it bluntly: a clean session with a better prompt almost always beats a long session full of accumulated corrections.
The second is compaction. As the context window fills, Claude Code summarises the conversation history on its own. In Codex, /compact summarises the visible chat to free tokens. So by turn forty the instruction the agent is holding is not the sentence you typed, it is a summary of it. One more compaction and it is a summary of a summary. That is the telephone game exactly, except every ear belongs to the same model.
Technical detail: Claude Code's docs say you can tell compaction what to keep by writing it into CLAUDE.md, with the example "When compacting, always preserve the full list of modified files and any test commands". Useful, but writing "don't forget this" inside a summary does not win the game, it only slows the loss. Add the silent 4,096-token window we saw in the local LLM chapter: the smaller the window, the fewer rounds it takes for the same game to go wrong.
The only message that never bends is the one sitting on disk. The agent reads it from a file, not from the previous ear.
How do you slice an elephant?#
I take the size of a slice from the Claude Code docs again, because it is the cleanest definition I have found: if you could describe the diff in one sentence, skip the plan and just ask for it. I use that rule the other way round. A slice is a diff you can describe in one sentence. If you can't, it is not a slice, it is the elephant.
Every slice has three parts:
- A one-sentence goal. "Add pagination to the product list", not "and filtering too".
- A done check you can run. A test,
php -l, acurlcall, the expected row count from a SQL query. The agent saying "I checked, it works" is not a check. - A commit. When the check passes the slice closes, and the next slice starts from a clean point.
An example elephant: adding filtering, pagination and CSV export to the product list in an admin panel written in plain PHP with no framework. Given as one prompt, the agent changes the query, the template, the route and the export all at once; when something breaks, neither you nor it knows which change did it. The sliced version is a PLAN.md at the project root:
# Product list: filters, pagination, CSV
Rule: one commit per slice. No moving on until the check passes.
## Slice 1: Move the query into a repository
- Done: tests/ProductRepositoryTest.php passes, the list page still returns the same 20 rows.
## Slice 2: Pagination (LIMIT/OFFSET, 20 per page)
- Done: ?page=3 shows rows 41-60; the total comes from a single COUNT(*) query.
## Slice 3: Category and status filters (PDO prepared statements)
- Done: filter tests pass; ANALYZE shows r_rows close to the estimate.
## Slice 4: CSV export (same filters, streamed)
- Done: a 10,000-row export stays under the memory ceiling.
## Lessons
- (Add one line here before every reset.)
That last heading matters, and I will come back to it. Why slice 3 checks r_rows in ANALYZE rather than rows is covered in the MariaDB composite index post.
Who writes the plan, and who signs it off?#
All four tools have a separate mode for "plan first, code second". The names differ, the idea is the same: the agent reads and proposes, and does not touch a file until you approve.
- Claude Code: plan mode. Press
Shift+Tabuntil the status bar shows plan mode, or start withclaude --permission-mode plan. Claude reads files and answers questions without making changes.Ctrl+Gopens the plan in your text editor so you can fix it before Claude proceeds. - Codex:
/planswitches to plan mode; the command table describes it as asking Codex to propose an execution plan before implementation starts. - Cursor: Plan Mode researches the codebase, asks clarifying questions and produces a plan you can edit. Plans are stored in your home directory by default; "Save to workspace" moves one into the project.
- Antigravity: the implementation plan arrives as an artifact. You comment on it (cut the scope, change the stack), then click Proceed to continue or Review to send your comments back as feedback.
Hidden gem: two settings in Cursor and Antigravity touch the broken telephone problem directly. Cursor's plan lives outside the project by default; unless you choose "Save to workspace", the rest of the team and the next session never see it. Antigravity's docs say the agent "typically" asks for review before implementing, but not when your artifact review policy is set to "Always proceed". You can remove the plan gate yourself with one setting.
Hard truth: plan mode is overhead too. Claude Code's docs suggest skipping it for fixing a typo, adding a log line or renaming a variable; planning pays off when you are unsure of the approach, when the change touches several files, or when you don't know the code. Opening plan mode for every slice is like rereading the whole menu before every bite.
A way back at the end of every slice#
A slice proves its worth when something breaks: there is a clean point to return to. Each tool has its own undo, but the limits are in the small print.
| Tool | Way back | Limit |
|---|---|---|
| Claude Code | Automatic checkpoint on every prompt, /rewind or Esc twice |
Does not track files changed by Bash commands; last 100 checkpoints; default retention about 30 days |
| Cursor | Agent checkpoints before significant changes, "Restore Checkpoint" | Agent changes only; your manual edits are not tracked; restoring reverts files, not the conversation |
| Aider | A descriptive git commit after every edit, /undo |
--no-auto-commits turns it off; work without commits and the safety net goes with them |
| Codex | /diff shows the git diff (untracked files included), /fork branches the chat |
No undo command in the command table; your way back is git itself |
Technical detail: the example in Claude Code's docs is exactly the kind that breaks a slice: if the agent ran rm, mv or cp, /rewind will not bring it back, because checkpoints only track edits made through the agent's file editing tools. Cursor's docs say the same thing in other words: checkpoints are not version control.
The only common denominator in that table is git. Aider built it into the design: every edit is a commit, so every slice is a way back on its own. With the other tools you bring the discipline yourself: check passes, commit; check fails, git restore and build a smaller slice.
Has the slice grown? Measure it#
"A one-sentence diff" is a good rule, but nobody counts lines while the agent is working. You usually notice the slice has bloated when you try to write the commit message: if it needs two "and"s, you are already late. To stop leaving that to the eye, I wrote a small script that counts the files and lines changed since the last commit. No framework, plain PHP 8.3:
<?php
declare(strict_types=1);
/**
* Slice meter: counts files and lines changed since the last commit,
* exits with 1 when the slice goes over budget.
* Usage: php slice-meter.php [--files=6] [--lines=200]
*/
$options = getopt('', ['files::', 'lines::']);
$fileLimit = (int) ($options['files'] ?? 6);
$lineLimit = (int) ($options['lines'] ?? 200);
function git(string $command): array
{
exec('git ' . $command . ' 2>/dev/null', $output, $code);
if ($code !== 0) {
fwrite(STDERR, "git $command failed (code $code). Are you inside a git repository?\n");
exit(2);
}
return $output;
}
$files = [];
// Tracked files: added plus deleted lines (binary files report "-").
foreach (git('diff --numstat HEAD') as $line) {
[$added, $deleted, $path] = explode("\t", $line, 3);
$files[$path] = $added === '-' ? 0 : (int) $added + (int) $deleted;
}
// New files not yet added to git: every line counts as new.
foreach (git('ls-files --others --exclude-standard') as $path) {
$files[$path] = is_file($path) ? count(file($path)) : 0;
}
$total = array_sum($files);
arsort($files);
printf("Slice: %d files, %d lines (budget %d files / %d lines)\n", count($files), $total, $fileLimit, $lineLimit);
foreach (array_slice($files, 0, 5, true) as $path => $count) {
printf(" %5d %s\n", $count, $path);
}
if (count($files) > $fileLimit || $total > $lineLimit) {
echo "Over budget: this slice is too big. Split it or roll it back before committing.\n";
exit(1);
}
echo "Slice within budget. Run the tests; if they pass, commit.\n";
exit(0);
I ran it against a four-class test repository in two scenarios. Changing six lines in one class and adding a test file gave "2 files, 15 lines" and exit 0. Rewriting three classes end to end and adding a new 120-line file gave "6 files, 435 lines" and exit 1. Outside a git repository it exits with 2. Technical detail: git diff --numstat counts a changed line as one deletion plus one addition, so a six-line change shows up as 12. Keep that in mind when you set the budget. Run it from the repository root, since the paths are relative to it.
Handing the script to the agent is a job for the constitution from chapter 4.2. Two lines in the root AGENTS.md are enough:
## Slice discipline
- After every slice, run `php slice-meter.php`. If it returns 1, do not commit; stop and propose how to split the slice.
- Do not start the next slice until the "Done" check in PLAN.md passes.
When you are working by hand, the same gate is one line:
php slice-meter.php --files=6 --lines=200 && git commit -am "Slice 2: pagination"
The numbers are my own arbitrary choice, not a measurement. A five-line config change and a 200-line class do not carry the same risk. The script is a scale, not a judge: it tells you "this bite is big", not "don't eat it".
A clean page after two corrections#
The move that actually breaks the telephone loop is restarting the game. /clear in Claude Code, /new or /clear in Codex, a new chat in Cursor and Antigravity. But not with a blank page.
Before you clear, add one line under "Lessons" in PLAN.md: "In slice 3 the agent suggested mysqli twice; this project only uses PDO." The new session reads the plan from disk, and the lesson now lives in a file instead of inside a summary. It is also the flow Claude Code's docs recommend for larger features: work out a spec file with the agent first, then start the implementation in a fresh session with clean context. It is the spec sheet from chapter 4.1, at the scale of a single slice.
Bottom line: chat history is short-term memory, PLAN.md is long-term memory. Short-term memory gets blurrier with every compaction; the file stays the same until you change it.
When does it help, and when doesn't it?#
It helps on any job that touches more than one file; in a codebase you don't know or one that is old; and whenever you plan to leave the agent running unattended. The longer you leave it alone, the smaller the slices and the more automatic the checks should be.
It doesn't help on one-sentence jobs, which are slices already. It also gets in the way of exploration: slicing an open question like "what would you improve in this file?" closes off the unexpected path the agent might have shown you. Write the plan after exploring, not before.
And there is a trap: you can make the slices so small that each one is right on its own and the sum is wrong. Ten small commits, none of them bad, and the architecture has drifted somewhere nobody decided. That is technical debt, and the next chapter, Vibe Coder #5.1 on technical debt and spaghetti code, looks at exactly how that interest piles up. The full series and the list of published chapters live on the Vibe Coder's Handbook page (in Turkish).
An agent that tries to eat the elephant in one bite says "done". An agent that eats it slice by slice says "this much is done, and here is the proof". Ask for the second one. Stay with technology.
Comments
0 comments
Leave a comment