Skip to content

Chapter 8: Subagents — Specialized Agents for Complex Work ​

What You'll Build ​

Three production-grade agents adapted from real repos:

  1. Architect agent — Analyzes system design with trade-off analysis (from ECC, uses Opus)
  2. Security scanner agent — Finds SQL injection, XSS, missing auth in real code with severity ratings
  3. Test writer agent — Generates tests with coverage targets and runs them to verify

These are the agent patterns from Everything Claude Code (architect, code-reviewer, tdd-guide) and gstack (role-based CEO/Designer/Engineer agents). Not toy examples — these are the actual patterns used in those 148k and 68k star repos.

Appendix links: A04 Agent Architecture Patterns explains the ReAct and plan-and-execute paradigms that underpin subagent design. A09 Extended Thinking covers when and how to use thinking budgets inside subagents for deeper analysis.

Research note: The multi-agent patterns in this chapter draw from Anthropic's Building effective agents guide, which formalizes the orchestrator-worker architecture Claude Code uses for subagent dispatch.

How Subagents Work ​

A subagent is a Claude instance running in its own context window. When the main session spawns a subagent, the subagent gets its own tools, instructions, and context space. Only the final result comes back.

Main session (your conversation)
  |
  +---> Subagent: architect
  |       Own context (200K)
  |       Tools: Read, Glob, Grep only
  |       Reads 40 files, analyzes architecture
  |       Returns: 500-token summary
  |
  Main session context grew by ~500 tokens, not 40 files worth

Why Subagents Instead of Doing Everything in the Main Session ​

Main SessionSubagent
ContextShared — every file read eats into your limitIsolated — reads don't affect main session
ToolsEverything availableRestricted to what you allow
ModelCurrent modelCan specify a different model
ParallelismSequential onlyMultiple can run simultaneously
OutputEverything visible in conversationOnly the final result returns

The killer feature is context isolation. Without subagents, asking Claude to review 30 files burns through your context window. With a subagent, those 30 files live in the subagent's context, and you only get the review report back.

GSD calls this the "Context Rot" problem — quality degrades past 50% context usage. Subagents are how you avoid it.

Agent Definition Format ​

Agents are Markdown files in .claude/agents/:

markdown
---
name: my-agent
description: What this agent does
tools:
  - Read
  - Glob
  - Grep
model: claude-sonnet-4-6
---

Agent instructions here.

Frontmatter Reference ​

FieldTypeWhat It Does
namestringAgent identifier
descriptionstringShown in agent listings
toolslistWhitelist of available tools
modelstringWhich model to use (claude-sonnet-4-6, claude-opus-4-6, etc.)
maxTurnsnumberMaximum conversation turns before stopping
isolationstringworktree = run in a separate git worktree
memorybooleanPersist knowledge across invocations
skillslistWhich skills this agent can use
mcpServerslistWhich MCP servers this agent can access

Tool Restrictions Matter ​

ECC's code-reviewer agent is restricted to Read, Glob, Grep — read-only tools. This isn't arbitrary. A reviewer that can Write or Bash might "fix" the code instead of reporting the issue, defeating the purpose of a review.

gstack takes this further with role-based restrictions:

  • CEO agent: Read-only. Evaluates business value, doesn't touch code.
  • Designer agent: Read-only. Reviews UX, doesn't change implementation.
  • Engineer agent: Full tools. Implements based on CEO and Designer feedback.

Demo 21: Architect Agent ​

21
Architect Agent with Trade-off Analysis
Intermediate~15 min

Adapted from ECC's architect agent — uses Opus model for system design analysis with trade-off tables.

The Problem ​

You've inherited a codebase, or your project has grown complex enough that you need to understand the big picture. Which modules depend on which? Where are the coupling hotspots? What would break if you refactored the auth system? You could spend a day reading code, or have an architect agent do it in 2 minutes.

Build It ​

1. Set Up a Realistic Codebase ​

bash
mkdir -p ~/claude-demos/demo-21/src/{api,services,models,middleware,utils} && cd ~/claude-demos/demo-21
git init && git branch -M main

# API layer
cat > src/api/routes.js << 'EOF'
import { UserService } from '../services/user.js';
import { OrderService } from '../services/order.js';
import { authMiddleware } from '../middleware/auth.js';
import { rateLimiter } from '../middleware/rateLimit.js';

export function registerRoutes(app) {
  app.post('/api/users', UserService.create);
  app.get('/api/users/:id', authMiddleware, UserService.getById);
  app.post('/api/orders', authMiddleware, rateLimiter, OrderService.create);
  app.get('/api/orders', authMiddleware, OrderService.list);
  app.delete('/api/orders/:id', authMiddleware, OrderService.delete);
}
EOF

# Services
cat > src/services/user.js << 'EOF'
import { UserModel } from '../models/user.js';
import { hashPassword, comparePassword } from '../utils/crypto.js';
import { sendEmail } from '../utils/email.js';
import { cache } from '../utils/cache.js';

export class UserService {
  static async create(req, res) {
    const { email, password, name } = req.body;
    const existing = await UserModel.findByEmail(email);
    if (existing) return res.status(409).json({ error: 'Email exists' });
    const hashed = await hashPassword(password);
    const user = await UserModel.create({ email, password: hashed, name });
    await sendEmail(email, 'Welcome!', `Hi ${name}, welcome aboard.`);
    cache.del('users:list');
    return res.status(201).json(user);
  }
  static async getById(req, res) {
    const cached = cache.get(`user:${req.params.id}`);
    if (cached) return res.json(cached);
    const user = await UserModel.findById(req.params.id);
    if (!user) return res.status(404).json({ error: 'Not found' });
    cache.set(`user:${req.params.id}`, user, 300);
    return res.json(user);
  }
}
EOF

cat > src/services/order.js << 'EOF'
import { OrderModel } from '../models/order.js';
import { UserModel } from '../models/user.js';
import { calculateTotal } from '../utils/pricing.js';
import { sendEmail } from '../utils/email.js';
import { cache } from '../utils/cache.js';
import { publishEvent } from '../utils/events.js';

export class OrderService {
  static async create(req, res) {
    const user = await UserModel.findById(req.userId);
    const total = calculateTotal(req.body.items);
    const order = await OrderModel.create({
      userId: req.userId, items: req.body.items, total, status: 'pending'
    });
    await publishEvent('order.created', { orderId: order.id, userId: req.userId });
    await sendEmail(user.email, 'Order Confirmed', `Order #${order.id}: $${total}`);
    cache.del(`orders:${req.userId}`);
    return res.status(201).json(order);
  }
  static async list(req, res) {
    const cached = cache.get(`orders:${req.userId}`);
    if (cached) return res.json(cached);
    const orders = await OrderModel.findByUserId(req.userId);
    cache.set(`orders:${req.userId}`, orders, 60);
    return res.json(orders);
  }
  static async delete(req, res) {
    const order = await OrderModel.findById(req.params.id);
    if (!order) return res.status(404).json({ error: 'Not found' });
    if (order.userId !== req.userId) return res.status(403).json({ error: 'Forbidden' });
    await OrderModel.delete(req.params.id);
    await publishEvent('order.deleted', { orderId: req.params.id });
    cache.del(`orders:${req.userId}`);
    return res.json({ success: true });
  }
}
EOF

# Models, middleware, utils (abbreviated but realistic)
cat > src/models/user.js << 'EOF'
import { db } from '../utils/db.js';
export class UserModel {
  static findByEmail(email) { return db.query('SELECT * FROM users WHERE email = $1', [email]).then(r => r.rows[0]); }
  static findById(id) { return db.query('SELECT * FROM users WHERE id = $1', [id]).then(r => r.rows[0]); }
  static create(data) { return db.query('INSERT INTO users (email, password, name) VALUES ($1,$2,$3) RETURNING *', [data.email, data.password, data.name]).then(r => r.rows[0]); }
}
EOF

cat > src/models/order.js << 'EOF'
import { db } from '../utils/db.js';
export class OrderModel {
  static findById(id) { return db.query('SELECT * FROM orders WHERE id = $1', [id]).then(r => r.rows[0]); }
  static findByUserId(userId) { return db.query('SELECT * FROM orders WHERE user_id = $1', [userId]).then(r => r.rows); }
  static create(data) { return db.query('INSERT INTO orders (user_id, items, total, status) VALUES ($1,$2,$3,$4) RETURNING *', [data.userId, JSON.stringify(data.items), data.total, data.status]).then(r => r.rows[0]); }
  static delete(id) { return db.query('DELETE FROM orders WHERE id = $1', [id]); }
}
EOF

cat > src/middleware/auth.js << 'EOF'
import { verifyToken } from '../utils/crypto.js';
export function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'No token' });
  try { req.userId = verifyToken(token).userId; next(); }
  catch { return res.status(401).json({ error: 'Invalid token' }); }
}
EOF

cat > src/middleware/rateLimit.js << 'EOF'
const requests = new Map();
export function rateLimiter(req, res, next) {
  const key = req.userId || req.ip;
  const now = Date.now();
  const windowMs = 60000;
  const max = 100;
  const entries = (requests.get(key) || []).filter(t => t > now - windowMs);
  if (entries.length >= max) return res.status(429).json({ error: 'Rate limited' });
  entries.push(now);
  requests.set(key, entries);
  next();
}
EOF

cat > CLAUDE.md << 'EOF'
# E-commerce API
- Node.js + Express
- PostgreSQL database
- In-memory cache (should be Redis in production)
EOF

git add -A && git commit -m "feat: initial e-commerce API"

2. Create the Architect Agent ​

bash
mkdir -p .claude/agents

cat > .claude/agents/architect.md << 'EOF'
---
name: architect
description: Analyzes system architecture with dependency mapping and trade-off analysis
tools:
  - Read
  - Glob
  - Grep
model: claude-opus-4-6
---

You are a senior systems architect. Your job is to analyze the codebase and produce an architectural assessment that a team could use to make refactoring decisions.

## Analysis Process

1. **Map the module structure**: Read every file, identify layers (API, services, models, utils, middleware)
2. **Trace dependencies**: For each module, list what it imports and what imports it
3. **Identify patterns**: What architectural patterns are in use? (MVC, layered, event-driven, etc.)
4. **Find coupling hotspots**: Which modules are imported by the most other modules?
5. **Assess scalability**: What breaks first under 10x load?

## Output Format

Architecture Assessment ​

System Map ​

[ASCII diagram showing module relationships]

Layer Analysis ​

LayerFilesResponsibilitiesDependencies
API.........

Dependency Graph ​

[Which modules depend on which, with counts]

Coupling Hotspots ​

[Modules that are imported most frequently — these are risky to change]

Trade-off Analysis ​

DecisionCurrent ApproachAlternativeTrade-off
CachingIn-memory MapRedisSimple but not distributed

Scalability Concerns ​

[What breaks at 10x scale and why]

Refactoring Recommendations ​

Priority-ordered list with estimated effort (hours, not story points).


## Rules
- Read ALL files before drawing conclusions
- Every claim must reference a specific file
- "I'd recommend" is vague — say exactly what to change and where
- Trade-off tables must include the downside of your recommendation, not just the upside
EOF

3. Use It ​

bash
claude
Use the architect agent to analyze this codebase and produce a full architecture assessment

4. Expected Terminal Output ​

terminal
$ claude
> Use the architect agent to analyze this codebase and produce a full architecture assessment

Claude: I'll launch the architect agent to analyze your codebase.

  Spawning agent: architect (claude-opus-4-6)
  Tools allowed: Read, Glob, Grep
  
  [architect] Glob: src/**/*.js -> 8 files found
  [architect] Read: src/api/routes.js (16 lines)
  [architect] Read: src/services/user.js (18 lines)
  [architect] Read: src/services/order.js (28 lines)
  [architect] Read: src/models/user.js (6 lines)
  [architect] Read: src/models/order.js (6 lines)
  [architect] Read: src/middleware/auth.js (7 lines)
  [architect] Read: src/middleware/rateLimit.js (12 lines)
  [architect] Grep: 'import' in src/ -> 24 matches
  [architect] Analysis complete. Returning report.

# Architecture Assessment

## System Map
  routes.js
    |--- UserService --- UserModel --- db.js
    |       |--- crypto.js, email.js, cache.js
    |--- OrderService --- OrderModel --- db.js
    |       |--- UserModel, pricing.js, email.js, cache.js, events.js
    |--- authMiddleware --- crypto.js
    |--- rateLimiter (standalone)

## Coupling Hotspots
1. utils/cache.js — imported by 2 services (will break both if changed)
2. utils/email.js — imported by 2 services (no fallback on failure)
3. models/user.js — imported by UserService AND OrderService (cross-domain coupling)

## Trade-off Analysis
| Decision        | Current            | Alternative    | Trade-off                           |
|-----------------|--------------------|----------------|-------------------------------------|
| Caching         | In-memory Map      | Redis          | Simple but not distributed          |
| Rate limiting   | In-memory Map      | Redis + sliding | Resets on deploy, no cluster support|
| Event publishing| Direct function call| Message queue  | Tightly coupled, no retry/replay    |

## Refactoring Recommendations
1. Extract cache to Redis adapter (~2h) — highest impact
2. Add error handling to email sends (~1h) — silent failures are dangerous
3. Decouple OrderService from UserModel (~3h) — pass user data in, dont re-fetch

Token usage: ~4,200 tokens (agent context), ~620 tokens returned to main session.

What Just Happened? ​

1
TaskAgent
architect
↓
2
Glob
src/**/*.js
↓
3
Read (x7)
All source files
↓
4
Grep
import statements
↓
5
Return
Main session

The architect agent consumed ~4,200 tokens in its own context reading 8 files and analyzing dependencies. Your main session only received the ~620 token report. Without a subagent, those 8 file reads would have consumed ~3,500 tokens of your main context directly — and you would still need to do the analysis yourself.


Demo 22: Security Scanner Agent ​

22
Security Scanner with Confidence Filtering
Intermediate~15 min

The Problem ​

You need a security review before merging a PR, but your team doesn't have a dedicated security engineer. The standard "ask Claude to review for security" approach produces vague output. A purpose-built security agent with strict output format and confidence filtering catches real issues and ignores noise.

Build It ​

bash
cd ~/claude-demos/demo-21

cat > .claude/agents/security-scanner.md << 'EOF'
---
name: security-scanner
description: Finds security vulnerabilities with severity ratings and confidence scores
tools:
  - Read
  - Glob
  - Grep
model: claude-sonnet-4-6
---

You are a security engineer performing a vulnerability assessment. You are thorough, precise, and you don't cry wolf.

## Vulnerability Categories

Scan for these specific vulnerability classes:

### Injection (CRITICAL)
- SQL injection: string interpolation in queries
- Command injection: user input in shell commands
- Template injection: user input in template engines
- NoSQL injection: user input in MongoDB queries

### Authentication & Authorization (CRITICAL)
- Missing auth checks on sensitive endpoints
- Weak token generation (predictable, short, no expiry)
- Password stored in plaintext
- Missing rate limiting on auth endpoints

### Data Exposure (HIGH)
- Sensitive fields in API responses (password hashes, tokens, internal IDs)
- Verbose error messages leaking stack traces
- Missing input validation allowing oversized payloads

### Configuration (MEDIUM)
- Hardcoded credentials or API keys
- Debug mode enabled
- CORS misconfiguration
- Missing security headers

## Output Rules

1. **Confidence threshold**: Only report findings with >= 80% confidence
2. **Specific locations**: Always include file:line, never "in the auth module"
3. **Severity levels**: P0 (exploit now), P1 (exploit with effort), P2 (defense-in-depth), P3 (hardening)
4. **Proof of concept**: For P0/P1, include a curl command or input that demonstrates the vulnerability

## Output Format

Security Assessment ​

Executive Summary ​

  • Critical (P0): N findings
  • High (P1): N findings
  • Medium (P2): N findings
  • Scanned: N files

Critical Findings (P0) ​

[SQLI-001] SQL Injection in user lookup ​

  • File: src/services/user.js:15
  • Confidence: 95%
  • Vector: email parameter from request body interpolated into SQL query
  • Impact: Full database read/write access
  • PoC: curl -X POST /api/users -d '{"email":"' OR 1=1 --"}'
  • Fix: Use parameterized queries
    javascript
    // Before
    db.query(`SELECT * FROM users WHERE email = '${email}'`)
    // After  
    db.query('SELECT * FROM users WHERE email = $1', [email])

...

Passed Checks ​

[List security measures that ARE in place — this matters for context]

EOF

Test It ​

bash
claude
Use the security-scanner agent to scan all files in src/ for vulnerabilities

Expected Terminal Output ​

terminal
$ claude
> Use the security-scanner agent to scan all files in src/ for vulnerabilities

Claude: Launching the security-scanner agent to assess your codebase.

  Spawning agent: security-scanner (claude-sonnet-4-6)
  Tools allowed: Read, Glob, Grep
  
  [security-scanner] Glob: src/**/*.js -> 8 files found
  [security-scanner] Read: src/api/routes.js
  [security-scanner] Read: src/services/user.js
  [security-scanner] Read: src/services/order.js
  [security-scanner] Read: src/middleware/auth.js
  [security-scanner] Read: src/middleware/rateLimit.js
  [security-scanner] Grep: 'password' in src/ -> 4 matches
  [security-scanner] Grep: 'req.body' in src/ -> 3 matches
  [security-scanner] Grep: 'req.params' in src/ -> 4 matches
  [security-scanner] Assessment complete. Returning report.

# Security Assessment

## Executive Summary
- Critical (P0): 0 findings
- High (P1): 2 findings
- Medium (P2): 3 findings
- Scanned: 8 files

## High Findings (P1)

### [AUTH-001] Missing input validation on user creation
- **File**: src/services/user.js:4
- **Confidence**: 92%
- **Vector**: No validation on email format, password length, or name content
- **Impact**: Can create users with empty passwords or invalid emails
- **PoC**: curl -X POST /api/users -d '{"email":"not-an-email","password":"","name":""}'
- **Fix**: Add input validation before processing

### [AUTH-002] Rate limiter not applied to user creation
- **File**: src/api/routes.js:8
- **Confidence**: 88%
- **Vector**: POST /api/users has no rate limiting, only POST /api/orders does
- **Impact**: Brute-force account creation, credential stuffing
- **Fix**: Add rateLimiter middleware to user creation route

## Medium Findings (P2)

### [CACHE-001] In-memory rate limiter resets on restart
- **File**: src/middleware/rateLimit.js:1
- **Confidence**: 95%
- **Vector**: Rate limit state stored in-process Map, lost on deploy/restart
- **Impact**: Rate limiting ineffective across restarts or multiple instances

### [DATA-001] Password hash may leak in user response
- **File**: src/services/user.js:8
- **Confidence**: 82%
- **Vector**: UserModel.create returns all columns including password hash
- **Impact**: Password hash exposed in 201 response body

### [ERR-001] Silent notification failure
- **File**: src/services/order.js:10
- **Confidence**: 90%
- **Vector**: sendEmail failure caught and discarded with .catch(() => {})
- **Impact**: Users never notified of order confirmation, no logging

## Passed Checks
- SQL queries use parameterized queries ($1, $2 placeholders) -- no injection risk
- Auth middleware validates JWT tokens before allowing access
- Order deletion checks ownership (userId match) -- proper authorization
- Sensitive routes protected with authMiddleware

What Just Happened? ​

1
TaskAgent
security-scanner
↓
2
Glob + Read
All source files
↓
3
Grep (x3)
Security-sensitive patterns
↓
4
Return
Main session

Notice what the agent did NOT report: it found parameterized SQL queries and correctly classified them as safe. The 80% confidence threshold filtered out speculative findings. This is the difference between a useful security review and a noisy one.

ECC's Confidence Filtering ​

This is directly from ECC's code-reviewer agent pattern: findings below 80% confidence are dropped. This single rule eliminates most false positives that make AI code reviews annoying. Without it, you get 20 "findings" where 15 are noise. With it, you get 5 findings that are all real.


Demo 23: Test Writer Agent ​

23
Test Writer Agent with Coverage Targets
Intermediate~20 min

The Problem ​

You have code with no tests. Writing tests from scratch is tedious. A test writer agent reads the code, identifies the public API, generates test cases covering happy paths and error paths, and runs them to verify they pass.

Build It ​

bash
cd ~/claude-demos/demo-21

cat > .claude/agents/test-writer.md << 'EOF'
---
name: test-writer
description: Generates comprehensive tests with coverage targets
tools:
  - Read
  - Write
  - Glob
  - Grep
  - Bash
model: claude-sonnet-4-6
---

You are a test engineer. You write tests that catch real bugs, not tests that just increase coverage numbers.

## Process

1. **Read the target code** — understand the public API, inputs, outputs, side effects
2. **Identify test scenarios**:
   - Happy path (normal usage)
   - Edge cases (empty input, boundary values, null/undefined)
   - Error paths (invalid input, missing dependencies, network failures)
   - Security cases (injection attempts, auth bypass, oversized input)
3. **Write the tests** using the project's test framework (detect from package.json)
4. **Run the tests** to verify they pass
5. **Report coverage**

## Test Quality Rules

- Each `it()` block tests exactly ONE behavior
- Test names describe the behavior, not the implementation: "returns 404 when user not found" not "tests getById"
- Mock external dependencies (database, email, cache) — don't test third-party code
- Include at least one test per public function/method
- Error path tests are not optional — every `catch` and error return must be tested

## Output Format

Test Report ​

Files Created ​

  • tests/services/user.test.js (12 tests)
  • tests/services/order.test.js (15 tests)

Coverage Summary ​

ModuleFunctionsBranchesLines
UserService100%85%92%
OrderService100%80%88%

Test Results ​

  • Total: 27 tests
  • Passed: 27
  • Failed: 0

Scenarios Covered ​

  • User creation: valid input, duplicate email, missing fields
  • Order creation: valid order, invalid items, unauthorized
  • Order deletion: own order, other user's order, non-existent ...

## Framework Detection

Check `package.json` for test framework:
- `vitest` -> use Vitest with `describe/it/expect`
- `jest` -> use Jest with `describe/it/expect`
- Neither -> install Vitest and configure it

If the project has no `package.json`, create one.
EOF

Test It ​

bash
# First install a test framework
cd ~/claude-demos/demo-21
npm init -y
npm install --save-dev vitest

claude
Use the test-writer agent to generate tests for src/services/user.js and 
src/services/order.js, then run them

Expected Terminal Output ​

terminal
$ claude
> Use the test-writer agent to generate tests for src/services/user.js and
  src/services/order.js, then run them

Claude: Launching the test-writer agent. This agent has Write and Bash access
so it can create test files and run them.

  Spawning agent: test-writer (claude-sonnet-4-6)
  Tools allowed: Read, Write, Glob, Grep, Bash
  
  [test-writer] Read: package.json -> vitest detected
  [test-writer] Read: src/services/user.js (18 lines)
  [test-writer] Read: src/services/order.js (28 lines)
  [test-writer] Read: src/models/user.js (dependencies)
  [test-writer] Read: src/models/order.js (dependencies)
  [test-writer] Write: tests/services/user.test.js (12 test cases)
  [test-writer] Write: tests/services/order.test.js (15 test cases)
  [test-writer] Bash: npx vitest run --reporter=verbose

  stdout | tests/services/user.test.js
    UserService
      create
        + returns 201 with valid user data (3ms)
        + returns 409 when email already exists (1ms)
        + hashes password before storing (2ms)
        + sends welcome email after creation (1ms)
        + invalidates user list cache (1ms)
      getById
        + returns cached user when available (1ms)
        + fetches from database on cache miss (2ms)
        + returns 404 when user not found (1ms)
        + sets cache with 300s TTL on fetch (1ms)

  stdout | tests/services/order.test.js
    OrderService
      create
        + creates order with correct total (2ms)
        + publishes order.created event (1ms)
        + sends confirmation email to user (1ms)
        + invalidates order cache for user (1ms)
      list
        + returns cached orders when available (1ms)
        + fetches from database on cache miss (2ms)
        + sets cache with 60s TTL (1ms)
      delete
        + returns 404 when order not found (1ms)
        + returns 403 when user does not own order (1ms)
        + deletes order and publishes event (2ms)
        + invalidates order cache after deletion (1ms)

  Test Files  2 passed (2)
       Tests  20 passed (20)
    Duration  0.34s

  [test-writer] Complete. Returning report.

# Test Report
## Files Created
- tests/services/user.test.js (9 tests)
- tests/services/order.test.js (11 tests)

## Test Results
- Total: 20 tests
- Passed: 20
- Failed: 0

What Just Happened? ​

1
TaskAgent
test-writer
↓
2
Read (x5)
Source files + package.json
↓
3
Write (x2)
Test files
↓
4
Bash
npx vitest run

Key difference from the read-only agents: the test-writer has Write and Bash access because its job requires creating files and executing commands. Tool restrictions should match the agent's role — not be uniformly restrictive.


When Things Go Wrong ​

Subagent Exceeding Tool Allowlist ​

terminal
$ claude
> Use the security-scanner agent to fix the SQL injection in user.js

Claude: Launching security-scanner agent...

  [security-scanner] Attempted to use tool: Write
  Error: Tool 'Write' is not in the allowed list for agent 'security-scanner'.
  Allowed tools: Read, Glob, Grep
  
  [security-scanner] I found the SQL injection issue but I cannot fix it directly.
  My tools are restricted to read-only access. Here is the finding and 
  recommended fix for you to apply manually...

What happened: You asked a read-only agent to make changes. The agent tried to use Write but the tool allowlist blocked it. This is working as designed — a security scanner should report issues, not fix them.

Fix: Either apply the fix yourself, or use an agent with write access (like the test-writer) for tasks that require code changes. If you want a "security fixer" agent, create a separate one with Write in its tools list.

Model Mismatch ​

terminal
$ claude
> Use the architect agent to quickly check if routes.js looks correct

Claude: Launching architect agent...

  Spawning agent: architect (claude-opus-4-6)
  [architect] Reading src/api/routes.js...
  [architect] Reading src/services/user.js...
  [architect] Reading src/services/order.js...
  [architect] Reading all remaining 5 files...
  [architect] Analyzing full dependency graph...
  
  (30 seconds later)
  
  # Full Architecture Assessment
  ## System Map...
  ## Layer Analysis...
  ## Dependency Graph...
  [... 800 token detailed report for a simple question ...]

What happened: You used the Opus-powered architect agent for a quick question. Opus is slower and produces deeper analysis than needed. You waited 30 seconds for a report when you just wanted a quick sanity check.

Fix: Use the right model for the task. For quick checks, ask the main session directly (which uses your current model, often Sonnet). Reserve the architect agent for full architectural reviews. You can also create a lightweight quick-review agent on Sonnet for fast answers:

yaml
---
name: quick-review
model: claude-sonnet-4-6
tools: [Read, Glob, Grep]
---
Give brief, focused answers. No full reports unless asked.

Subagent Timeout ​

terminal
$ claude
> Use the architect agent to analyze this monorepo

Claude: Launching architect agent...

  Spawning agent: architect (claude-opus-4-6)
  [architect] Glob: src/**/*.js -> 847 files found
  [architect] Read: src/api/routes/index.js
  [architect] Read: src/api/routes/auth.js
  [architect] Read: src/api/routes/users.js
  ... (reading files one by one) ...
  
  Warning: Agent 'architect' reached maximum turns (25). Returning partial results.
  
  # Architecture Assessment (PARTIAL)
  ## System Map
  Only analyzed 120 of 847 files before reaching turn limit.

What happened: The agent tried to read all 847 files in a large monorepo, one at a time, and hit the maxTurns limit before finishing.

Fix: Add maxTurns to the agent definition if needed, but more importantly, scope the agent's task. Instead of "analyze this monorepo," say "analyze the src/api/ and src/services/ directories." You can also update the agent instructions to prioritize breadth over depth:

## For large codebases (>100 files)
1. Glob to map the full structure
2. Read only entry points and high-fan-in modules
3. Use Grep to trace dependencies without reading every file
4. Sample 2-3 files per directory instead of reading all

Agent Design Principles ​

From ECC: Model Selection ​

ECC uses different models for different agents:

  • Architect: Opus (deep analysis, trade-off reasoning)
  • Code reviewer: Sonnet (fast, good enough for pattern matching)
  • TDD guide: Sonnet (iterative, needs speed over depth)

Match the model to the task. Don't use Opus for everything — it's slower and more expensive. Don't use Haiku for architecture analysis — it'll miss nuance.

From gstack: Role-Based Agents ​

gstack's role system assigns different perspectives, not just different tools:

  • CEO review: "Is this worth building? What's the business impact?"
  • Design review: "Does the UX flow make sense? Is it accessible?"
  • Engineering review: "Is the architecture sound? What are the risks?"

You can apply this to your own agents. The security scanner above is an "Engineering review" agent. You could add a "Product review" agent that evaluates features from the user's perspective.

Worktree Isolation ​

For agents that need to make experimental changes:

yaml
---
name: refactor-experiment
isolation: worktree
tools:
  - Read
  - Write
  - Edit
  - Bash
---

The agent works in a separate git worktree. Its changes don't affect your working directory. If the experiment works, you merge it in. If it doesn't, you delete the worktree.


Exercise: Build a 3-Agent Review Pipeline ​

Create three agents that review code from different perspectives:

  1. product-reviewer: Read-only. Evaluates from the user's perspective. "Does this feature make sense? Is it intuitive?"
  2. security-reviewer: Read-only. Uses the security-scanner pattern from Demo 22.
  3. performance-reviewer: Read-only. Looks for N+1 queries, unnecessary allocations, missing caching.

Run all three on the same codebase. Compare their findings — they should be complementary, not overlapping.

Success Criteria ​

  • [ ] All three agent files exist in .claude/agents/
  • [ ] Each has appropriate tool restrictions (all read-only)
  • [ ] Each produces a structured report with specific file:line references
  • [ ] Findings don't overlap significantly between agents
  • [ ] You can run them in a single Claude session and get a combined view

Knowledge Check ​

Why does ECC restrict its code-reviewer agent to Read, Glob, and Grep only?
To save API costs by limiting tool usage
To prevent the reviewer from modifying code instead of reporting issues
Because Sonnet cannot use Write or Bash tools
To make the agent run faster with fewer tools available
Your architect agent (Opus) reads 40 files and produces a 500-token report. How much did your main session context grow?
By ~40 files worth of tokens (everything the agent read)
By ~500 tokens (only the returned report)
By zero tokens (subagents are completely invisible)
By half the agent context (shared memory)
The security scanner found a potential issue but only has 70% confidence. What happens?
It reports the issue with a warning that confidence is low
It drops the finding entirely because it is below the 80% threshold
It escalates the finding to the main session for human review
It changes the severity from P0 to P3
You need a quick check on one file but your only agent uses Opus. What should you do?
Use the Opus agent anyway -- better quality is always worth waiting for
Ask the main session directly instead of spawning an agent
Create a new agent file every time you need a different model
Edit the existing agent to use Sonnet, then change it back later

Summary ​

Subagents are how you scale Claude Code from a single conversation to a team of specialists. The three patterns in this chapter — architect, security scanner, test writer — cover the most common needs.

Key points:

  • Context isolation is the main reason to use subagents — avoid GSD's "Context Rot" past 50% usage
  • Tool restrictions enforce the agent's role — a reviewer that can Write isn't a reviewer
  • Model selection matters — Opus for depth, Sonnet for speed
  • ECC's 80% confidence threshold eliminates false positives in reviews
  • Worktree isolation lets agents experiment without risk

Next: Chapter 9: Agent Teams — Orchestrate multiple agents working in parallel on complex tasks.

Released under MIT License