Building a custom subagent is mostly about discipline, not syntax. The file format is simple — frontmatter plus a system prompt. What separates a subagent that Claude actually uses from one that sits idle is a precise description, a tight tool scope, and a focused prompt. Get those right and the agent earns its keep.
This walks through it end to end: scaffolding the file, writing each frontmatter field, scoping tools, writing the system prompt, and testing that delegation fires. There’s a full example to copy, plus the mistakes that quietly break agents. If you want the conceptual background first, see the subagents guide.
Before you start
- Claude Code installed and working in a project
- A clear, single job for the agent
- Know whether it should be project-scoped or user-scoped
Step 1: Create the file
Two ways. The interactive route:
/agents
This opens an editor inside Claude Code that scaffolds the frontmatter, lists available tools as checkboxes, and saves to the right directory. It’s the safest way to start because you can’t misspell a field name.
The manual route is just a markdown file. Project agents go in .claude/agents/; user agents go in ~/.claude/agents/:
# project-scoped agent (committed with the repo)
mkdir -p .claude/agents
touch .claude/agents/test-writer.md
On Windows, the user directory is C:\Users\YourName\.claude\agents\. In PowerShell:
New-Item -ItemType Directory -Force -Path "$HOME\.claude\agents"
New-Item -ItemType File -Path "$HOME\.claude\agents\test-writer.md"
Step 2: Write the frontmatter
Four fields. Each does a specific job:
Frontmatter fields
| name | Lowercase, hyphenated, unique. How you invoke it: test-writer |
|---|---|
| description | When to use the agent — this drives delegation |
| tools | Comma-separated allowed tools (omit to inherit) |
| model | Model for this agent, or inherit (omit for default) |
The description is the field that determines whether Claude ever uses the agent automatically. Claude reads it and matches it against the task at hand. So write it as a trigger condition, not a label:
# weak — Claude has nothing concrete to match
description: Helps with tests.
# strong — clear trigger and scope
description: >
Use this agent to write or update unit tests after new code is
added or changed. Covers happy paths and edge cases using the
project's existing test framework and conventions.
Step 3: Scope the tools tightly
Give the agent only the tools its job requires. This isn’t just tidiness — it constrains what the agent can do, which makes its behavior predictable and prevents it from wandering outside its remit.
Tool scoping by agent type
| Agent type | Sensible tools |
|---|---|
| Reviewer / auditor (read-only) | Read, Grep, Glob |
| Explorer / context-gatherer | Read, Grep, Glob |
| Test writer | Read, Grep, Glob, Write, Edit, Bash |
| Refactorer | Read, Grep, Glob, Edit, Write, Bash |
If you leave tools out entirely, the agent inherits whatever the main thread can use. That’s convenient but rarely what you want for a focused agent — inheriting everything is how a “reviewer” ends up editing files. The complete list of tool names is in Anthropic’s tools reference.
Step 4: Write the system prompt
The body below the frontmatter is the system prompt. Write it for a specialist who knows the task but nothing about your project. Cover four things: the role, the steps, the rules, and the output format.
You are a test engineer. You write focused unit tests for code that
was just added or changed.
Your process:
1. Read the changed code and the existing tests for that module.
2. Match the project's test framework, file layout, and naming.
3. Cover the happy path plus edge cases: empty input, boundaries,
error conditions.
Rules:
- Only create or edit files under the project's test directory.
- Never modify production code.
- Run the test suite when done and report pass/fail.
- If you can't tell what the code should do, say so — don't guess.
Notice the constraints. “Only create or edit test files,” “never modify production code,” “don’t guess.” A focused prompt with explicit limits beats a long, abstract one.
Step 5: Test delegation, then iterate
Now check that it actually fires. Give the main thread a task that should trigger the agent and watch whether it delegates:
Add tests for the new pagination logic in the users endpoint.
If Claude delegates to your agent, the description works. If it does the work itself instead, the description is too vague — tighten the trigger condition. You can also invoke the agent by name to test the prompt independently of delegation:
Use the test-writer agent to cover src/api/users.ts
Iterating is normal. Run the agent on a few real tasks, see where it under- or over-reaches, and adjust the prompt and tools. An agent that’s too eager usually has a description that’s too broad; one that never gets used has a description that’s too vague.
Give the agent project context it can’t guess
A subagent starts each run cold. It doesn’t carry over memory from previous runs, so anything project-specific has to be either in the prompt or discoverable through the tools you gave it. Two approaches work well together. Put durable conventions — test framework, directory layout, naming rules — directly in the system prompt so they’re always present. For everything else, tell the agent where to look: “read the existing tests in the same folder before writing new ones.” That way the agent adapts to the actual code instead of relying on assumptions baked into the prompt that may go stale.
Choosing project or user scope
Where you put the file decides who gets the agent. The rule of thumb is ownership: if the agent encodes how this repo should be worked on, it belongs to the repo.
Project vs user scope
| Put it in .claude/agents/ when | The whole team should use it; it's tied to this codebase; you want it in version control |
|---|---|
| Put it in ~/.claude/agents/ when | It's your personal helper; you want it across every project; it's not repo-specific |
Project agents committed to git mean a new teammate clones the repo and immediately has the same reviewer and test-writer you do. User agents follow you instead. When both define an agent with the same name, the project one wins — which lets a repo override a personal default with something more opinionated.
A complete example
Here’s the finished test-writer, ready to drop into .claude/agents/test-writer.md:
---
name: test-writer
description: >
Use this agent to write or update unit tests after new code is
added or changed. Covers happy paths and edge cases using the
project's existing test framework and conventions.
tools: Read, Grep, Glob, Write, Edit, Bash
model: sonnet
---
You are a test engineer. You write focused unit tests for code that
was just added or changed.
Your process:
1. Read the changed code and the existing tests for that module.
2. Match the project's test framework, file layout, and naming.
3. Cover the happy path plus edge cases: empty input, boundaries,
error conditions.
Rules:
- Only create or edit files under the project's test directory.
- Never modify production code.
- Run the test suite when done and report pass/fail.
- If you can't tell what the code should do, say so — don't guess.
Sharing the agent with your team
A project subagent is just a file in the repo, so sharing it is sharing the repo. Commit .claude/agents/test-writer.md and every teammate who pulls gets the same agent, with the same tools and prompt. That’s the appeal of project scope — the conventions encoded in the prompt travel with the code instead of living in someone’s head.
git add .claude/agents/test-writer.md
git commit -m "Add test-writer subagent"
Treat these files like any other code. Review changes to an agent’s prompt in pull requests, because a loosened tool scope or a reworded description changes how the agent behaves for everyone. When two people need slightly different behavior, prefer one shared project agent plus a personal user agent over forking the project file — the project-wins precedence rule means a personal copy with the same name will quietly shadow the shared one, which is rarely what a teammate expects.
Troubleshooting delegation
When an agent doesn’t behave, the symptom usually points straight at the cause:
Symptom and likely cause
| Symptom | Likely cause |
|---|---|
| Agent never gets used | Description too vague — no trigger condition |
| Agent fires on unrelated tasks | Description too broad or overlaps another agent |
| Agent edits files it shouldn't | Tools too broad, or tools omitted so it inherited everything |
| Agent ignores project conventions | Conventions missing from the prompt; tell it where to look |
Most of these trace back to the description or the tool scope, which is why those two fields deserve the most attention when you write the file.
Common mistakes to avoid
- Vague description. “Helps with code” gives Claude nothing to match. Use a trigger condition.
- Too-broad tools. Inheriting everything by omitting
toolsis the usual culprit. Scope to the job. - A prompt that does everything. One agent, one job. If the prompt is sprawling, split it into two agents.
- Overlapping agents. Two agents with similar descriptions confuse delegation. Keep each distinct.
- Skipping the test. An agent you never verified is an agent that probably doesn’t fire when you expect.
For ideas on which agents are worth building once you’ve got the method down, see the best subagents to set up. And subagents are one of several extension points — for the full picture, the power-user guide ties them together with Skills, hooks, and MCP.
Wrapping up
A good custom subagent comes down to four moves: a description that states exactly when to use it, a tool list scoped to nothing more than the job needs, a focused system prompt with explicit rules, and a quick test to confirm delegation fires. Build one, run it on real work, and tighten it. That loop is what turns a markdown file into an agent you actually rely on.