Agent mode: from suggestion to execution
Lesson 4 of 12
4Agent mode: from suggestion to execution
Here the model stops suggesting and starts editing your files and running commands, with a real exercise we ran, showing that the “test” the agent wrote can never fail.
What the picture shows: An agent does not answer once: it plans, edits, runs, reads the output and loops. Your review of the diff is the one point of control.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
In agent mode the colleague no longer only suggests; it takes the keyboard: it opens files, edits them and runs commands itself. That saves a lot of time, but it is like handing someone your house keys: you watch what it does and review every change before you accept it.
An agent is a loop: it plans, calls tools (read a file, edit, run a command), reads the result and repeats. You control it with permissions and by reviewing the diff. The exercise uncovers a test the agent wrote that can never fail, so you check that every test fails first when you break the code.
Required fundamentals
Part three
Working chat, and the habit of reading a suggestion before accepting it. An agent is that same chat with hands.
An open project folder
The agent needs somewhere to write. Open an empty folder called mini-agent-demo, not a single file.
Node installed
We run JavaScript from the terminal. Check with node -v; any recent version is fine.
Reading a diff
A + line was added, a - line was removed. That is the whole notation, and it is your only control point.
Core concepts
Completion writes a line, chat hands you text to copy, an agent writes files on your disk and runs commands. The difference is not intelligence, it is permission.
An agent does not answer once: it plans, edits, runs, reads the output, then corrects itself. That loop is its whole advantage, which is why it suits tasks with a measurable outcome.
Reviewing before the write (the diff) is the one moment the decision is yours. Accept without reading and you have handed the project to a model that does not know your intent.
Every command the agent runs really runs on your machine: it creates files, installs packages, overwrites your work. Never run an agent in a folder holding work that is not committed to git.
How it works
Diagram 1, Three modes, three completely different outputs
Diagram 2, The agent loop: note the arrow returns to “plan” twice, on your rejection and on a failed command
The exercise: let the agent build, then test its test
The request we gave the agent was one sentence: “create a small dice project with a function returning a random number from 1 to the number of sides, plus a test and a README”. In seconds it created three files. Here is the first, and it is excellent:
// dice.js - a single die roll
function roll(sides) {
if (!Number.isInteger(sides) || sides < 1) {
throw new RangeError("sides must be a positive integer");
}
return Math.floor(Math.random() * sides) + 1;
}
module.exports = { roll };Number.isInteger before use, threw RangeError which is the correct error type, and used Math.floor rather than Math.round so it can never produce a zero. This is better than a lot of human code.Then it created a file it called a test. Here is the surprise:
// dice.test.js - what the agent called a test
const { roll } = require("./dice");
for (let i = 0; i < 5; i++) {
console.log("roll(6) ->", roll(6));
}$ node dice.test.js roll(6) -> 6 roll(6) -> 2 roll(6) -> 4 roll(6) -> 4 roll(6) -> 6 $ echo $? 0
0, “success”.Math.floor → Math.round) to see whether the “test” would notice.roll(6) -> 7 on a six-sided die, and returned 0 as if nothing had happened.exit 1.// one character changed: floor -> round - return Math.floor(Math.random() * sides) + 1; + return Math.round(Math.random() * sides) + 1;
$ node dice.test.js roll(6) -> 2 roll(6) -> 2 roll(6) -> 7 <-- a seven, on a six-sided die roll(6) -> 2 roll(6) -> 3 $ echo $? 0 <-- still "success"
The whole lesson in one line: a file that runs successfully is not a test. A test is something that can fail. Here is the one we wrote ourselves:
// dice.spec.js - a test that can actually fail
const assert = require("assert");
const { roll } = require("./dice");
check("every roll(6) is an integer in 1..6", () => {
for (let i = 0; i < 10000; i++) {
const v = roll(6);
assert.ok(Number.isInteger(v), "not an integer: " + v);
assert.ok(v >= 1 && v <= 6, "out of range: " + v);
}
});
check("all six faces appear in 10000 rolls", () => {
const seen = new Set();
for (let i = 0; i < 10000; i++) seen.add(roll(6));
assert.strictEqual(seen.size, 6, "faces seen: " + [...seen].sort().join(","));
});
check("roll(0) throws RangeError", () => {
assert.throws(() => roll(0), RangeError);
});$ node dice.spec.js # healthy dice.js PASS every roll(6) is an integer in 1..6 PASS all six faces appear in 10000 rolls PASS roll(1) is always 1 PASS roll(0) throws RangeError PASS roll(2.5) throws RangeError PASS roll() with no argument throws RangeError 6/6 checks passed $ echo $? 0
$ node dice.spec.js # after floor -> round FAIL every roll(6) is an integer in 1..6 -> out of range: 7 FAIL all six faces appear in 10000 rolls -> faces seen: 1,2,3,4,5,6,7 FAIL roll(1) is always 1 -> Expected values to be strictly equal: 2 !== 1 PASS roll(0) throws RangeError PASS roll(2.5) throws RangeError PASS roll() with no argument throws RangeError 3/6 checks passed $ echo $? 1 <-- a failure you cannot miss
| The agent's test | A real test | |
|---|---|---|
| Prints output | Yes | Yes |
| Asserts a value | No | Yes |
| Fails when the code breaks | No | Yes |
| Exit code when broken | 0 | 1 |
| Usable in CI | No, always passes | Yes |
Myth vs. reality
| The myth | The reality |
|---|---|
| “The agent created a test, so the code is covered.” | The agent created a file named test. Coverage is measured by a file's ability to fail, not by its name. |
| “The agent ran the command and it passed, so the task is done.” | “Passed” means the exit code was zero. The command may have checked nothing, or checked something else. |
| “Reviewing the diff wastes time, I trust the tool.” | Trust is not verifiable. Review is the cheapest test there is: two seconds against an hour of debugging. |
| “Agent mode beats chat because it is newer.” | It is stronger at execution and weaker at judgement. A task you have no measure for is solved by discussion, not execution. |
| “If it breaks something I'll just undo.” | An agent touches several files and may run commands. git is the real undo button, not Ctrl+Z. |
Try it yourself
The agent created a file, ran it, printed five numbers and returned exit 0. What did that prove?
Exit code zero means the program finished without an exception. A program that only prints always finishes without an exception, even when it prints 7 on a six-sided die, which is exactly what we saw.
Which of these tasks best suits agent mode?
Because it spans several files and has one clear measure of success: the tests pass. The first and fourth are decisions and the second is an explanation, all better served by chat.
Quick review flashcards
exit 0 means?git commit, not Ctrl+Z.Check your understanding
1. In one sentence: what is the difference between “the code works” and “the code is correct”?
“Works” means it finished without an exception on the input you happened to try. “Correct” means it gives the expected result for every input you care about, edge cases and bad input included. The first you can see; the second needs verification.
2. Why did the agent's test not notice the die returned 7?
Because it compares nothing. console.log sends text to the screen and ends; the 7 appeared in front of our eyes and passed because nobody asked “is it ≤ 6?”. The real test asked, and failed instantly.
3. (Spot the bug) An agent edited 6 files and reports “all tests pass”. What do you do first?
Run the tests yourself in your own terminal, then read the diff file by file. The reason: you have just seen with your own eyes that “pass” can mean “checks nothing”. Second: git diff, to learn what actually changed.
4. The agent proposes installing a package you have never heard of. What now?
Stop. Ask why, get the exact name, and confirm it exists and is genuinely used. Models invent plausible package names, and installing an invented one, or one whose name resembles a popular package, is a real security risk, not merely a bug.
5. (Decision) A manager says: “run the agent over the whole project and let it clean up the code”. Your answer?
That this task has no measure of success: “clean up” is not an outcome a command can check, so the agent will touch everything and nobody will know what improved and what broke. The alternative: a new branch, green tests first, then one specific measurable task at a time (“remove unused imports, then run the tests”), with a diff review between each step.
Resources for this part
Sorted by kind: start with the official docs when you want the precise reference, or with an article or video when you want the explanation first.