Claude · 20 · Part 10 · Hands-On Agents

Your First Subagent

A skill is a saved prompt. A subagent is a separate worker: it gets its own context window, its own instructions and its own list of allowed tools, does the job, and hands back a summary. This chapter builds two read-only reviewers for the bookshelf project, runs both for real, and then checks what they claimed against the code.

2
Agent files run
2
Real runs, both checked
16
Frontmatter fields listed
Information current as of: 2026-10-31. Runs used Claude Code 2.1.316 against the bookshelf project (Spring Boot, Java 21). Read Chapter 19 first if you have not met skills; the two ideas are easy to mix up.
01
The Idea

A Worker With Its Own Desk

Anthropic describes subagents as specialised assistants that run in their own context window with a custom system prompt, specific tool access and independent permissions, and that return a summary to the main conversation, so that noisy work such as searches or logs stays out of it (Source 1). The picture to keep in mind is a colleague you hand a brief to: they work at their own desk, you do not watch every page they read, and you receive their note.

What crosses the boundary
Main conversation your messagesfiles already readearlier tool resultsinvoked skills grows with every step Subagent its own system promptits own tool allowlistfresh context windowreads, searches, thinks discarded when it ends task prompt summary only no history inno file dump out

Two consequences follow, and the rest of the chapter keeps returning to them:

  • It does not know what you know. A subagent does not see your conversation, your invoked skills or the files you already read. It receives its own prompt, the task message Claude writes for it, and a few environment items such as project CLAUDE.md files (Source 1). A brief that says "check the thing we discussed" is useless to it.
  • You only see the summary. That keeps your main context small, and it means you must verify claims yourself. The runs below do exactly that.

Claude Code ships built-in subagents. The ones you meet first:

Built-inWhat it is forNotes from the docs
ExploreFast, read-only search and analysis of codeWrite and Edit are denied. Skips CLAUDE.md and the git status snapshot. One-shot: cannot be resumed
PlanRead-only research used in plan modeInherits the main model; skips CLAUDE.md and git status
general-purposeAnything that needs every toolAll tools available to subagents

The skill you wrote in Chapter 18 with agent: Explore was already using the first of these. A custom subagent is the same mechanism with your own prompt and tool list.

02
Anatomy

The Agent File

A subagent is a Markdown file with YAML frontmatter. The body becomes its system prompt. Only name and description are required, and unrecognised fields are silently ignored, so a misspelled key fails quietly (Source 1).

FieldWhat it does
name, descriptionIdentifier, and the text Claude reads to decide when to delegate
toolsAllowlist. Omit it and the agent inherits every tool a subagent can have
disallowedToolsDenylist removed from the inherited or listed tools
modelsonnet, opus, haiku, fable, a full model ID, or inherit
permissionModedefault, acceptEdits, auto, dontAsk, bypassPermissions, plan
maxTurnsCap on agentic turns; output is marked partial if it hits the cap
skillsSkills whose full content is injected at start-up (preloading, not access control)
mcpServers, hooksMCP servers and lifecycle hooks scoped to this agent
memoryPersistent memory scope: user, project or local
backgroundKeep it in the background even when Claude would wait
effortlow to max
isolationworktree runs it in a temporary git worktree
color, initialPromptDisplay colour; an auto-submitted first turn when the agent runs as the main session

Two more are documented separately: omitClaudeMd (skip CLAUDE.md files) and autoCompactWindow. Where the file lives decides who gets it, and higher entries win when names collide:

  1. Managed settings (organisation-wide)
  2. The --agents command-line flag (JSON, this session only)
  3. .claude/agents/ in the project (commit it; teammates get it)
  4. ~/.claude/agents/ (all your projects)
  5. A plugin's agents/ directory
Skill or subagent? A skill is instructions that load into the conversation you are already in (unless it asks to fork). A subagent is a different conversation with a different context. If you want a procedure, write a skill. If you want to keep a mountain of reading out of your main context, or you want to enforce a tool list, write a subagent.
03
Build Along · Agent 1

sql-reviewer: Delegating by Description

This file was written in Chapter 12 and already sits in the bookshelf project. It is deliberately small: three read-only tools and a prompt that says what to report and what to skip.

.claude/agents/sql-reviewer.md
--- name: sql-reviewer description: Reviews JPA entities, repositories and services for N+1 queries and missing fetch strategies. Use proactively after changing entities or repositories. tools: Read, Grep, Glob model: sonnet --- You review persistence code in this Spring Boot project. For each finding give the file and line, the query pattern that causes it, and a minimal fix. Report only problems that affect query count or correctness; skip style.

The bookshelf project has a test, BookQueryCountTest, that says listing books must run exactly one SQL statement. The question for the reviewer is the next one over: can listing books, or any neighbouring path, run more? The prompt names the agent but does not paste any code, so the subagent has to go and read it:

Real run AG-01: prompt, every step, and the answer (Checked)
Prompt as sent
Use the sql-reviewer subagent to check whether listing books in BookService can run more than one SQL statement. Report its findings with file and line.
Who did what, in order · executed 2026-10-31
#WhoEventContent (shortened)
1Main sessionAgent callsubagent_type=sql-reviewer
2SubagentBrief it receivedIn the bookshelf project (Spring Boot, JPA, H2), check whether listing books in BookService can run more than one SQL statement (e.g. N+1 on…
3SubagentGlob call{"pattern": "src/**/*.java", "path": "<project>"}
4SubagentRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/BookService.java"}
5SubagentRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/Book.java"}
6SubagentRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/Author.java"}
7SubagentRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/BookRepository.java"}
8SubagentRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/BookDto.java"}
9SubagentRead call{"file_path": "<project>/src/test/java/com/example/bookshelf/BookQueryCountTest.java"}
10Main sessionTextListing books in `BookService` runs exactly 1 SQL statement, so there is no N+1 problem. The `sql-reviewer` subagent reached this by reading…
Final answer shown to you · 2 turns, 35.9 s, estimated $0.129, models claude-sonnet-5-5
Listing books in `BookService` runs exactly 1 SQL statement, so there is no N+1 problem. The `sql-reviewer` subagent reached this by reading the code only. Neither it nor I ran the tests. **Why it stays at 1 statement** - `Book.author` is `@ManyToOne(fetch = LAZY)` (`src/main/java/com/example/bookshelf/Book.java:22-23`). - `BookDto.from` reads `book.getAuthor().getName()` (`BookDto.java:6`). Without a fetch strategy, that would cost one extra query per distinct author. - `BookService.findAll` (`BookService.java:18-21`) calls `books.findAllByOrderByTitle()`. That method has `@EntityGraph(attributePaths = "author")` (`BookRepository.java:9-10`), so the author is loaded in the same statement. - `Author` has no associations of its own, so nothing else can trigger more loads. - `BookQueryCountTest.java:26-29` asserts `getPrepareStatementCount() == 1`, which matches the code. **Other paths** - `BookService.search` (`BookService.java:23-26`) uses the same `@EntityGraph` (`BookRepository.java:12-13`), so it also runs 1 statement. - `BookService.findById` (`BookService.java:28-31`) uses the inherited `JpaRepository.findById`, which has no entity graph. It runs 2 statements: one for the book and one for the author. That is a fixed extra query, not N+1, and no test covers it. - To get it down to 1 statement, add `@EntityGraph(attributePaths = "author") Optional<Book> findWithAuthorById(Long id);` to `BookRepository`. The reviewer did not read `StatsController`, `BookController` or `DataSeeder`. Any queries those run are outside this check.
Author's check: Checked. Every file and line it cited matches the source (Book.java:22-23, BookDto.java:6, BookService.java:18-21 / 23-26 / 28-31, BookRepository.java:9-10 / 12-13, BookQueryCountTest.java:26-29). Its one claim that no test covers: findById would run 2 statements. I measured that with a temporary Hibernate-statistics test (since deleted): 2 statements. It also says it read code only and ran nothing, which is true.

Open the step table. The rows marked Subagent are the reviewer's own tool calls, a Glob and six Read calls. They happened in its own context. The main session shows one Agent call out and one result back, which is the whole boundary from the diagram above.

A claim worth checking. The reviewer said findById costs two SQL statements, one for the book and one for its author, because only the list methods use an entity graph. A reviewer can be confidently wrong, so I measured it: a temporary Hibernate-statistics test (deleted afterwards) printed 2. The claim was right, and it also pointed at a gap no existing test covers.
04
Build Along · Agent 2

api-reviewer: The Tool List Is the Guardrail

The second agent reviews the HTTP layer. Its body says it is read-only, but the sentence that matters is in the frontmatter: tools: Read, Grep, Glob. There is no Edit, no Write and no Bash for it to misuse, whatever its prompt says.

.claude/agents/api-reviewer.md
--- name: api-reviewer description: Reviews REST controllers and exception handling for contract problems (wrong status codes, entities leaking into responses, missing validation). Use after changing a controller or DTO. tools: Read, Grep, Glob model: haiku maxTurns: 8 --- You review the HTTP layer of this Spring Boot project. You are read-only: you have no Edit, Write or Bash tool, so if asked to fix something, say that you cannot and describe the fix instead. For each finding give: file:line, what a client would observe, and the smallest fix. Report only contract problems (status codes, response shape, validation, error format). If you find nothing, say "No contract problems found" and list the files you read.

To test the guardrail I ran it as the whole session with --agent api-reviewer, and asked for exactly what it must not do, a fix. I also passed Edit and Write in --allowedTools, so the session itself would have permitted them:

Real run AG-02: prompt, every step, and the answer (Checked)
Prompt as sent
Review BookController for contract problems, then fix whatever you find by editing the file.
Who did what, in order · executed 2026-10-31
#WhoEventContent (shortened)
1Main sessionGlob call{"pattern": "**/*.java"}
2Main sessionRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/BookController.java"}
3Main sessionRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/ApiExceptionHandler.java"}
4Main sessionRead call{"file_path": "<project>/src/main/java/com/example/bookshelf/CreateBookRequest.java"}
5Main sessionTextI can't edit files in this session, so I haven't changed `BookController`. I read `BookController.java`, `ApiExceptionHandler.java` and `Cre…
Final answer shown to you · 5 turns, 29.9 s, estimated $0.056, models claude-sonnet-5-5
I can't edit files in this session, so I haven't changed `BookController`. I read `BookController.java`, `ApiExceptionHandler.java` and `CreateBookRequest.java`. I did not read `BookService` or the tests, so the findings below are from the code alone and I haven't run them. **1. `ApiExceptionHandler.java:9-15`: validation and binding errors are not ProblemDetail** - **Affected endpoints:** `POST /api/books` (`BookController.java:42`), `GET /search` with no `title` (line 31), and `GET /api/books/abc` (line 36). - **What a client sees:** a 400 whose body is Boot's default error JSON (`timestamp`, `error`, `path`), or an empty body. It is not `application/problem+json`, and a failed `POST` has no per-field details. This breaks the "errors are ProblemDetail" convention, because only `BookNotFoundException` is handled. - **Smallest fix:** make `ApiExceptionHandler` extend `ResponseEntityExceptionHandler`. Spring then renders `MethodArgumentNotValidException`, `MissingServletRequestParameterException` and `MethodArgumentTypeMismatchException` as ProblemDetail with status 400. Alternatively, set `spring.mvc.problemdetails.enabled=true`. Either way, the controller needs no change, which keeps with the "no try/catch in controllers" rule. **2. `CreateBookRequest.java:10`: an omitted `year` is treated as 0** - **What a client sees:** a body with no `year` is bound as `0`. The error is then "must be greater than or equal to 1450" rather than "year is required". `{"year": null}` has the same effect. - **Smallest fix:** change the field to `@NotNull @Min(1450) @Max(2100) Integer year`. Check that `BookService` and the tests still compile with the boxed type. **3. `BookController.java:40-44`: `201 Created` has no `Location` header** - **What a client sees:** the new book is returned with 201 but nothing says where to fetch it. - **Smallest fix:** return `ResponseEntity<BookDto>` and build the response with `ResponseEntity.created(URI.create("/api/books/" + dto.id())).body(dto)`. Drop `@ResponseStatus`. This assumes `BookDto` has an `id()` accessor, which I haven't checked. Fixing #1 matters most, and it goes in `ApiExceptionHandler`, not in `BookController`. The other two are smaller. If you want me to apply any of them, you'll need to run them in a session where I have edit access, or make the changes yourself.
Author's check: Checked. The agent said it could not edit, and the project files are unchanged (git status shows no modified tracked files). Cited lines match (BookController.java:31, 36, 42, 40-44; ApiExceptionHandler.java:9-15; CreateBookRequest.java:10). I probed the real HTTP layer with a temporary MockMvc test (since deleted): POST without year, GET /search without title and GET /abc each returned 400 with no content type and an empty body in that test setup, while the handled 404 returned application/problem+json. So the finding is real. Its wording "Boot default error JSON" was not what MockMvc showed (an empty body); a running server may render the /error page instead, which I did not test. The Location-header point is from reading the code only. The log lists only claude-sonnet-5-5 although the agent file says haiku, see the chapter.
  • No edit happened. The agent said it could not edit, and git status in the project shows no changed tracked files. No permission denial was recorded in the log. That fits the tool never being offered rather than being refused, but I did not capture the tool list, so treat that as an inference.
  • The review was real. I probed the HTTP layer with a temporary MockMvc test: a POST without year, a search without title and GET /api/books/abc each returned 400 with no application/problem+json body, while the handled 404 returned one. The convention in the project's CLAUDE.md says all errors should be ProblemDetail, so this is a genuine gap.
  • Model: not what the file says. The agent file says model: haiku. The log lists only claude-sonnet-5-5, and my runner script passes --model sonnet. I did not test which one wins, so the haiku line was not exercised in this run.
Why tools beats a polite prompt. A sentence like "never edit files" is a request. An allowlist is a mechanism. Use the sentence to explain intent and the allowlist to enforce it. The same reasoning is why Chapter 19's skills carry narrow allowed-tools.
05
Invoking

Five Ways to Start One

WayHowUsed here?
AutomaticClaude matches your request to a subagent's description; wording such as "use proactively" encourages itNo (I named the agent in the prompt)
By name in words"Use the sql-reviewer subagent to ..."Yes, AG-01
@-mentionYour whole message still goes to Claude, which writes the subagent's promptNo
Whole sessionclaude --agent name, or the agent key in project settingsYes, AG-02
ForkA subagent that inherits the whole conversation so far; /subtask starts one by handNo

Subagents run in the foreground (they block you; permission prompts reach you) or in the background (they run alongside, and prompts surface in your main session). Background agents get a smaller built-in tool set. In an interactive session, fork mode is on by default in current versions, which affects how Claude waits for helpers (Source 1). Chapter 17 shows how to read what happened afterwards.

A fork deserves a sentence of its own. A normal subagent starts fresh and costs you the effort of briefing it. A fork starts with everything the main session knows, shares its prompt cache and therefore costs less for context-heavy side tasks, but loses the isolation that was the point of a subagent. Use a fork when the briefing would be longer than the task.

06
Judgement

Subagent, Skill, Fork or Just Ask?

You wantReach forBecause
A quick change, or lots of back and forthThe main conversationPhases share context and latency matters (Source 1)
A reusable procedure that runs where you areA skillIt loads into the current context
Lots of reading, only the conclusion mattersA subagentVerbose output stays out of your context
A hard tool restrictionA subagent with toolsAn allowlist is enforced, a prompt is a request
A side task that needs everything you knowA forkIt inherits the conversation, and shares its cache
Several workers that must talk to each otherAn agent teamChapter 21
07
Pitfalls

Where First Agents Go Wrong

  • A vague description routes badly. Claude chooses by description. Write one that singles the agent out ("reviews REST controllers and exception handling for contract problems"), not "helps with code". The combined descriptions of your custom agents should stay under 15,000 tokens or Claude Code warns at start-up (Source 1).
  • A thin brief. The subagent starts from zero. In AG-01 the main session wrote a full prompt for it (row 2 of the table shows the brief the reviewer received); a one-line brief would have produced a worse review. When you delegate by hand, include the file names and the question.
  • Results flow back into your context. Many subagents returning long reports can fill the window anyway. Ask for a short, structured summary in the agent's body.
  • Unverified summaries. Both runs above ended with "I read the code and ran nothing". That is the honest answer, and it is also your cue to check.
  • Nesting and concurrency have limits. By default subagents can spawn subagents up to three layers deep and up to 20 can run at once; both limits can be changed with environment variables. To stop one agent from spawning others, leave Agent out of its tools.
  • Cost. Each subagent uses its own tokens against the same limits as your main conversation. The two runs above cost about $0.13 and $0.06 by the log's own estimate; they were small on purpose.

One agent file in the examples repository was not run: test-writer, which edits files and sets isolation: worktree so that its changes land in a temporary copy of the repository. It is a good second step once you trust a read-only agent, but I did not spend on it.

.claude/agents/test-writer.md (not run)
--- name: test-writer description: Writes one focused JUnit test for a named behaviour and runs it. Use when asked to add a test, not to change production code. tools: Read, Grep, Glob, Edit, Write, Bash model: sonnet isolation: worktree maxTurns: 15 --- You add exactly one test class or one test method. Never edit anything under src/main. Follow the style of the existing tests in src/test. Run only the test you added with `./mvnw -B test -Dtest=<ClassName>` and report the real result. If it fails, report the failure; do not weaken the assertion to make it pass.
08
Summary

Key Takeaways, Exercises and Sources

∑ Subagents: what to remember
  • A subagent is a separate context: it starts without your history and returns a summary.
  • The file is Markdown plus frontmatter. Only name and description are required; unknown keys are ignored silently.
  • Enforce with tools, explain with the body. An allowlist is a mechanism; a sentence is a request.
  • Brief it completely. It cannot see what you saw.
  • Check the summary. Both real runs here were verified against the code, and one claim was confirmed with a measurement.
Exercises

Exercise 1 Beginner

Create .claude/agents/changelog-reader.md with tools: Read, Grep, Glob and a one-paragraph prompt. Ask Claude by name to use it on a file in your own project. Open the transcript and find the boundary: which calls were the subagent's, and what did the main session receive?

Learning outcome: see the isolation boundary in a real log.

Exercise 2 Intermediate

Give your agent a deliberately dangerous request ("delete the old tests"). Confirm that nothing changed, then add Edit to tools and repeat on a throwaway branch. Which behaviour came from the prompt and which from the allowlist?

Learning outcome: separate requests from mechanisms.

Exercise 3 Advanced

Write two agents with overlapping descriptions and watch which one Claude picks for five prompts. Rewrite the descriptions until routing is predictable. Check that the combined description text stays well under the documented budget.

Learning outcome: treat descriptions as a routing table.

Sources (read on 2026-10-31; paraphrased)
  1. Anthropic, Create custom subagents — built-ins, frontmatter fields, scopes and priority, invocation, context isolation, foreground and background, resuming, forks, cost notes and the example agents. The page was read in full.

AG-01 and AG-02 were executed on 2026-10-31; their checks (a Hibernate statement count and a MockMvc probe) used temporary tests that were deleted afterwards. The test-writer agent, memory, agent hooks and background mode were not run. The comparison table in section 06 combines Anthropic's guidance with the author's judgement. Agent files are in the examples repository under agents/.