Chat vs agent, and the four-part prompt

Day 1: From chat to spec · Lessons 1 and 2 of 6, with Lesson 2 starting halfway down · 14 min

The model you have been pasting from all year is the same model. What changes today is what sits around it: tools, a working directory, and a loop that runs until something says the work is finished. Your unit of work stops being an answer you paste and becomes a diff you have to check, and your prompt stops being a question and becomes an assignment: the outcome you want, the files that may change, the command that decides, and the condition that sends the work back to you.

Watch first · 3 min

How Claude Code Works

Claude, 2026-05-13, 2 min 50 s. Watch for the three beats it draws, which this page calls plan, execute and verify. Count how many times verify sends the run back to plan, because that return path is what a chat window has no way to perform. Keep one question open: at which beat is a human still required? The section “The loop: plan, execute, verify” below is where you settle it.

Also worth five minutes, outside the lesson's fourteen: Boris Cherny: We Cut 80% of Claude Code’s Prompt (Y Combinator, 2026-07-27), chapters at 19:32 and 21:57. Listen for what a prompt is for once the model can act.

What comes back is different

Ask chat to fix a currency-formatting bug and you get text: an explanation and a corrected function. Nothing has happened to your repository. You are the step between the answer and the file, so every mistake is caught at paste time, by you — and that safety property is the one you give up today.

Ask an agent the same thing and what comes back is a changed working tree plus a transcript of the commands it ran. The artifact you review is a diff: a pull request nobody else has read.

Tools, a working directory, and a loop

Three things get added to the same model. It can read and edit files. It can run commands and read their output, so a failing test is information it receives rather than something you relay. And it repeats, deciding for itself whether the task is finished.

It starts in the directory you launch it from. What it may actually read, edit and run is decided by permission rules, which you meet on Day 2; today the bounds you write into the prompt do that work.

Chat ends in text; the agent loop ends in a diff The same task drawn twice, with time running left to right in both rows. In the chat row, your prompt produces text back, which you apply yourself to your files; there is no return path anywhere, because you are the step between the answer and the repository. In the agent row, your prompt enters a bordered region called the loop, which holds plan, execute and verify in that order; a failing verify returns to plan on its own, without you, and the loop's output is a diff, which is the thing you review. chat your prompt text back you apply it your files you are the step agent your prompt the loop plan execute verify test red diff you review this
Same task, two shapes: chat ends at text you apply; the agent's loop ends at a diff you read.

The loop: plan, execute, verify

Plan is the change it intends, from what it has read: which files, and what it expects to be true afterwards. Execute is the edits. Verify is a command it runs and whose exit status it reads. A red test sends it back to plan, without you.

This is the whole course. Day 1 runs the loop once by hand. Day 2 lets it run unattended between stopping points you choose. Day 3 puts a team's framework around it.

An agent with no verify step is a code generator that writes to disk. The loop cannot close without a command that can fail, which is why every prompt in this course that commissions a change carries one — the four-part prompt below makes it the third part.

"Done" is a command you ran and its output

The agent reporting that the fix is in is a claim. The evidence is the verification command and the line it printed, copied out of your terminal. For LedgerKit that command is one line, the same line every time today, and the same command on Days 2 and 3, run from that day's own copy of the lane:

Swift lane
swift test

The two lines that carry the counts, from a green run here on 2026-09-22 (a swift-testing banner prints after them):

Test Suite 'All tests' passed at 2026-09-22 17:15:20.343.
	 Executed 34 tests, with 1 test skipped and 0 failures (0 unexpected) in 0.019 (0.024) seconds

Your timestamp and durations will differ; the counts will not. The skipped test is deliberate, and you meet it in Exercise 2.

Kotlin lane
./gradlew test

A green run on this machine on 2026-09-22 printed the build verdict and the task count:

BUILD SUCCESSFUL in …
5 actionable tasks: 5 executed
Consider enabling configuration cache to speed up this build: …

The task count varies with what Gradle rebuilds. This lane gates its known-issue case on an environment variable rather than skipping it, so no test counts appear; Exercise 2 turns it on.

A useful team norm: every result anyone reports carries its command and the line that carries the verdict, because the command is the part a reviewer can run again.

Lesson 2: The four-part prompt

The agent does the work and "done" is a command. This half is how you write the prompt that commissions it.

A prompt to an agent is an assignment

In chat you ask for text and judge it as it arrives; a bad prompt costs you a bad paragraph. An agent prompt commissions work that runs whether or not you are watching, and you cannot judge it until the files have changed.

So the prompt is a brief with parts: the outcome, what may change to reach it, what decides that it worked, and what brings it back to you unfinished. All four get answered on every run, by you or by the agent.

Goal: the outcome, not the steps

State the behaviour you want afterwards, in one or two sentences, in the domain's words rather than the code's. Searching for cafe finds Cafe Lumiere, and so does searching for CAFE. Anyone can tell whether that is true.

Steps are the agent's job, and it has read this code more recently than you have; a list of steps is a plan, and Lesson 3 shows where a plan belongs. A goal has to be able to be wrong, and "improve merchant search" cannot be.

Bounds: which files may change

Name the file or directory that may change, and name at least one that may not. The second half rules out the plausible neighbouring edit: the helper lifted into a shared type, the signature widened on the way past.

Swift lane

In bounds: Sources/LedgerKit/LedgerFilter.swift and its tests. Out of bounds, and worth naming: Sources/LedgerKit/Money.swift. The fixtures file the prompt below protects is Tests/LedgerKitTests/Fixtures/transactions.tsv.

Kotlin lane

In bounds: src/main/kotlin/ledgerkit/LedgerFilter.kt and its tests. Out of bounds, and worth naming: src/main/kotlin/ledgerkit/Money.kt. The fixtures file the prompt below protects is Fixtures/transactions.tsv.

They also give you a check cheaper than reading every hunk: count the files touched. A filter change that edited the money type is out of bounds however green the suite is.

Verification: the command that decides

Give the exact command, the one in the section above, and say what the output has to look like; without that the agent picks its own finish line and stops when the change reads as complete.

Ask for the output, not a verdict. "Verify it" gets a sentence, and a sentence about tests passing is cheap to produce; asking for the line that carries the verdict, the one quoted under your lane's command above, asks for the run.

Stop: when to come back and ask

One or two named triggers, written as conditions rather than as warnings. If the fix needs a file outside those two paths, stop and say which and why. If the same test fails twice in the same way, stop and show me the failure. Both are checkable by the thing doing the work, which separates a stop condition from a plea to be careful.

A stop condition turns a surprise into a question instead of a diff, and it is where your own uncertainty goes. Day 2 turns these conditions into a runner's gates.

The four parts of an agent prompt and the failure each blank causes One outlined rectangle labelled one agent prompt holds four stacked bands, read top to bottom. Goal, the outcome you want to exist; left blank, the agent picks what 'better' meant. Bounds, files that may and may not change; left blank, neighbouring files change and green hides it. Verification, the command that decides; left blank, the agent sets its own finish line. Stop, the condition that sends it back; left blank, the surprise arrives as a diff, not a question. A bracket down the left of all four bands is labelled you answer these, or the agent does. one agent prompt you answer these, or the agent does Goal the outcome you want to exist blank: the agent picks what 'better' meant Bounds files that may and may not change blank: neighbouring files change and green hides it Verification the command that decides blank: the agent sets its own finish line Stop the condition that sends it back blank: the surprise arrives as a diff, not a question
Each blank has a failure that belongs to it; the agent fills it either way.

The same assignment, twice

The same task, written both ways. First, as you would ask a colleague:

The merchant search in this project doesn't work very well - searching
for cafe doesn't find Cafe Lumiere. Can you fix that?

The second is the same request with the four questions answered:

Swift lane
Goal: LedgerFilter's merchant match should ignore case and diacritics, so the query "cafe" matches the merchant "Cafe Lumiere" and the query "CAFE" matches it too.

Bounds: you may change Sources/LedgerKit/LedgerFilter.swift and Tests/LedgerKitTests/LedgerFilterTests.swift and nothing else. Do not change Sources/LedgerKit/Money.swift, Sources/LedgerKit/TransactionFormatter.swift, Sources/LedgerKit/LedgerSummary.swift, the fixtures file, or CLAUDE.md.

Verification: run swift test and show me the Executed line right under Test Suite 'All tests' passed (or failed). Add at least one test for the diacritic case and one for the case-insensitive case, using merchants that already exist in the fixtures.

Stop: if the change needs a file outside those two paths, stop and tell me which file and why, without editing it. If the same test fails twice in the same way, stop and show me the failure.
Kotlin lane
Goal: LedgerFilter's merchant match should ignore case and diacritics, so the query "cafe" matches the merchant "Cafe Lumiere" and the query "CAFE" matches it too.

Bounds: you may change src/main/kotlin/ledgerkit/LedgerFilter.kt and src/test/kotlin/ledgerkit/LedgerFilterTest.kt and nothing else. Do not change src/main/kotlin/ledgerkit/Money.kt, src/main/kotlin/ledgerkit/TransactionFormatter.kt, src/main/kotlin/ledgerkit/LedgerSummary.kt, the fixtures file, or CLAUDE.md.

Verification: run ./gradlew test and show me the BUILD line. Add at least one test for the diacritic case and one for the case-insensitive case, using merchants that already exist in the fixtures.

Stop: if the change needs a file outside those two paths, stop and tell me which file and why, without editing it. If the same test fails twice in the same way, stop and show me the failure.

The premise in both prompts is deliberately wrong: there is no Cafe Lumiere in the fixture, and the merchant match already folds case and diacritics. The verification line guards against taking it on trust. The good outcome is the agent reporting that the search already works, with the suite's verdict line as evidence; any tests it adds pass on their first run, pinning behaviour that was already there.

Exercise 1 runs both prompts for real, each in a fresh session (/clear empties the conversation).

When chat, or one line, is the right tool

If there is no file to change, there is nothing for verify to run, and a loop that cannot close is overhead you pay for in file reads and turns. Explaining a stack trace, or arguing about where state belongs before you commit to an approach: chat answers both in one turn, and neither ends in a diff. Reach for the agent when the outcome you want is a diff, and for chat when it is a sentence.

Nor should the prompt cost more than the change it commissions. If you can review the diff cold in under a minute without being told the bounds — a rename, a typo, a version bump — one line is enough. Three tells push the other way: the change spans files, so the file list no longer fits in your head; you will not be watching it run; or you will run it again next month, and what you left implicit gets re-invented. Any one of those and the four parts pay for themselves.

Every keystroke, flag and path in this course was checked against Claude Code v2.1.280 on 2026-09-22; claude --version shows yours. If your terminal disagrees with a page, your terminal is right.

Quick check

Your prompt reads "make the merchant search better". Which missing part lets a wrong result look finished, and what happens because of it?

Verification. With no command that decides, the agent sets its own finish line and stops when the change reads as complete, so you learn what "better" meant only by reading the whole diff. The goal is missing too, but a goal says what right looks like; only the command decides when the work is done.

A colleague asks why the build got slow. Nothing in the repo has to change. Why is running that through the agent loop a poor trade?

There is no diff, so verify has nothing to run and the loop cannot tell itself it is finished. You pay for file reads and tool turns to get an answer chat gives you in one.

Sources

Claude Code documentation, accessed 2026-09-22 against version 2.1.280.