Telling the agent where the task is and where it stops
In this lesson we give a coding agent the same small task twice. The first brief is one sentence, and we watch which files the agent reads to make sense of it. The second brief says where the task is and where it stops: which files are in play, which are off limits, which function to imitate, which test to extend and which command proves it done. Then we compare the two sessions on the files they read and the diff they produce.
The brief is the agent’s context, and the agent grounds its work in what it reads, as the retrieval lesson showed for a chat model: an answer rests on the passage it was given, and the passage can be the wrong one. A coding agent that picks its own files can pick the wrong ones too. The vendor of Claude Code gives the same advice for its tool: say which files are in play, what must not change, and which existing code the new code should copy [1], and its short course has a module on managing context [2]. Where this lesson names a Claude Code feature it says so, and the source is checked on the date at the end of the lesson.
The fixture
Section titled “The fixture”The fixture is the to-do program from Plan first, then drive the change
from a failing test, with the clear bug fixed and a few weeks of growth.
It has grown an export.py that writes the list as CSV and a
docs/SPEC.md that specifies every command. A legacy/ directory holds
the first version of the program, one file, kept for reference. It is in the course
repository under
site/examples/coding-with-agents/scoping-and-referencing/fixture-repo/.
Copy that directory to a place of its own and change into it, as in the
last lesson. This time also make the copy a git repository with one
commit, so that git status can show what a session changed:
git init -q && git add -A && git commit -qm startDelete the copy and copy again to reset.
Look at what is there before the agent does.
Count the lines per file
Section titled “Count the lines per file”Run this from inside the copy. The course prints the same table from its
own copy of the fixture, and your wc may pad the numbers differently.
The find leaves out hidden files such as .git/ and a .DS_Store, and
the sed strips the ./ that find prints.
find . -type f -not -path '*/.*' | sed 's#^\./##' | LC_ALL=C sort | xargs wc -l8 AGENTS.md 28 README.md 16 docs/SPEC.md 28 export.py 57 legacy/todo_v1.py 13 render.py 27 store.py 45 test_todo.py 55 todo.py 18 todos.json 295 total
Output verified in CI from site/examples/coding-with-agents/scoping-and-referencing/files.py.
Ten files, 295 lines. An agent can read the whole repository. Even here, a vague brief makes it read files it doesn’t need, and one of those files is a trap.
Run the tests
Section titled “Run the tests”Run the tests and keep the last line.
python3 -m unittest -q 2>&1 | tail -1OK
Output verified in CI from site/examples/coding-with-agents/scoping-and-referencing/tests.py.
The task for this lesson is one docs/SPEC.md already specifies and nobody
has built: an undo command that marks a done item as open again. The
spec says it prints open #<n>: <text>, and no item #<n> when there is
no such item.
What a vague brief makes the agent read
Section titled “What a vague brief makes the agent read”Start your coding agent in the copy and give it the brief an engineer types when in a hurry.
Let me un-mark a done item.
I searched the repository for how items are marked and unmarked, and read
todo.py, store.py, render.py, export.py, legacy/todo_v1.py and
test_todo.py.
legacy/todo_v1.py already has an undone command that does this, with a
mark(items, number, done) helper the done command shares. I’ll port that
design into todo.py with a mark helper and a rewritten done, and add
an undone command that prints reopened: <text>. export.py gets a
state value for reopened items so the CSV shows them. Go ahead?
The transcripts in this lesson are illustrative: they show what such an exchange looks like and don’t record a specific model’s words. Your agent phrases it differently, and it may read a different set of files.
Read the response as a list of what the agent did with the room the brief
gave it. It read six of the ten files. It found the nearest plausible
answer in legacy/, a file the project keeps for reference and nobody
imports. The old command name and the old message come from there. It
proposed to rewrite done, which works and which nobody asked about, and
to change export.py, which the task doesn’t touch. None of that is a
model failure. The brief said nothing about where the task is or where it
stops, and the agent filled the gap from what its search found.
You can see why the search led there. The words of the brief are the words the agent greps for first.
Search for the words of the brief
Section titled “Search for the words of the brief”List the files that contain each word, ignoring case. The course runs the same search over its copy of the fixture and prints a count above each list.
for word in done undo; do grep -ril --exclude-dir=.git "$word" . | sed 's#^\./##' | LC_ALL=C sortdonedone: 9 files README.md docs/SPEC.md export.py legacy/todo_v1.py render.py store.py test_todo.py todo.py todos.json undo: 2 files docs/SPEC.md legacy/todo_v1.py
Output verified in CI from site/examples/coding-with-agents/scoping-and-referencing/search.py.
done is in nine of the ten files, so it narrows nothing. undo is in
two, and one of them is the legacy file, where it is part of undone. An
agent that starts from a grep opens legacy/todo_v1.py and reads it with
the same attention as todo.py. The spec, the file that says what
undo should do, is the other hit, and the agent in the transcript didn’t
open it.
Scoping a task is telling the agent where the task is and where it
stops: which module, which files are in play and which are off limits. It
has two effects you can see. The agent reads less, so more of its context
window is left for the work, and the vendor’s own guidance names an
unscoped investigation that reads hundreds of files as a common failure
[1]. And the change stays where you put it. A
well-meaning refactor of done, or a column in the CSV, is a change that
spreads because nothing told the agent to stop.
What did the vague brief cost?
Section titled “What did the vague brief cost?”A learner briefed a coding agent with one sentence on a small repository. The agent read most of the files, found an old command in a legacy directory that nobody imports, and proposed to port its design and to change an export module the task doesn't concern.
Look at the illustrative response once more. What did the missing scope cost?
Which of these follows from the brief saying nothing about where the task stops?
Point at the file instead of describing it
Section titled “Point at the file instead of describing it”The second half of the fix is referencing files: naming the exact file,
function or document the agent should read, so it loads the right context
on the first try. “The function that marks an item done” is a description,
and the agent has to search for what matches it, which is how it reached
legacy/. A path and a line range are an address, and there is only one
thing at an address.
Read what the reference points at
Section titled “Read what the reference points at”Print lines 16 to 21 of todo.py with their line numbers. The
brief in the next section points at these lines.
awk 'NR >= 16 && NR <= 21 { print NR "\t" $0 }' todo.py16 def done(items, number):
17 index = number - 1
18 if index < 0 or index >= len(items):
19 return f"no item #{number}"
20 items[index]["done"] = True
21 return f"done #{number}: {items[index]['text']}"Output verified in CI from site/examples/coding-with-agents/scoping-and-referencing/reference.py.
That is the example to imitate. A brief that says “add undo next to
done in todo.py lines 16 to 21, with the same arguments and the same
checks” leaves the agent one thing to read and one pattern to follow. The vendor’s guidance for
Claude Code says the same in its own words: point the agent at an existing
pattern in the codebase and name a file that is a good example of it
[1].
A brief most often needs the same references. The example to imitate
is the code the new code should look like, here done at todo.py lines
16 to 21. The test to extend is the test the new one should sit next
to, here test_done_marks_the_numbered_item at test_todo.py line 21.
The spec to follow is the document that says what the result should
do, here the undo row of docs/SPEC.md. Each one replaces a search with
a read.
In Claude Code, an @ before a path in the prompt makes the agent read
that file before it responds, so @todo.py is a reference the tool acts on
as well as a hint to the model [1]. In another
agent, write the path. The agent reads it, and a path it can open is exact where
a description it has to match is a guess.
Which reference does each line of the brief need?
Section titled “Which reference does each line of the brief need?”A brief for a coding agent can point at three kinds of file: the code the new code should look like, the test file the new test should join, and the document that says what the result should do.
Match each need in the brief to the reference that meets it.
For each line, ask what the agent would otherwise have to search for.
Add what the code can’t show, then run it again
Section titled “Add what the code can’t show, then run it again”What is left is what no file in the repository says. The task-level
boundary comes first: store.py, render.py and export.py are not part
of this change, and legacy/ is never part of any change. The project’s
instruction file, AGENTS.md, already contains the standing rule about
legacy/, which is where a boundary that holds for every task belongs
[3]. The boundary for this task goes in the brief. Then the check:
the command that says the task is done, here the test suite.
Reset the copy, start a fresh session, and hand over the scoped brief.
Add an undo command to todo.py. Imitate done at todo.py lines 16 to
21: same arguments, same no item #<n> handling, and it sets done to
False. The message is open #<n>: <text>, as the undo row of
docs/SPEC.md says. Add the undo branch in main next to the done
branch. Add a test next to test_done_marks_the_numbered_item in
test_todo.py (line 21), or in a new test_undo.py. Change nothing in
store.py, render.py, export.py or legacy/, and leave todos.json
as it is. Done when python3 -m unittest -q prints OK.
I read todo.py, test_todo.py and the undo row of docs/SPEC.md.
Added undo(items, number) to todo.py after done, with the same index
check, setting done to False and returning open #<n>: <text>. Added
an undo branch in main after the done branch. Added test_undo.py
with a test for the normal case and one for a bad number.
python3 -m unittest -q prints OK. Files changed: todo.py,
test_undo.py (new).
The agent read todo.py, test_todo.py and the spec, where the vague
brief made it read six files, and it never opened legacy/. The agent
didn’t rewrite done, and export.py isn’t in the list, because the brief
said where the task stops. Run the check yourself.
Run the suite after the change
Section titled “Run the suite after the change”Run the tests and keep the last line. The course applies the change the response describes to its own copy and checks this output against it.
python3 -m unittest -q 2>&1 | tail -1OK
Output verified in CI from site/examples/coding-with-agents/scoping-and-referencing/tests_after.py.
Then read the diff and prove the command against the list, on a copy.
git status --shortgit status names two files, todo.py modified and test_undo.py new.
The undo command itself:
Reopen a done item
Section titled “Reopen a done item”Run undo and then list against a copy, so the committed file doesn’t
change.
cp todos.json /tmp/todos.jsonTODO_FILE=/tmp/todos.json python3 todo.py undo 2TODO_FILE=/tmp/todos.json python3 todo.py listopen #2: Call the plumber 1. [ ] Buy milk 2. [ ] Call the plumber 3. [x] Renew the passport 4. [ ] Water the plants 3 open, 1 done
Output verified in CI from site/examples/coding-with-agents/scoping-and-referencing/undo_after.py.
The message is the one the spec asked for, because the brief named the spec. Compare the two sessions once more. The first read six files and proposed changes to two, one of them a rewrite nobody asked for. The second read three files and changed one, plus a new test. The diff a reviewer gets from the second session is one they can read in a minute. Scoping buys a smaller read for the agent now and a smaller read for the reviewer later.
Fix the brief
Section titled “Fix the brief”A colleague hands this brief to the agent right after the undo change.
Rewrite it so it names the file and lines in play, the files off limits,
the spec that decides, and the check. Notice first whether the task should
be done at all.
In export.py, the rows() function (lines 13 to 16) sets the state column to "done" or "open". Nothing records that an item was reopened, and docs/SPEC.md has no such state, so don't add one. Leave the export as it is and close the task. If you disagree, say why before changing anything. Do not touch todo.py, store.py, render.py or legacy/. Done when python3 -m unittest -q still prints OK.
Which file would the agent have to search for, and which file should it never open?
Which lines scope the task or reference a file?
Section titled “Which lines scope the task or reference a file?”A brief for a coding agent can name where a task is and where it stops, and can point at files by path instead of describing them. Some lines in a brief do that work and some don't.
Which of these do the work of a scoped brief?
Which lines would change what the agent reads, or where it stops?
Instruction file or brief?
Section titled “Instruction file or brief?”The lesson puts a boundary that holds for every task in the project's instruction file, and the boundary and references for one task in that task's brief. The project has a legacy/ folder that nothing imports.
Does this line hold for every task in the project, or only for this one?
Exercise
Reset the copy of the fixture and run both sessions yourself with your
coding agent. Give it the one-sentence brief first and write down every
file it reads, from its own report or from the tool calls it shows. Reset
the copy, start a fresh session, and give it the scoped brief from this
lesson. Write down the files it reads again, run python3 -m unittest -q,
and read git status --short. Plan on ten minutes.
A good result: the second session read fewer files than the first and
didn’t open legacy/, the diff touches todo.py and one test file, and
the message matches the undo row of docs/SPEC.md. Finish by deleting
the copy. Then answer one question: which line of the scoped brief kept
the agent out of legacy/, and would the AGENTS.md rule alone have done
it?
Stretch: Do the same for a task you choose in a repository of your own. Write the vague brief first, then the scoped one, and count the files the agent reads for each.
Recap
- Scope a task by saying which files are in play and which are off limits. The agent reads less, and the change stays where you put it [1].
- Reference files by path instead of describing them. Name the example to imitate, the test to extend and the spec to follow, or the agent imitates the first file that looks similar.
- A boundary that holds for every task belongs in the project’s instruction file, and the boundary for this task belongs in the brief [3].
- End the brief with the check that proves the task done, and run it yourself.
- A scoped brief gives the reviewer a small diff, and a small diff is the one that gets read.
You can now
- Gives the agent the files, constraints and limits the task needs
References
Section titled “References”- Anthropic. Best practices for Claude Code. Claude Code documentation. Reference.
Claude Code best practices - Anthropic. Claude Code 101. Claude Academy. Course.
Academy claude-code-101 - Addy Osmani, Ivar Soares Urdalen, Leo Simons. AGENTS.md: contents, monorepo hierarchies, with a builder widget. Agent Engineer Course. Course.
AEC-15