Skip to content

Latest commit

Β 

History

History
537 lines (435 loc) Β· 11.4 KB

File metadata and controls

537 lines (435 loc) Β· 11.4 KB

API Reference

Complete API documentation for the custom subagent system.

Core API

runSubagent()

Execute a named subagent with a specific goal.

async function runSubagent(
  name: string,
  userGoal: string,
  registry: SubagentRegistry,
  options?: RunSubagentOptions
): Promise<SubagentResult>

Parameters

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

Options

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
}

Returns

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
  }
}

Throws

  • Error - If the subagent name is not found in the registry
  • Error - If no result is received from the subagent
  • AbortError - If execution times out (when timeout is set)
  • Error - If the subagent execution encounters an error

Examples

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, ...
}

createPermission()

Create a permission rule for tool access control.

function createPermission(
  tool: string,
  action: 'allow' | 'deny' | 'ask' | 'reject',
  options?: PermissionOptions
): Permission

Parameters

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

Permission Actions

  • '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

Options

interface PermissionOptions {
  matches?: {
    path?: string   // Glob pattern for file paths (Read/Write)
    cmd?: string    // Pattern for commands (Bash)
  }
}

Pattern Matching

Glob patterns for paths:

  • **/*.ts - All TypeScript files
  • src/** - 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 commands
  • npm audit* - npm audit commands

Examples

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}' } 
})

Types

NamedSubagent

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
}

Example

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'],
      }
    }
  }
}

SubagentRegistry

A collection of named subagents.

type SubagentRegistry = Record<string, NamedSubagent>

Example

const registry: SubagentRegistry = {
  'test-runner': {
    system: 'You run tests...',
    permissions: [...]
  },
  'code-reviewer': {
    system: 'You review code...',
    permissions: [...]
  }
}

RunSubagentOptions

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
}

Message Types

Messages received via onMessage callback:

Text Message

{
  type: 'text',
  text: string
}

Result Message

{
  type: 'result',
  result: string
}

Tool Use Message

{
  type: 'tool_use',
  tool: string,
  input: any
}

MCP Server API

When running as an MCP server, the following tools are exposed:

Tool Format

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"]
  }
}

Tool Invocation

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
  }
}

CLI Usage

Command Format

npm run dev <subagent-name> "<goal>" [cwd]

Arguments

  • subagent-name - Name of the subagent from the registry
  • goal - Task description (quoted if contains spaces)
  • cwd - Optional working directory (defaults to current directory)

Examples

# 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"

Output

πŸ€– Running subagent: test-runner
πŸ“‹ Goal: Run all unit tests
πŸ“ Working directory: /path/to/project
────────────────────────────────────────────────────────────────────────────────
[streaming messages...]
────────────────────────────────────────────────────────────────────────────────
Subagent completed

πŸ“Š Result:
All tests passed successfully.

Best Practices

Permission Design

  1. Principle of Least Privilege

    // Start restrictive, then allow specific operations
    permissions: [
      createPermission('Read', 'allow'),           // Safe
      createPermission('Write', 'ask'),            // Controlled
      createPermission('Bash', 'deny')             // Restricted
    ]
  2. Specific Over General

    // Good: Specific patterns
    createPermission('Write', 'allow', { matches: { path: '**/*.test.*' } })
    
    // Avoid: Overly broad permissions
    createPermission('Write', 'allow')
  3. Order Matters

    // More specific rules first
    permissions: [
      createPermission('Write', 'deny', { matches: { path: 'config/prod.json' } }),
      createPermission('Write', 'ask', { matches: { path: 'config/**' } }),
      createPermission('Write', 'allow')
    ]

Error Handling

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
}

Timeout Management

// 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)