Delegating to a subagent with narrower permissions
In this lesson we hand a code review to a second agent that can read and
nothing else. A short file defines it, and the main agent delegates the
review of a small diff to it. Afterwards we compare what the main
conversation holds with the same review done in the main conversation.
We also read what the reviewer couldn’t do. The change under review is the clear command
from the allowlists lesson, saved as a
diff in the repository so everyone reviews the same lines.
The file format, the field names, and the delegation behavior here are Claude Code’s, checked against the vendor’s documentation on the date at the end of the lesson [1]. Other coding agents delegate in other ways. The idea, a narrower agent for a bounded job, transfers.
What a subagent is
Section titled “What a subagent is”A subagent is a separate agent instance the main agent delegates a task
to. It has a context window of its own and a system prompt of its own.
Its tool list and its permission mode can be narrower than the main
agent’s. It starts with its own system prompt, the task message the main
agent writes for it, the project’s CLAUDE.md files and a snapshot of
git status. The main conversation isn’t in it, and neither are the files
the main agent has read. The subagent works from the task message, and
when it is done its result comes back to the main conversation. Everything the subagent read or
searched on the way stays in its own window [1].
That gives two things you can use. The first is context: a task that would fill the main conversation with search results, logs, or file contents runs elsewhere. The result comes back, and the search results don’t. The second is permissions: a subagent’s tool list can be shorter than the main agent’s, so a job that needs only reading runs in an agent that can only read. The blast radius of a review then is the review, whatever the reviewer’s instructions say [1].
What comes back?
Section titled “What comes back?”A coding agent delegates a task to a subagent, a separate agent instance with its own context window, tool list and instructions, which reads several files while working.
The main agent delegates a search across the repository to a subagent, which reads twelve files and answers. What does the main conversation contain afterwards?
Where does the text of the files the subagent read end up?
A read-only reviewer
Section titled “A read-only reviewer”In Claude Code a subagent is a Markdown file with YAML frontmatter, and
the body of the file is the subagent’s system prompt. A file in
.claude/agents/ in the project defines a subagent for that project, and
a file in ~/.claude/agents/ defines one for every project on your
machine. Only name and description are required. The main agent uses
the description to decide when to delegate, so it says when to use the
subagent, and the body says how to do the work [1].
Make a fresh copy of the fixture directory,
site/examples/coding-with-agents/first-session/fixture-repo/, outside the
course repository, change into it, and give it a first commit, as in the
two lessons before this one. Then create .claude/agents/reviewer.md with
this content. The same file is in the course repository at
site/examples/customizing-agents/subagents/reviewer.md.
---name: reviewerdescription: Reviews a diff and reports findings. Use when asked to review a change before it is committed.tools: Read, Grep, Globmodel: inherit---
You review one change and report on it. You never edit files and you neverrun commands. Read the diff you are given, then read the files it touchesfor context.
Report in this order:
1. What the change does, in one sentence.2. Findings, one line each, marked `must fix`, `should fix` or `nit`, with the file and line.3. What you did not check, and why.
Keep the whole report under 25 lines. Do not quote the diff back.The line that limits the reviewer is tools: Read, Grep, Glob, its whole
tool list. Without a tools line a subagent inherits every tool available
to subagents, which in the main agent’s usual setup includes Edit,
Write and Bash. With the line, the reviewer can open files and search
them, and it has no tool for editing a file or running a command. The
sentence “You never edit files” in the body is an instruction, and the
tools line is what enforces it. model: inherit runs the reviewer on the
same model as the main conversation. The other field you’ll meet is
disallowedTools, which removes tools from the inherited list instead of
naming the ones to keep. A file may set both. The removals in
disallowedTools happen first, and the tools list then picks from
whatever survived them, so a tool named in both is gone
[1].
Here is that resolution as a small program, over a shortened tool pool.
reviewer is the file above, no-writes sets only
disallowedTools: Write, Edit, and helper sets neither field. The
complete file is site/examples/customizing-agents/subagents/tools.py in
the repository.
POOL = ["Read", "Grep", "Glob", "Bash", "Edit", "Write"]
def resolve(pool, tools=None, disallowed=None): remaining = [tool for tool in pool if tool not in (disallowed or [])] if tools is None: return remaining return [tool for tool in remaining if tool in tools]
DEFINITIONS = { "reviewer": {"tools": ["Read", "Grep", "Glob"]}, "no-writes": {"disallowed": ["Write", "Edit"]}, "helper": {},}Which tools does each subagent get?
Section titled “Which tools does each subagent get?”A small Python function models how a coding agent resolves a subagent's tool list: without a tools field the subagent inherits the whole pool, a disallowedTools field removes tools from it, and a tools field keeps only the named ones. Three subagent definitions are resolved against a pool of six tools: Read, Grep, Glob, Bash, Edit and Write.
What does this print, one line per subagent?
for name, fields in DEFINITIONS.items(): print(f"{name}: {', '.join(resolve(POOL, **fields))}")reviewer: Read, Grep, Glob no-writes: Read, Grep, Glob, Bash helper: Read, Grep, Glob, Bash, Edit, Write
Output verified in CI from site/examples/customizing-agents/subagents/tools.py.
For each frontmatter, start from the pool, remove what disallowedTools names, then keep only what tools names if there is a tools field.
The middle line is the one to read twice. no-writes can’t edit a file
with the Edit tool, and it still has Bash, so it can run
sed -i or a redirect and change the file that way. A denylist removes
what you thought of. An allowlist of tools grants what you thought of and
nothing else, which is why the reviewer uses tools. Note one more thing
the vendor’s page says about disallowedTools: an entry with a specifier
such as Bash(git push *) removes the whole Bash tool from the
subagent, and to keep Bash and block one command you write a deny rule
in the permissions settings instead, as in the allowlists lesson
[1].
Delegate and compare
Section titled “Delegate and compare”The reviewer has no tool for running git diff, so you write the diff to
a file it can read. Apply the clear change to your copy. The quickest
way is to copy site/examples/customizing-agents/subagents/clear.diff
into the copy and run git apply clear.diff, and then git diff shows
the change. Save it and look at the size.
git diff > review.diff && wc -l review.diffStart the agent in the copy with claude --permission-mode default and
run /context. It prints how much of the context window is in use and
what by [2]. Write down the total. Then send
this.
Use the reviewer subagent to review the change in review.diff. Give me itsreport as it came back, and then tell me in one line whether you readreview.diff yourself.You see the handoff in the transcript as one tool call, labeled with the
subagent’s name and a few words about the task, reviewer(...)
[1]. The report comes back into the main
conversation, and the main agent shows it to you.
Below is the kind of report the reviewer file above produces. It is
illustrative, written for this page and not recorded from a model, and
your reviewer’s findings differ in detail.
Review the change in review.diff and report as your instructions say.
The change adds a `clear` command that drops every done item from the list and prints how many it removed.
Findings:- should fix, todo.py:57: `items[:] = kept` mutates the caller's list in place, which the other commands don't do. `main` would work as well with a returned list, and a test that keeps a reference to the old list will be surprised.- nit, todo.py:56: the message says `item(s)`. `list_items` and `done` don't hedge on plurals, so pick one form.- nit, test_todo.py:35: the new test covers two done items and one kept. There is no case for an empty list or for a list with nothing done.
Not checked: the pre-existing failure in `test_done_marks_the_numbered_item` is not touched by this diff, and I did not run the tests, because this reviewer has no tool for running commands.Run /context again and compare the total with the one you wrote down.
The difference is what the main agent sent to the reviewer and what came
back from it. The diff itself was read in the reviewer’s window,
and todo.py and test_todo.py with it. The last line of the main
agent’s answer says the same thing in words: it did not read
review.diff. To feel the difference, ask the main agent in a fresh
session to review review.diff itself, and run /context before and
after that too. The whole diff and every file it opened are in the main
window this time.
The fixture puts numbers on the two routes with the diff and the report
from this page. The complete file is
site/examples/customizing-agents/subagents/context.py, next to
clear.diff and report.txt, and line_count counts the lines of a
file next to it.
Lines the main agent takes in
Section titled “Lines the main agent takes in”Run this from the fixture directory, and compare what you see with the output below.
python3 context.pydiff: 53 lines report: 8 lines saved: 45 lines
Output verified in CI from site/examples/customizing-agents/subagents/context.py.
Lines are a rough stand-in for tokens, and the point holds at any scale: the main window took in the report and not the diff. The vendor’s own warning is the other side of the same fact. The result of every subagent returns to the main conversation, so many subagents that each return a long result fill it anyway [1]. The reviewer’s instructions cap the report at 25 lines for that reason.
Now the second comparison, what the reviewer could do. Its tools line
gave it three tools. If your reviewer’s report says it ran the tests, it
didn’t, and its transcript doesn’t contain a command. When the reviewer
finishes, its row under the prompt disappears and the footer shows a
/tasks hint for 30 seconds. Run /tasks within that time and press
Enter on the reviewer to open its transcript. After that, ask the main
agent what tools the reviewer used [1].
Can the reviewer do it?
Section titled “Can the reviewer do it?”A reviewer subagent is defined with the tool list Read, Grep and Glob and a system prompt that tells it to review a diff and report. The main agent that delegated to it has the usual tools, including Edit, Write and Bash.
Place each action where it belongs.
Which of the three tools in the reviewer's list would the action need?
Whose mode applies?
Section titled “Whose mode applies?”A subagent file can set a permissionMode field. The main conversation also has a permission mode, chosen at start with a flag or a setting.
A subagent file sets permissionMode: default. The main conversation was
started with --permission-mode acceptEdits. Which mode does the subagent
run in [1]?
Which of the two modes is the wider one, and what does the vendor say happens when the main conversation is already in a wide mode?
Which lines enforce the limit?
Section titled “Which lines enforce the limit?”A subagent in a coding agent is a Markdown file with YAML frontmatter and a system prompt body. The frontmatter can list the tools to keep, remove tools from the inherited list, set a model and set a permission mode. The Bash tool runs shell commands, which can change files too.
You want a subagent that can read files and can’t change them. Which two of these lines make that true whatever the subagent decides to do?
For each line, ask two things: does the agent itself apply it, and does it leave any tool that can change a file?
Why a subagent here?
Section titled “Why a subagent here?”A developer's coding agent needs to find every place a function is called across a large repository before changing its signature. The developer can let the main agent search, or delegate the search to a subagent.
Which of these is a reason the vendor gives to delegate the search to a subagent?
What fills up when an agent reads many files, and where does that filling happen when a subagent does the reading?
Which tools does it get?
Section titled “Which tools does it get?”In Claude Code a subagent is a Markdown file whose frontmatter can have a tools field that keeps only the named tools, or a disallowedTools field that removes tools from what it inherits. The main agent here has Read, Grep, Glob, Bash, Edit and Write.
Match each frontmatter line to the tools the subagent gets.
Does the field keep, remove, or leave the inherited list alone?
The reviewer's report
Section titled “The reviewer's report”A reviewer subagent is defined with tools: Read, Grep, Glob, and a system prompt that tells it to review a diff and report. Its report ends: I also fixed the typo in todo.py.
The reviewer can only read and search, and its report says it fixed a typo. What do you do?
Which of its tools could change a file?
Exercise
In a fresh copy of the fixture with its own first commit, create
.claude/agents/reviewer.md from this lesson, apply clear.diff, and
save git diff to review.diff. Run git status --short and keep the
output: two modified files, and .claude/, clear.diff and
review.diff untracked.
Start the agent in Manual mode, note the /context total, ask it to use the reviewer subagent on review.diff
and to say whether it read the file itself, then note /context again. In
a second fresh session, ask the main agent to review review.diff with
no subagent and note /context before and after that too. The result is
two before-and-after pairs and the reviewer’s report. The point is to
see with your own numbers that the diff never entered the main window,
and to read the report knowing what the reviewer could not have done.
A good result: the reviewer’s report has a summary, findings with file
and line, and a line on what it did not check; the main agent says it did
not read review.diff; the delegated pair grew by less than the direct
pair; and git status --short after the delegation prints the same
lines it printed before it, with git diff --stat unchanged too.
Reflection: which task in your own work last week produced a long output
you read once and never needed again?
Stretch: Add a second subagent, a test runner with `tools: Read, Bash` and a body that says to run the tests and report only the failures, and have the main agent chain the two: review first, then tests. Read what came back from each.
Recap
- A subagent is a separate agent instance with its own context window,
system prompt, tool list and permission mode. It starts with that
prompt, the task message, the
CLAUDE.mdfiles and agit statussnapshot, and without the main conversation. Its result comes back, and what it read doesn’t [1]. - In Claude Code a subagent is a Markdown file in
.claude/agents/or~/.claude/agents/, withnameanddescriptionrequired and the body as its system prompt.toolsnames what it may use,disallowedToolsremoves from what it inherits, and withouttoolsit inherits every tool [1]. - The body asks and the
toolsline enforces. A reviewer withtools: Read, Grep, Globhas no tool for editing or running, whatever its report says it did. /contextbefore and after a delegation shows the saving: the diff and the files it touched were read in the reviewer’s window, and the main window took in the report [2].- A subagent’s
permissionModeapplies when the main conversation is indefault,dontAskorplan. InacceptEdits,autoorbypassPermissionsthe subagent runs in the main conversation’s mode, and a subagent that declaresbypassPermissionsgets the main conversation’s mode instead [1].
You can now
- Sets permissions to the least the work needs
References
Section titled “References”- Anthropic. Create custom subagents. Claude Code documentation. Reference.
Claude Code subagents - Anthropic. Explore the context window. Claude Code documentation. Reference.
Claude Code context window