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.
| Feature | Purpose |
|---|---|
| Subagents | Independent agents that handle discrete parts of a task in parallel with isolated context |
| Hooks | Scripts that run before or after agent actions to observe, block, or modify behavior |
2. Subagents
Key concepts
| Term | Meaning |
|---|---|
| Parallel execution | Multiple subagents run simultaneously to speed up complex tasks |
| Context isolation | Each subagent has its own context window, keeping the main chat clean |
| Specialized expertise | Create 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:
| Field | Required | Description |
|---|---|---|
name | No | Unique identifier. Use lowercase letters and hyphens. Defaults to filename. |
description | No | When to use this subagent. Agent reads this to decide delegation. |
model | No | Model to use: inherit or a specific model ID. Defaults to inherit. |
readonly | No | If true, the subagent runs with restricted write permissions. |
is_background | No | If 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.
-
Create the file:
.cursor/agents/verifier.md -
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. -
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 case | Description |
|---|---|
| Automation | Run formatters, linters, or other checks automatically after edits |
| Security | Scan for secrets or PII, and gate risky operations like database writes |
| Observability | Log agent actions to get insights into your development process |
Hook events
Common events:
| Event | When it fires |
|---|---|
sessionStart / sessionEnd | At the start and end of a session |
preToolUse / postToolUse | Before and after any tool is used |
beforeShellExecution | Before a shell command is executed |
afterFileEdit | After the agent edits a file |
stop | When 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.
-
Create
hooks.json: In your project's.cursor/directory, createhooks.json:{ "version": 1, "hooks": { "beforeShellExecution": [{ "command": ".cursor/hooks/audit.sh" }], "afterFileEdit": [{ "command": ".cursor/hooks/audit.sh" }] } } -
Create the script: In
.cursor/hooks/, createaudit.sh:#!/bin/bash INPUT=$(cat) echo "$(date): $INPUT" >> .cursor/audit.log echo '{}' exit 0 -
Make it executable:
chmod +x .cursor/hooks/audit.sh -
Usage: As the agent works, check the
.cursor/audit.logfile 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.
-
Create
TASKS.mdat 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 -
Create the implementer subagent: In
.cursor/agents/, createimplementer.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 -
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, readingloop_countfrom stdin. -
Create the hook script: In
.cursor/hooks/, createcheck-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(); } -
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.
-
Set up
hooks.json:{ "version": 1, "hooks": { "beforeShellExecution": [ { "command": ".cursor/hooks/block-command.sh", "matcher": "BLOCK_TEST" } ] } } -
Create the script: In
.cursor/hooks/, createblock-command.sh:#!/bin/bash echo '{"permission": "deny", "user_message": "This command has been blocked for safety."}' exit 0 -
Make it executable:
chmod +x .cursor/hooks/block-command.sh -
Test it: Run
echo BLOCK_TESTand verify it's blocked. TheBLOCK_TESTpattern 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.