Sub-Agents
Sub-agents allow you to create specialized agents and use them as tools within other agents. This enables powerful delegation patterns and modular agent architectures.
Overview
A sub-agent is simply an agent converted to a tool using the toTool() method. The parent agent can then delegate tasks to the sub-agent by calling this tool.
Benefits: - Specialization: Different agents for different tasks - Modularity: Reusable agent components - Context isolation: Sub-agents have separate conversation history - Simplified reasoning: Parent focuses on delegation, sub-agents on execution
Basic Example
import { Agent, SIMPLE_FINISH_TOOL, CALCULATOR_TOOL, WebToolProvider } from '@stirrup/stirrup';
// Create specialized sub-agents
const researchAgent = new Agent({
client,
name: 'researcher',
maxTurns: 5,
tools: [new WebToolProvider()],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'You are a research specialist. Use web search to find accurate information.',
});
const mathAgent = new Agent({
client,
name: 'mathematician',
maxTurns: 3,
tools: [CALCULATOR_TOOL],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'You are a math specialist. Use the calculator tool for precise calculations.',
});
// Create coordinator that uses sub-agents
const coordinator = new Agent({
client,
name: 'coordinator',
maxTurns: 10,
tools: [
researchAgent.toTool('Delegate research and web search tasks'),
mathAgent.toTool('Delegate calculations and math problems'),
],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: `You are a task coordinator. Break down complex tasks and delegate to specialists:
- Use 'researcher' for web research and information gathering
- Use 'mathematician' for calculations and math problems`,
});
// Use the coordinator
await using session = coordinator.session();
const result = await session.run(`
What is the current population of Tokyo?
If it grows by 2% annually, what will the population be in 5 years?
`);
How It Works
1. Creating Sub-Agents
Create agents with specialized capabilities:
const codeReviewer = new Agent({
client,
name: 'code-reviewer',
tools: [fileReadTool, grepTool],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'You are an expert code reviewer. Analyze code for bugs and improvements.',
});
const tester = new Agent({
client,
name: 'tester',
tools: [new LocalCodeExecToolProvider()],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'You are a testing specialist. Write and run tests.',
});
2. Converting to Tools
Use toTool() to make agents available as tools:
const coordinator = new Agent({
client,
tools: [
codeReviewer.toTool('Review code for quality and bugs'),
tester.toTool('Write and execute tests'),
],
finishTool: SIMPLE_FINISH_TOOL,
});
3. Delegation
The coordinator delegates by calling sub-agent tools:
// Coordinator sees sub-agents as regular tools
// When called, sub-agent runs independently and returns results
const result = await coordinator.run('Review and test the authentication code');
Design Patterns
Specialist Pattern
Multiple specialized agents for different domains:
const dataAnalyst = new Agent({
client,
name: 'analyst',
tools: [new LocalCodeExecToolProvider()],
systemPrompt: 'Data analysis specialist. Use pandas, numpy for analysis.',
});
const visualizer = new Agent({
client,
name: 'visualizer',
tools: [new LocalCodeExecToolProvider()],
systemPrompt: 'Visualization specialist. Create charts with matplotlib.',
});
const writer = new Agent({
client,
name: 'writer',
tools: [fileWriteTool],
systemPrompt: 'Report writing specialist. Create clear, concise reports.',
});
const dataScience = new Agent({
client,
tools: [
dataAnalyst.toTool('Analyze data'),
visualizer.toTool('Create visualizations'),
writer.toTool('Write reports'),
],
});
Pipeline Pattern
Sequential processing through multiple agents:
const scraper = new Agent({
client,
name: 'scraper',
tools: [new WebToolProvider()],
systemPrompt: 'Extract data from websites.',
});
const cleaner = new Agent({
client,
name: 'cleaner',
tools: [new LocalCodeExecToolProvider()],
systemPrompt: 'Clean and normalize data.',
});
const analyzer = new Agent({
client,
name: 'analyzer',
tools: [new LocalCodeExecToolProvider()],
systemPrompt: 'Analyze data and generate insights.',
});
const pipeline = new Agent({
client,
tools: [
scraper.toTool('Scrape data from URLs'),
cleaner.toTool('Clean and normalize data'),
analyzer.toTool('Analyze data'),
],
systemPrompt: 'Process data through pipeline: scrape → clean → analyze',
});
Hierarchical Pattern
Multiple levels of delegation:
// Level 2: Specialized workers
const pythonExpert = new Agent({ /* ... */ });
const jsExpert = new Agent({ /* ... */ });
// Level 1: Language coordinator
const coder = new Agent({
client,
tools: [
pythonExpert.toTool('Python tasks'),
jsExpert.toTool('JavaScript tasks'),
],
});
// Level 0: Main coordinator
const projectManager = new Agent({
client,
tools: [
coder.toTool('Coding tasks'),
tester.toTool('Testing tasks'),
reviewer.toTool('Code review'),
],
});
Communication Patterns
Direct Task Delegation
Parent provides complete task description:
await coordinator.run(`
Use the researcher to find:
1. Current Bitcoin price
2. 24h trading volume
3. Market cap
`);
Iterative Refinement
Parent iterates based on sub-agent results:
await coordinator.run(`
1. Ask researcher for sales data
2. If data looks incomplete, ask again with more specific query
3. Once you have good data, ask analyst to process it
`);
Parallel Execution
Multiple sub-agents called independently:
await coordinator.run(`
In parallel:
- Ask researcher for market trends
- Ask analyst for current performance
Then combine results into summary
`);
Context Management
Isolated Contexts
Each sub-agent has its own conversation history:
// Researcher's context: only sees research-related conversation
// Math agent's context: only sees math-related conversation
// Coordinator's context: sees overall task and sub-agent results
Passing Context
Parent provides context in sub-agent calls:
await coordinator.run(`
First, use researcher to find Tokyo population.
Then use math agent to calculate 5-year growth at 2%.
Pass the population number to the math agent.
`);
Preserving State
Sub-agents don't preserve state between calls:
// Each call to sub-agent is independent
await coordinator.run('Ask researcher for data on topic A');
await coordinator.run('Ask researcher for data on topic B');
// Researcher doesn't remember topic A
File Handling
Sub-Agent File Creation
Sub-agents can create files:
const imageGen = new Agent({
client,
tools: [new LocalCodeExecToolProvider()],
systemPrompt: 'Create images with matplotlib',
});
const coordinator = new Agent({
client,
tools: [imageGen.toTool('Generate images')],
});
await using session = coordinator.session({ outputDir: './output' });
await session.run('Create a bar chart of sales data');
// Files from sub-agent automatically transferred to ./output/
File Transfer
Files flow from sub-agent → parent:
- Sub-agent creates file in its execution environment
- Sub-agent finishes with file in
pathsparameter - File transferred to parent's execution environment
- Parent can use file or save to output directory
Monitoring Sub-Agents
Structured Logging
Structured logging automatically tracks sub-agent activity:
import { createStructuredLogger } from '@stirrup/stirrup';
const cleanup = createStructuredLogger(coordinator, { level: 'debug' });
await using session = coordinator.session();
await session.run('Complex task');
cleanup();
// Output shows:
// Turn 1
// Tool: researcher
// Turn 1 (sub-agent)
// Tool: web_search
// Turn 2 (sub-agent)
// Tool: finish
// Tool: mathematician
// Turn 1 (sub-agent)
// Tool: calculator
Custom Event Handlers
Track sub-agent calls:
coordinator.on('tool:start', ({ name }) => {
if (name === 'researcher' || name === 'mathematician') {
console.log(`Delegating to ${name}...`);
}
});
coordinator.on('tool:complete', ({ name, result }) => {
if (name === 'researcher' || name === 'mathematician') {
console.log(`${name} completed:`, result);
}
});
Best Practices
1. Clear Specialization
Give each sub-agent a focused responsibility:
// Good: Focused specialists
const scraper = new Agent({ /* web scraping */ });
const analyzer = new Agent({ /* data analysis */ });
// Bad: Overlapping responsibilities
const dataWorker = new Agent({ /* scraping AND analysis */ });
2. Appropriate Granularity
Don't over-decompose:
// Too granular - unnecessary overhead
const adder = new Agent({ /* only addition */ });
const subtractor = new Agent({ /* only subtraction */ });
// Better - appropriate scope
const calculator = new Agent({ /* all arithmetic */ });
3. Clear System Prompts
Help agents understand their role:
const reviewer = new Agent({
client,
systemPrompt: `You are a code reviewer. Your ONLY job is to:
1. Read code files
2. Identify bugs, performance issues, and anti-patterns
3. Suggest improvements
Do NOT write or modify code - only review.`,
});
4. Limit Recursion Depth
Avoid deep nesting:
// Good: 2 levels
coordinator → specialists → tools
// Risky: 4+ levels
coordinator → managers → specialists → workers → tools
5. Handle Sub-Agent Failures
Sub-agents might fail or produce unexpected results:
await coordinator.run(`
Try to get data from researcher.
If researcher can't find data, use fallback approach:
- Check local database
- Use cached data
- Report data unavailable
`);
Limitations
Token Usage
Each sub-agent call includes: - Sub-agent's system prompt - Sub-agent's tool definitions - Sub-agent's conversation
This increases token usage significantly.
Latency
Sub-agent calls add latency: - Parent → sub-agent delegation - Sub-agent execution (multiple turns) - Result → parent
Complexity
More agents = more complexity: - Harder to debug - More failure modes - Increased token costs
Examples
Complete Data Pipeline
import { Agent, SIMPLE_FINISH_TOOL, WebToolProvider, LocalCodeExecToolProvider } from '@stirrup/stirrup';
// Fetch data from web
const fetcher = new Agent({
client,
name: 'fetcher',
maxTurns: 3,
tools: [new WebToolProvider()],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'Fetch data from URLs and APIs.',
});
// Process and clean data
const processor = new Agent({
client,
name: 'processor',
maxTurns: 5,
tools: [new LocalCodeExecToolProvider()],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'Clean and process data using pandas.',
});
// Create visualizations
const visualizer = new Agent({
client,
name: 'visualizer',
maxTurns: 5,
tools: [new LocalCodeExecToolProvider()],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: 'Create charts with matplotlib.',
});
// Coordinate the pipeline
const pipeline = new Agent({
client,
name: 'pipeline',
maxTurns: 15,
tools: [
fetcher.toTool('Fetch data from web'),
processor.toTool('Process and clean data'),
visualizer.toTool('Create visualizations'),
],
finishTool: SIMPLE_FINISH_TOOL,
systemPrompt: `Execute data pipeline:
1. Fetch data
2. Process data
3. Create visualizations`,
});
await using session = pipeline.session({ outputDir: './results' });
await session.run('Analyze recent tech stock performance and create charts');
Next Steps
- Creating Tools - Build custom tools
- Tool Providers - Manage tool lifecycle
- Examples - More sub-agent examples