Giving the agent a check it can run, with a limit
At the end of the previous lesson you checked the agent’s fix yourself: you ran the reproduction steps on every night and compared the totals. That check works, but only you can run it, and you run it once. In this lesson the same steps become a test the coding agent runs after every change it makes. Then you give the agent a limit on how often it may try, and a rule that it may not change the test, and you confirm afterwards that it kept to both.
The program is the nightly sales import from the previous lesson, before the fix. Here are the reproduction steps you wrote there.
From nightly/, run: python3 importer.py nights/2026-09-15/*.csvExpected: a total in euros, exit status 0Actual: the batch is rejected, exit status 1Reruns: 3 of 3 fail. The 14th and the 16th pass 3 of 3,with totals 22975.95 EUR and 23257.33 EUR.Write the check as a test
Section titled “Write the check as a test”A check that only you can run gets run when you remember it. The vendor guide for Claude Code says that without a check the agent can run, you are the one who has to notice every mistake. Its advice is to give the agent a check with a pass or fail result. A test suite is one, and a script that compares the output with a known good result is another. The agent can then rerun the check after each change and stop once it passes [1]. The Academy course The AI-native SDLC playbook (SDLC is short for the development life cycle of software) teaches the same idea in a lesson on giving the agent a feedback loop [2].
For the nightly import, the check is test_nights.py, a short Python
script in the course repository. It runs the importer on each night the
way the nightly job does, and prints one line per night. It exits with
status 0 when every night passes, and with status 1 when one fails. These
are the nights it knows:
# Each night, with the total it must print, or None when the total is not# pinned yet.NIGHTS = { "2026-09-14": "22975.95 EUR", "2026-09-15": None, "2026-09-16": "23257.33 EUR", "2026-09-17": None,}A night passes when the importer exits with status 0 and parses every data row of the night’s exports, and for the 14th and the 16th, when it prints the same total as before. The test counts the rows itself, one per line after the header. That count is there because of the previous lesson: a “fix” that skips a row it can’t read also exits with status 0, and the row count is what catches it.
From the root of your clone of the course repository, copy the import and the test to a place of your own, and run the test there.
cp -R site/examples/coding-with-agents/observing-and-debugging/nightly ~/loop-mecp site/examples/coding-with-agents/self-checking-loops/test_nights.py ~/loop-me/cd ~/loop-mepython3 test_nights.py; echo "exit status $?"The test, before any fix
Section titled “The test, before any fix”Run the test in your copy and compare what you see with the output below.
2026-09-14: pass 2026-09-15: FAIL: exit status 1; parsed 39 rows, expected 40 rows 2026-09-16: pass 2026-09-17: FAIL: exit status 1; parsed 46 rows, expected 47 rows 2 passed, 2 failed exit status 1
Output verified in CI from site/examples/coding-with-agents/self-checking-loops/run_test.py.
The examples in this lesson aren’t graded. Each one is a command you run, with output the course checks on every build.
The output is written for a reader who wasn’t there. Each failing line names the night and what went wrong, so the agent can read it and decide what to try next, and the last line and the exit status give the verdict in one place.
Which check can the agent run?
Section titled “Which check can the agent run?”A team has a job that builds a sales report as a PDF every morning. Today a person checks it by hand. The team wants a coding agent to change the job and check its own work.
Today the check is “open the PDF each morning and see that the totals are right”. Which rewrite gives the agent a check it can run and read?
Which option gives the agent a result it can read without asking a person?
Brief the loop with a limit
Section titled “Brief the loop with a limit”A bounded self-checking loop is a loop where the agent runs the check, fixes and reruns, with a limit you set. The brief sets two limits. The first is a number of attempts, after which the agent stops and reports what it found, pass or fail. The second says what the agent may change, and the check itself is not on that list. Without the limits, an agent told to keep going until the test passes can keep going for a long time, and the shortest way to a passing test is often to change the test. Automating a check is one of the verification skills in Brilliant’s skills map for coding with AI [3]. The limits are what make it safe to hand the check to the agent.
The transcript below is illustrative and doesn’t record a specific model’s words.
Run python3 test_nights.py in this directory. It fails on the 15th and
the 17th, because read_rows in importer.py splits a quoted amount
such as "1,250.00" into two fields. Fix importer.py so the test
passes. After each change, rerun the test and read its output. Stop
after five attempts, pass or fail, and show me the last output and what
you tried. test_nights.py and the files under nights/ are read-only.
If you think the test is wrong, stop and tell me why, and don’t change
it. Done when the test prints 4 passed, 0 failed and exits with
status 0.
Attempt 1: I changed read_rows to read each file with the csv
module, which keeps a quoted field together. The test still failed on
the 15th and the 17th with exit status 1: the amount “1,250.00” can’t
be turned into a number. Attempt 2: I removed the thousands separator
before the conversion. The test now prints 4 passed, 0 failed. I
changed only importer.py.
The response is a claim about what the agent did. Before the run, make the copy a git repository with one commit and give that commit a tag. Afterwards git can list every file that differs from the tag, even when the agent made commits of its own.
git initgit add .git commit -m "Before the agent's run"git tag before-runAfter the run, list what changed, and run the test yourself. A plain
git diff leaves out files the agent created, so stage everything
first, with git add -A, and compare the staged files with the tag.
A new file then shows in the list like a changed one.
What the bounded run changed
Section titled “What the bounded run changed”These are the commands you run in ~/loop-me after your own agent’s
run, in the exercise at the end of the lesson:
git add -Agit diff --cached --stat before-runpython3 test_nights.pyThe course’s script applies the change from the transcript to a fresh
copy of its own, runs the same three commands, and prints their output,
with the exit status of the test as the last line. Run it from
site/examples/coding-with-agents/self-checking-loops/ in your clone.
python3 after_loop.py$ git add -A $ git diff --cached --stat before-run importer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) $ python3 test_nights.py 2026-09-14: pass 2026-09-15: pass 2026-09-16: pass 2026-09-17: pass 4 passed, 0 failed exit status 0
Output verified in CI from site/examples/coding-with-agents/self-checking-loops/after_loop.py.
Only importer.py changed, and no file was added, so the test ran with
the assertions you wrote. The test passing now means what it meant when you wrote it.
“Read-only” in a brief is a request, and the agent decides whether to
follow it. Claude Code can enforce part of it with a deny rule on the
file. The rule applies to the agent’s own file tools, to the file
commands it recognizes in a shell command, such as sed, and to the
target of a redirect. It doesn’t apply to a script the agent runs that
opens the file itself [4]. With or without the
rule, the list of changed files after the run is the check that the
test stayed the same.
What goes into the loop brief?
Section titled “What goes into the loop brief?”A learner briefs a coding agent to fix a nightly import until its test, test_nights.py, passes. The agent can run the test itself and read the result.
Which of these lines belong in the brief for the loop?
Which lines make the agent check its own work, which lines limit how long it runs or what it may change, and which lines take a limit away?
Here is the same fixture after a run without the read-only rule. The agent made the importer skip the rows it can’t read, saw the row count in the test fail, and removed the row count from the test.
What a run without limits changed
Section titled “What a run without limits changed”The course’s script makes those two changes to a fresh copy and runs the
same three commands. You can run it from
site/examples/coding-with-agents/self-checking-loops/ in your clone.
python3 unbounded_run.py$ git add -A $ git diff --cached --stat before-run importer.py | 3 +-- test_nights.py | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) $ python3 test_nights.py 2026-09-14: pass 2026-09-15: pass 2026-09-16: pass 2026-09-17: pass 4 passed, 0 failed exit status 0
Output verified in CI from site/examples/coding-with-agents/self-checking-loops/unbounded_run.py.
The test passes, and the test changed
Section titled “The test passes, and the test changed”A coding agent was asked to fix a nightly import until its test passed. After the run, the list of changed files holds importer.py and the test file, test_nights.py, with six lines deleted from the test. The test prints 4 passed, 0 failed.
The agent reports 4 passed, 0 failed. git diff --cached --stat before-run lists
importer.py and test_nights.py, with six lines deleted from the
test. What do you do?
If you put the test back the way you wrote it, what would it say about the importer as it is now?
Gates and second opinions
Section titled “Gates and second opinions”The test is one of the deterministic gates a project can have. A deterministic gate is a check whose result doesn’t depend on anyone’s judgment: tests, type checks, linters, builds and schema validation. A gate that isn’t flaky gives the same answer for the same input, which is why a team can let a failing gate block a merge. The same gates can also end an agent’s loop. In Claude Code, a hook that runs when the agent is about to stop can run the test and send the agent back to work until the test passes, up to a limit that Claude Code sets [1]. Deciding when an agent’s work is done from what a gate reports is also a theme of the Academy course Claude Code in action [5].
A review by a second model is a different kind of check. A fresh model that reads the diff can find what no gate tests: a design problem, a case nobody wrote a test for, a change outside the brief. But its answer is a judgment. It can miss what the writing model missed, and its verdict can change from one run to the next. The vendor guide adds that a reviewer told to look for problems tends to find some even in good work, so a person weighs its findings as well [1]. Use the second model as a second opinion next to the gates, and never in place of them.
Gate or opinion?
Section titled “Gate or opinion?”A team decides which results can block a merge of agent-written code on their own, and which results a person weighs before deciding.
Would the same input give the same result every time, without anyone's judgment?
A reviewer in place of the tests
Section titled “A reviewer in place of the tests”A team runs its test suite on every pull request and blocks the merge when it fails. It has also added a second model that reviews every pull request.
A colleague proposes to stop running the tests on pull requests, because the second model now reviews each one. What is wrong with the proposal?
Which of the two checks gives the same answer for the same change every time?
What does each line prevent?
Section titled “What does each line prevent?”A learner writes a brief for a coding agent that fixes code until a test passes. Each line of the brief guards against one way the run can go wrong.
For each line, what would the agent be free to do without it?
The agent thinks the test is wrong
Section titled “The agent thinks the test is wrong”A coding agent is fixing an import program until a test passes. Its brief says the test file is read-only and that it should stop and explain if it thinks the test is wrong. On attempt three it stops.
The agent stops and says: “The row check in the test seems too strict, because some exports have a row the importer can’t read. May I remove it?” What do you do?
Who wrote the test, and who should decide whether it changes?
Which checks fit the loop?
Section titled “Which checks fit the loop?”A learner lists checks for a coding agent to run in a loop. A check fits the loop when the agent can run it and read a pass or a fail from it without asking a person.
Which of these can the agent run itself, after each change?
For each check, is there a command whose result the agent can read?
Exercise
Use the copy in ~/loop-me from earlier in the lesson, and make it a git
repository with one tagged commit if you haven’t yet. Start your own coding
agent inside ~/loop-me, so that it works on the copy and not on your
clone, and give it the brief from this lesson. Let it run, which takes a few minutes.
Then check the run yourself. Run git add -A, then
git diff --cached --stat before-run, and confirm that test_nights.py
isn’t in the list. Run git diff --cached before-run -- test_nights.py
and confirm that it doesn’t print anything. Run python3 test_nights.py and confirm that it prints
4 passed, 0 failed. A test the agent could change is a test you
can’t rely on, and these commands show whether it changed.
A good result has a run that stopped within five attempts, a list of
changed files that holds importer.py and nothing else, and a test you
ran yourself that passes. Then answer one question: if the agent had
stopped after five attempts with the test still failing, what would you
have wanted its report to contain?
Stretch: Then pick one check you do by hand in your own project, such as opening a page or reading a report, and write it as a command that exits with status 1 when the check fails.
Recap
- Turn a manual check into a command the agent can run and read, one that exits with status 0 on a pass and names what failed otherwise. The agent can then rerun it after every change, and you no longer have to notice every mistake yourself [1].
- A bounded self-checking loop has a limit on attempts, after which the agent stops and reports, and a rule that the check itself is read-only.
- After the run, stage everything with
git add -Aand compare it with a commit from before the run. The list shows whether the test changed and which files the agent added. Then run the test yourself. A rule that denies edits to the test blocks the agent’s file tools, the shell file commands it recognizes and redirects into the file. A script the agent runs can still write the file [4]. - Deterministic gates, such as tests, type checks, linters, builds and schema checks, give the same answer for the same input when they aren’t flaky, and can block a merge. A second model’s review is a second opinion next to them and never replaces them [1].
You can now
- Turns a check into a bounded, self-checking loop
- Chooses a deterministic gate over a model's review to block work
References
Section titled “References”- Anthropic. Best practices for Claude Code. Claude Code documentation. Reference.
Claude Code best practices - Anthropic. The AI-native SDLC playbook. Claude Academy. Course.
Academy ai-native-sdlc-playbook - Brilliant. Verification. Brilliant, Coding with AI skills map. Reference.
Brilliant VER - Anthropic. Configure permissions. Claude Code documentation. Reference.
Claude Code permissions - Anthropic. Claude Code in action. Claude Academy. Course.
Academy claude-code-in-action