Back to materials

Subagents and hooks

Table of Contents

1. Overview

Subagents and hooks extend Cursor's agent: subagents run tasks in parallel, hooks run scripts before or after agent actions.

FeaturePurpose
SubagentsIndependent agents that handle discrete parts of a task in parallel with isolated context
HooksScripts that run before or after agent actions to observe, block, or modify behavior

2. Subagents

Key concepts

TermMeaning
Parallel executionMultiple subagents run simultaneously to speed up complex tasks
Context isolationEach subagent has its own context window, keeping the main chat clean
Specialized expertiseCreate reusable, expert subagents for specific domains (e.g., security, testing)

Custom subagents

Create custom subagents in .cursor/agents/ (for project-specific) or ~/.cursor/agents/ (for global) directories. Each subagent is a markdown file with YAML frontmatter.

Configuration:

FieldRequiredDescription
nameNoUnique identifier. Use lowercase letters and hyphens. Defaults to filename.
descriptionNoWhen to use this subagent. Agent reads this to decide delegation.
modelNoModel to use: inherit or a specific model ID. Defaults to inherit.
readonlyNoIf true, the subagent runs with restricted write permissions.
is_backgroundNoIf true, the subagent runs in the background without waiting for completion.

Exercise 1: Build a verifier subagent

Validates completed work before accepting it as done.

  1. Create the file: .cursor/agents/verifier.md

  2. Add the content:

    ---
    name: verifier
    description: Validates completed work. Use after the agent marks a task done.
    model: inherit
    ---
    
    You are a skeptical validator. When invoked:
    
    1. Identify what was claimed as completed
    2. Verify the implementation exists and works
    3. Run tests and check edge cases
    4. Report what passed, what failed, and what needs fixing
    
    Do not accept claims at face value; verify before confirming.
    
  3. Usage: After the main agent completes a task, invoke your new subagent:

    /verifier confirm the implementation is complete and all tests pass.
    

Docs: Subagents

3. Hooks

Example use cases

Use caseDescription
AutomationRun formatters, linters, or other checks automatically after edits
SecurityScan for secrets or PII, and gate risky operations like database writes
ObservabilityLog agent actions to get insights into your development process

Hook events

Common events:

EventWhen it fires
sessionStart / sessionEndAt the start and end of a session
preToolUse / postToolUseBefore and after any tool is used
beforeShellExecutionBefore a shell command is executed
afterFileEditAfter the agent edits a file
stopWhen the agent's work is complete

Configuration

Create a hooks.json file in ~/.cursor/ (global) or <project>/.cursor/ (project-specific). Each hook specifies an event and a command to run.

Exercise 2: Build an audit hook

Logs all shell commands and file edits.

  1. Create hooks.json: In your project's .cursor/ directory, create hooks.json:

    {
      "version": 1,
      "hooks": {
        "beforeShellExecution": [{ "command": ".cursor/hooks/audit.sh" }],
        "afterFileEdit": [{ "command": ".cursor/hooks/audit.sh" }]
      }
    }
    
  2. Create the script: In .cursor/hooks/, create audit.sh:

    #!/bin/bash
    INPUT=$(cat)
    echo "$(date): $INPUT" >> .cursor/audit.log
    echo '{}'
    exit 0
    
  3. Make it executable: chmod +x .cursor/hooks/audit.sh

  4. Usage: As the agent works, check the .cursor/audit.log file to see the logged events.

Exercise 3: Build an autonomous task loop

Uses subagents to keep context fresh and a hook to restart the agent if tasks remain.

  1. Create TASKS.md at your project root with checkbox format:

    # Tasks
    
    Goal: Add test coverage
    
    - [ ] Add tests for the main utility functions
    - [ ] Add tests for API endpoint responses
    - [ ] Add tests for error handling cases
    - [ ] Add tests for edge cases (empty input, invalid data)
    - [ ] Ensure all tests pass
    
  2. Create the implementer subagent: In .cursor/agents/, create implementer.md:

    ---
    name: implementer
    description: Implements tasks from TASKS.md. Delegate coding work to this agent for fresh context.
    model: inherit
    ---
    
    You are an implementation specialist. When delegated:
    
    1. Read TASKS.md to see current progress
    2. Implement the next unchecked task
    3. Mark it complete with [x] in TASKS.md
    4. Run tests and report status
    
  3. Set up hooks.json:

    {
      "version": 1,
      "hooks": {
        "stop": [
          { "command": "bun run .cursor/hooks/check-tasks.ts", "loop_limit": 10 }
        ]
      }
    }
    

    The hook script enforces the iteration cap via MAX_ITERATIONS, reading loop_count from stdin.

  4. Create the hook script: In .cursor/hooks/, create check-tasks.ts:

    import { readFileSync, existsSync as fileExists } from "fs";
    
    // When a Cursor agent's turn ends, the IDE runs any registered "stop" hooks.
    // It pipes a JSON payload to the hook's stdin describing what happened,
    // and reads the hook's stdout for instructions on what to do next.
    // Responding with {} means "stop here." Responding with { followup_message }
    // means "start another agent turn with this message."
    
    interface StopHookInput {
      conversation_id: string;
      status: "completed" | "aborted" | "error";
      loop_count: number; // how many times this hook has already triggered a follow-up (starts at 0)
    }
    
    // Configuration
    const MAX_ITERATIONS = 10;
    const tasksPath = "TASKS.md";
    
    // Read the payload Cursor piped to stdin
    const input: StopHookInput = await Bun.stdin.json();
    
    // An empty JSON response tells Cursor "do not continue"
    const noFollowup = JSON.stringify({});
    
    const stopLoop = () => {
      console.log(noFollowup);
      process.exit(0);
    };
    
    // Parse TASKS.md and return a followup message if work remains, or null if done
    const getFollowup = () => {
      const content = readFileSync(tasksPath, "utf-8");
      const completedTasks = (content.match(/- \[x\]/gi) || []).length;
      const remainingTasks = (content.match(/- \[ \]/g) || []).length;
      const totalTasks = completedTasks + remainingTasks;
    
      if (remainingTasks > 0) {
        return `[${completedTasks}/${totalTasks} done] ${remainingTasks} tasks remain. Delegate to /implementer to continue.`;
      }
      return null;
    };
    
    // If the agent completed successfully, we haven't looped too many times,
    // and the tasks file exists, check if there's more work to do.
    if (
      input.status === "completed" &&
      input.loop_count < MAX_ITERATIONS &&
      fileExists(tasksPath)
    ) {
      const followup = getFollowup();
      if (followup) {
        console.log(JSON.stringify({ followup_message: followup }));
      } else {
        stopLoop();
      }
    } else {
      stopLoop();
    }
    
  5. Usage: Prompt the agent:

    Complete all tasks in TASKS.md. Use /implementer for each batch of work to keep context fresh.
    

Exercise 4: Block commands with matcher

Uses a matcher to block commands matching a pattern.

  1. Set up hooks.json:

    {
      "version": 1,
      "hooks": {
        "beforeShellExecution": [
          {
            "command": ".cursor/hooks/block-command.sh",
            "matcher": "BLOCK_TEST"
          }
        ]
      }
    }
    
  2. Create the script: In .cursor/hooks/, create block-command.sh:

    #!/bin/bash
    echo '{"permission": "deny", "user_message": "This command has been blocked for safety."}'
    exit 0
    
  3. Make it executable: chmod +x .cursor/hooks/block-command.sh

  4. Test it: Run echo BLOCK_TEST and verify it's blocked. The BLOCK_TEST pattern is included for safe testing.

Docs: Hooks

4. Scaling patterns

Combine hooks and subagents to automate multi-step workflows.

Orchestrator pattern

The agent can delegate to defined subagents when prompted. For complex tasks, specify which subagents to use:

> "Refactor the auth module. Use subagents: /implementer for code changes and /verifier to confirm each change works."

The parent agent coordinates while subagents do the work in isolated contexts.

5. Reference

Documentation