Complete API documentation for the custom subagent system.
Execute a named subagent with a specific goal.
async function runSubagent(
name: string,
userGoal: string,
registry: SubagentRegistry,
options?: RunSubagentOptions
): Promise<SubagentResult>| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Subagent identifier from the registry |
userGoal |
string |
Yes | Task description for the subagent to accomplish |
registry |
SubagentRegistry |
Yes | Registry object containing subagent definitions |
options |
RunSubagentOptions |
No | Configuration options for execution |
interface RunSubagentOptions {
cwd?: string // Working directory (default: process.cwd())
context?: string // Focused conversation context (files, intent, project details)
onMessage?: (msg: any) => void // Message streaming callback
timeout?: number // Execution timeout in milliseconds
}Promise<SubagentResult> - Structured result containing:
interface SubagentResult {
summary: string // Concise summary of what was accomplished
transcript: string[] // Full execution transcript (all text outputs)
filesChanged: string[] // List of files modified during execution
metadata: {
subagentName: string // Name of the subagent that executed
goal: string // Original goal provided
startTime: string // ISO timestamp of start
endTime: string // ISO timestamp of completion
duration: number // Duration in milliseconds
}
}Error- If the subagent name is not found in the registryError- If no result is received from the subagentAbortError- If execution times out (when timeout is set)Error- If the subagent execution encounters an error
Basic usage:
import { runSubagent } from './index.js'
import { subagents } from './subagents.js'
const result = await runSubagent(
'test-runner',
'Run all unit tests',
subagents
)
console.log('Summary:', result.summary)
console.log('Files changed:', result.filesChanged)
console.log('Duration:', result.metadata.duration, 'ms')With options:
const result = await runSubagent(
'migration-planner',
'Plan migration from CommonJS to ESM',
subagents,
{
cwd: '/path/to/project',
timeout: 120000, // 2 minutes
onMessage: (msg) => {
if (msg.type === 'text') {
console.log('[Subagent]:', msg.text)
}
}
}
)With conversation context:
const context = `
User is working on a Next.js project with TypeScript.
Relevant files:
- package.json uses "type": "commonjs"
- src/utils/db.js has require() statements
- src/api/routes.ts mixes import and require
User wants to modernize to ESM for better tree-shaking.
`
const result = await runSubagent(
'migration-planner',
'Create a step-by-step migration plan',
subagents,
{
cwd: '/path/to/project',
context,
timeout: 120000
}
)Error handling:
try {
const result = await runSubagent('unknown-agent', 'task', subagents)
} catch (error) {
console.error('Subagent failed:', error.message)
// Error: Unknown subagent: unknown-agent. Available: test-runner, migration-planner, ...
}Create a permission rule for tool access control.
function createPermission(
tool: string,
action: 'allow' | 'deny' | 'ask' | 'reject',
options?: PermissionOptions
): Permission| Parameter | Type | Required | Description |
|---|---|---|---|
tool |
string |
Yes | Tool name (e.g., 'Read', 'Write', 'Bash') |
action |
'allow' | 'deny' | 'ask' | 'reject' |
Yes | Permission action |
options |
PermissionOptions |
No | Pattern matching options |
'allow'- Allow the tool without asking'deny'- Deny the tool silently'ask'- Ask for user approval before allowing'reject'- Reject the tool with an error
interface PermissionOptions {
matches?: {
path?: string // Glob pattern for file paths (Read/Write)
cmd?: string // Pattern for commands (Bash)
}
}Glob patterns for paths:
**/*.ts- All TypeScript filessrc/**- All files in src directory**/*.{test,spec}.*- All test files**/secrets/**- All files in secrets directories
Patterns for commands:
npm test*- Commands starting with "npm test"git*- All git commandsnpm audit*- npm audit commands
Allow all Read operations:
createPermission('Read', 'allow')Ask before writing to source files:
createPermission('Write', 'ask', {
matches: { path: 'src/**' }
})Deny all Write operations:
createPermission('Write', 'deny')Allow specific bash commands:
createPermission('Bash', 'allow', {
matches: { cmd: 'npm test*' }
})Multiple file patterns:
createPermission('Write', 'ask', {
matches: { path: '**/*.{md,txt,json}' }
})Defines a subagent configuration.
type NamedSubagent = {
system: string // System prompt with role and rules
mcp?: MCPConfig // Optional MCP server configuration
permissions?: ReturnType<typeof createPermission>[] // Tool access permissions
}const mySubagent: NamedSubagent = {
system: `You are a specialized subagent for testing.
Rules:
- Run tests only
- Report all results
- Fix failures when possible`,
permissions: [
createPermission('Read', 'allow'),
createPermission('Write', 'ask'),
createPermission('Bash', 'allow', { matches: { cmd: 'npm test*' } }),
],
mcp: {
servers: {
'test-server': {
command: 'npx',
args: ['-y', 'test-mcp-server'],
}
}
}
}A collection of named subagents.
type SubagentRegistry = Record<string, NamedSubagent>const registry: SubagentRegistry = {
'test-runner': {
system: 'You run tests...',
permissions: [...]
},
'code-reviewer': {
system: 'You review code...',
permissions: [...]
}
}Configuration options for subagent execution.
interface RunSubagentOptions {
cwd?: string // Working directory
context?: string // Focused conversation context
onMessage?: (msg: any) => void // Message handler
timeout?: number // Timeout in milliseconds
}Messages received via onMessage callback:
{
type: 'text',
text: string
}{
type: 'result',
result: string
}{
type: 'tool_use',
tool: string,
input: any
}When running as an MCP server, the following tools are exposed:
Each subagent is exposed as subagent_<name>:
{
"name": "subagent_test-runner",
"description": "Test Runner",
"inputSchema": {
"type": "object",
"properties": {
"goal": {
"type": "string",
"description": "The task or goal for this subagent to accomplish"
},
"cwd": {
"type": "string",
"description": "Working directory (optional)"
}
},
"required": ["goal"]
}
}Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "subagent_test-runner",
"arguments": {
"goal": "Run all tests and fix failures",
"cwd": "/path/to/project"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "All tests passed successfully."
}
]
}
}Error Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Error: Unknown subagent: invalid-name"
}
],
"isError": true
}
}npm run dev <subagent-name> "<goal>" [cwd]subagent-name- Name of the subagent from the registrygoal- Task description (quoted if contains spaces)cwd- Optional working directory (defaults to current directory)
# Run test-runner
npm run dev test-runner "Run all unit tests"
# Run in specific directory
npm run dev migration-planner "Create migration plan" /path/to/project
# Run security audit
npm run dev security-auditor "Scan for vulnerabilities"π€ Running subagent: test-runner
π Goal: Run all unit tests
π Working directory: /path/to/project
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[streaming messages...]
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Subagent completed
π Result:
All tests passed successfully.
-
Principle of Least Privilege
// Start restrictive, then allow specific operations permissions: [ createPermission('Read', 'allow'), // Safe createPermission('Write', 'ask'), // Controlled createPermission('Bash', 'deny') // Restricted ]
-
Specific Over General
// Good: Specific patterns createPermission('Write', 'allow', { matches: { path: '**/*.test.*' } }) // Avoid: Overly broad permissions createPermission('Write', 'allow')
-
Order Matters
// More specific rules first permissions: [ createPermission('Write', 'deny', { matches: { path: 'config/prod.json' } }), createPermission('Write', 'ask', { matches: { path: 'config/**' } }), createPermission('Write', 'allow') ]
try {
const result = await runSubagent(name, goal, registry, {
timeout: 60000,
onMessage: (msg) => {
// Handle streaming messages
if (msg.type === 'text') {
console.log(msg.text)
}
}
})
return result
} catch (error) {
if (error.message.includes('Unknown subagent')) {
console.error('Subagent not found:', name)
} else if (error.message.includes('aborted')) {
console.error('Subagent timed out')
} else {
console.error('Execution failed:', error)
}
throw error
}// Short timeout for quick tasks
await runSubagent('quick-check', goal, registry, {
timeout: 30000 // 30 seconds
})
// Longer timeout for complex analysis
await runSubagent('migration-planner', goal, registry, {
timeout: 300000 // 5 minutes
})
// No timeout for user-supervised tasks
await runSubagent('interactive-agent', goal, registry)