Skip to main content
Claude Code Tutorial: From First Command to Custom Workflows

Building Custom Subagents: File Format, Discovery, and Lifecycle

Master the exact syntax for defining custom subagents, how Claude discovers and matches them, and how to build a production subagent from scratch with proper error handling and output structure.

Intermediate16 minBy ToolDix Editorial

Learning objectives

  • Write a subagent definition file with correct YAML frontmatter and markdown structure
  • Control which tools a subagent can use and understand the tradeoffs
  • Design subagent descriptions that trigger automatic matching
  • Build an end-to-end subagent for a real workflow (test runner, code analyzer, etc.)
  • Understand subagent lifecycle: spawn, execution, reporting, and context inheritance

ToolDix original visual

Claude Code Tutorial practice loop
1

Frame

Name the outcome and constraints.

2

Build

Try one bounded workflow.

3

Review

Keep evidence, revise, and share.

The subagent definition file: exact format and semantics

Every custom subagent is a markdown file in a .claude/agents/ directory. The file must have:

  1. YAML frontmatter (between --- markers) with metadata
  2. Markdown body with the system prompt

Here is the minimal valid subagent:

---
name: my-subagent
description: "What this subagent does and when to use it"
---

You are a specialist in [domain]. Your task is to [action].

Provide clear, actionable output.

The filename must match the name field (e.g., my-subagent.md).

Frontmatter schema (YAML)

The complete schema, with all supported fields, is:

| Field | Type | Required | Default | Notes | | :--- | :--- | :--- | :--- | :--- | | name | string | Yes | — | Identifier, kebab-case. Must equal filename minus .md. Used in prompts like "Use the [name] subagent to..." | | description | string | Yes | — | Natural language explanation of when Claude should invoke this. The description is matched against the main prompt to decide whether to delegate automatically. | | tools | comma-separated string or array | No | All available | Tools the subagent can use (e.g., Read, Grep, Glob, Bash). Omit to inherit all. | | model | string | No | Main model | Override model for this subagent (haiku, sonnet, opus, or a full model ID like claude-opus-4-20250514). | | background | boolean | No | false | If true, run as a non-blocking background task. Main agent continues without waiting. | | max_turns | number | No | No limit | Maximum number of agentic turns (plan-act-observe cycles) before stopping. |

Fields not listed above are ignored and should not be used.

Markdown body: the system prompt

Everything after the second --- is the system prompt. This becomes the subagent's instructions. Use plain Markdown:

  • Headings to structure the prompt
  • Lists to enumerate responsibilities
  • Code blocks for examples
  • Tables for reference material

The body is plain text; it is not evaluated as code. Keep it readable and specific.

Designing descriptions for automatic matching

The description field is the hinge between your prompt and subagent invocation. Claude reads the description and decides: "Does this task match this subagent?"

A good description:

  1. Names the domain — "code reviewer," "security auditor," "test runner"
  2. States the purpose — "Identifies bugs and style issues"
  3. Includes keywords that match typical prompts — "test," "review," "security," "performance"
  4. Is specific — Not "does stuff" but "runs unit tests and reports coverage"

Examples:

Good descriptions:

  • "Test runner specialist. Executes unit tests, reports failures, and suggests fixes."
  • "Security code reviewer. Identifies injection vulnerabilities, auth flaws, and data handling risks."
  • "Performance profiler. Analyzes code for hotspots and suggests optimizations."

Poor descriptions:

  • "A code subagent" (too generic)
  • "Helps with code" (vague)
  • "Reviews code" (matches many things; not specific enough)

When your prompt is "Run the test suite and fix failures," Claude will match it to a test-runner subagent with the good description above but might miss one with a poor description.

Tool restrictions and common combinations

When you specify tools, you explicitly allow only those tools. Tools not listed are unavailable — Claude will not ask for them, they will not appear in the tool menu, and the subagent cannot use them.

Safe tool combinations by use case

| Use case | Tools | Why | | :--- | :--- | :--- | | Read-only code analysis | Read, Glob, Grep | Analyze without risk of unintended changes | | Test execution and reporting | Bash, Read, Grep | Run tests, read output, analyze failures | | Code review with suggestions | Read, Glob, Grep, WebSearch | Review code and cite external standards | | Code refactoring | Read, Edit, Write, Bash, Glob, Grep | Full edit access; can run tests to verify | | Security audit | Read, Glob, Grep | Read-only to prevent accidental changes |

Omitting tools (full access)

If you do not specify tools, the subagent inherits every tool available to subagents:

---
name: general-fixer
description: "Fixes any issue. Has full tool access."
---

Fix whatever is broken. Use whatever tools you need.

This is rarely the right choice. Explicit tool lists are safer and focus the subagent.

Subagent lifecycle: what context they inherit and how they report

When you invoke a subagent, here is exactly what happens:

1. Spawn

Claude creates a new, isolated agent session. The subagent receives:

  • Its system prompt (the markdown body)
  • The specific task from the Agent tool call (what the main agent asked it to do)
  • Your project's CLAUDE.md (loaded automatically; see best practices for details)
  • Tools listed in the tools field (or all available tools if omitted)

The subagent does NOT receive:

  • The main agent's conversation history
  • Files the main agent has read
  • Earlier decisions or context

This is intentional: a fresh context keeps the subagent focused.

2. Execution

The subagent operates autonomously, making its own plan, reading files, running tools, and reasoning about results. If it fails, it retries. If a tool is not in its tools list, it will not use it.

3. Reporting

When the subagent finishes (either successfully or after max turns/errors), it returns a final message to the main agent. This message includes:

  • The subagent's reasoning and findings
  • Any structured output (lists, tables, code snippets)
  • An agentId marker in the response (used if you want to resume the subagent later)

The main agent receives only the final message, not the intermediate files or tool outputs the subagent read internally.

Worked example: Building a comprehensive test-runner subagent

This example walks through building a production-ready subagent that runs tests, analyzes failures, and suggests fixes.

File: .claude/agents/test-runner.md

---
name: test-runner
description: "Runs test suites, analyzes failures, and suggests fixes. Focuses on test execution and debugging."
tools: Bash, Read, Grep, Glob
model: sonnet
---

# Test Runner Subagent

You are a test execution and debugging specialist. Your role is to run tests, capture output, analyze failures, and suggest specific fixes.

## Your process

1. **Discover tests**: Use Glob to find test files matching common patterns (*.test.js, *.spec.ts, test_*.py, etc.)
2. **Run the full suite**: Execute the configured test command (npm test, pytest, cargo test, etc.)
3. **Capture output**: Record the full output and exit code
4. **Analyze failures**: For each failure:
   - Read the test file and identify what it was testing
   - Read the implementation file being tested
   - Understand why the test failed (assertion, exception, timeout)
5. **Suggest fixes**: Propose specific, testable fixes to the implementation
6. **Verify**: Run the tests again to confirm fixes work

## Important constraints

- Do not edit test files. Only fix the implementation code.
- Do not modify the test runner configuration unless explicitly asked.
- If tests are slow, note it but do not optimize test infrastructure.
- If a test environment is missing (database, service), report it clearly rather than working around it.

## Output format

Report findings as:

1. **Test run summary**: Total tests, passed, failed, skipped
2. **Each failure** (as a numbered list):
   - Test name
   - Error message
   - Root cause (what in the implementation is wrong)
   - Suggested fix (code change or architectural improvement)
3. **Overall assessment**: Can failures be fixed? Are they test issues or implementation issues?

How it works:

When you prompt:

Run the test suite and fix any failures

Claude may invoke test-runner automatically because "test" is in your prompt and the description matches.

Or explicitly:

Use the test-runner subagent to run the test suite and report issues

What the subagent does:

  1. Runs npm test (or similar) to discover and execute the test suite
  2. Reads test files and implementation files to understand what failed
  3. Analyzes the failure root cause
  4. Reports findings with specific line numbers and suggestions

Why this works:

  • The subagent has only Bash, Read, Grep, Glob—it cannot accidentally edit code
  • It uses sonnet (cheaper than opus) because test analysis is straightforward
  • The main agent receives a concise summary, not megabytes of test output
  • Your main agent can then decide: fix in main, delegate to another subagent, or ask the test-runner for more details

Subagent vs. foreground vs. background

By default, subagents run in foreground: the main agent waits for the result before continuing. This blocks the main conversation.

Set background: true to run the subagent in the background:

---
name: lint-checker
description: "Runs linters and reports violations"
tools: Bash, Read, Grep
background: true
---

When to use background:

  • The main agent does not need the result immediately
  • You want multiple tasks happening in parallel
  • Long-running operations (full codebase scans, integration tests)

When to use foreground (default):

  • The main agent needs the result to make the next decision
  • The task is quick (a few seconds)
  • You want feedback immediately

Lifecycle traceability: resuming a subagent

When a subagent finishes, the Agent tool result includes:

... findings ...
agentId: abcd-1234-efgh-5678

You can capture this agentId and resume the subagent later in the same session:

Resume agent abcd-1234-efgh-5678 and analyze the following files...

The resumed subagent has access to its full prior conversation and tool results, so you can follow up without starting from scratch.

This requires:

  1. Same session (/resume or don't close the session)
  2. Same subagent definition (if custom) or a newly created one with the same name
  3. The captured agentId

Error handling and robustness

Subagents are autonomous but can fail:

  • Tool not available: If the subagent tries to use a tool not in its tools list, it will not have access and must work around it
  • API errors: Rate limits or server errors may cut a subagent short; the main agent receives partial output
  • Timeout: Very long-running subagents may hit Claude Code's turn limits; use max_turns to set explicit bounds

Robust subagent definitions handle these gracefully:

---
name: resilient-reviewer
description: "Reviews code even if some tools are unavailable"
tools: Read, Grep, Glob
---

Review code for issues. If you cannot access a tool, work with what you have.

If you hit a rate limit or timeout:
- Report what you found so far
- State clearly what you could not check
- Suggest next steps for the main agent

Common mistakes

1. Overly broad tools list

tools: Read, Edit, Write, Bash, Grep, Glob, WebSearch

This gives the subagent so much access that it might accidentally do things you did not intend. Be specific:

tools: Read, Grep, Glob

2. Vague or generic description

description: "A helpful subagent"

Claude will not reliably invoke this because "helpful" matches everything. Be specific:

description: "Tests code and reports test failures with suggested fixes"

3. Mixing concerns in one subagent

One subagent should have one clear job. Instead of:

name: do-everything
description: "Reviews, tests, deploys, and documents code"

Create separate subagents:

name: code-reviewer
name: test-runner
name: deployer
name: doc-generator

Then the main agent can invoke each when needed.

Common mistake

Assuming subagents see everything the main agent sees. They do not. A subagent is a fresh session with only the task prompt from the Agent tool. If you need the subagent to know about earlier context (a file path, an error message, earlier findings), include it explicitly in the Agent tool call: "Review the authentication module at src/auth/oauth.ts. Earlier we found a potential session handling issue; check if that is related to this failure."

Sources and license context

These references informed the lesson. ToolDix adds its own explanation, workflow, and practice rather than reproducing source material. Every link below leaves ToolDix and opens the publisher's own site in a new tab.

Keep going

Read these next on ToolDix.

Original lessons that build on what you just read.