Skip to content

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 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:

Terminal window
git init -q && git add -A && git commit -qm start

Delete the copy and copy again to reset.

Look at what is there before the agent does.

Example · run it

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.

Terminal window
find . -type f -not -path '*/.*' | sed 's#^\./##' | LC_ALL=C sort | xargs wc -l
Output
    8 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.

Example · run it

Run the tests and keep the last line.

Terminal window
python3 -m unittest -q 2>&1 | tail -1
Output
OK

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.

Start your coding agent in the copy and give it the brief an engineer types when in a hurry.

Prompt (illustrative, not a recorded transcript)

Let me un-mark a done item.

Response

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.

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.

Checkpoint · choice

Look at the illustrative response once more. What did the missing scope cost?

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.

Example · run it

Print lines 16 to 21 of todo.py with their line numbers. The brief in the next section points at these lines.

Terminal window
awk 'NR >= 16 && NR <= 21 { print NR "\t" $0 }' todo.py
Output
16	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.

Checkpoint · match

Which reference does each line of the brief need?

Section titled “Which reference does each line of the brief need?”

Match each need in the brief to the reference that meets it.

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.

Prompt (illustrative, not a recorded transcript)

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.

Response

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.

Example · run it

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.

Terminal window
python3 -m unittest -q 2>&1 | tail -1
Output
OK

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.

Terminal window
git status --short

git status names two files, todo.py modified and test_undo.py new. The undo command itself:

Example · run it

Run undo and then list against a copy, so the committed file doesn’t change.

Terminal window
cp todos.json /tmp/todos.json
TODO_FILE=/tmp/todos.json python3 todo.py undo 2
TODO_FILE=/tmp/todos.json python3 todo.py list
Output
open #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.

Checkpoint · repair

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.

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

  1. 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].
  2. 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.
  3. 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].
  4. End the brief with the check that proves the task done, and run it yourself.
  5. 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

  1. Anthropic. Best practices for Claude Code. Claude Code documentation. Reference. Claude Code best practices
  2. Anthropic. Claude Code 101. Claude Academy. Course. Academy claude-code-101
  3. Addy Osmani, Ivar Soares Urdalen, Leo Simons. AGENTS.md: contents, monorepo hierarchies, with a builder widget. Agent Engineer Course. Course. AEC-15