SubAgents
SubAgents are child agents that can be delegated complex tasks. Unlike AIFunctions (single operations) or Skills (guided workflows), SubAgents have their own reasoning capabilities and can autonomously work through multi-step problems.
What Are SubAgents?
A SubAgent is a fully-configured agent exposed as a tool:
Parent Agent SubAgent
┌─────────────────┐ ┌─────────────────┐
│ "Review this PR"│ ────────► │ Code Reviewer │
│ │ │ │
│ │ │ • Reads files │
│ │ │ • Analyzes code │
│ │ ◄──────── │ • Returns report│
└─────────────────┘ └─────────────────┘
delegates works autonomouslyWhen to use SubAgents:
- Tasks requiring autonomous multi-step reasoning
- Specialized domains (code review, research, analysis)
- Isolating complex workflows from the parent agent
Basic Usage
Mark a method with [SubAgent] and return a SubAgent object:
public class DelegationTool
{
[SubAgent]
public SubAgent CodeReviewer()
{
var config = new AgentConfig
{
Name = "Code Reviewer",
SystemInstructions = @"
You are an expert code reviewer. When given code to review:
1. Check for bugs and logic errors
2. Evaluate code style and readability
3. Suggest improvements
4. Rate overall quality (1-10)"
};
return SubAgentFactory.Create(
name: "Code Review",
description: "Reviews code for quality, bugs, and best practices",
agentConfig: config
);
}
}SubAgentFactory Methods
Create() - Stateless
Each invocation starts fresh with a new conversation thread:
public static SubAgent Create(
string name,
string description,
AgentConfig agentConfig,
params Type[] toolTypes // Optional tools for the SubAgent
)[SubAgent]
public SubAgent Researcher()
{
return SubAgentFactory.Create(
name: "Research",
description: "Researches topics thoroughly",
agentConfig: new AgentConfig { SystemInstructions = "..." }
);
}Use when: Each task is independent; no context needs to persist.
CreateStateful() - Shared Thread
Maintains conversation context across multiple invocations:
public static SubAgent CreateStateful(
string name,
string description,
AgentConfig agentConfig,
params Type[] toolTypes
)[SubAgent]
public SubAgent ProjectAssistant()
{
return SubAgentFactory.CreateStateful(
name: "Project Assistant",
description: "Helps with ongoing project tasks, remembers context",
agentConfig: new AgentConfig { SystemInstructions = "..." }
);
}Use when: The SubAgent needs to remember previous interactions (e.g., ongoing project work).
CreatePerSession() - User-Managed Thread
External thread management for advanced scenarios:
public static SubAgent CreatePerSession(
string name,
string description,
AgentConfig agentConfig,
params Type[] toolTypes
)Use when: You need fine-grained control over thread lifecycle.
Thread Modes Explained
| Mode | Thread Behavior | Memory | Use Case |
|---|---|---|---|
Stateless | New thread per call | None | Independent tasks |
SharedThread | Reuses same thread | Persistent | Ongoing collaboration |
PerSession | Externally managed | Controlled | Custom session handling |
Example: Stateless vs Stateful
// Stateless: Each review is independent
[SubAgent]
public SubAgent IndependentReviewer()
{
return SubAgentFactory.Create(
name: "Review Code",
description: "Reviews a single piece of code",
agentConfig: reviewerConfig
);
}
// Stateful: Remembers previous reviews, can compare
[SubAgent]
public SubAgent ProjectReviewer()
{
return SubAgentFactory.CreateStateful(
name: "Project Reviewer",
description: "Reviews code with project context, remembers past reviews",
agentConfig: reviewerConfig
);
}SubAgents with Tools
SubAgents can have their own tools:
[SubAgent]
public SubAgent FileAnalyzer()
{
var config = new AgentConfig
{
Name = "File Analyzer",
SystemInstructions = "Analyze files and provide insights..."
};
return SubAgentFactory.Create(
name: "Analyze Files",
description: "Analyzes files for patterns, issues, and insights",
agentConfig: config,
typeof(FileSystemTool), // SubAgent can read files
typeof(SearchTool) // SubAgent can search
);
}The SubAgent only has access to the tools you explicitly provide.
Provider Inheritance
By default, SubAgents inherit the parent agent's provider (chat client). If you don't specify a Provider in the SubAgent's AgentConfig, it automatically uses the same LLM provider as the parent.
[SubAgent]
public SubAgent SimpleReviewer()
{
// No Provider specified = inherits parent's provider
var config = new AgentConfig
{
Name = "Simple Reviewer",
SystemInstructions = "Review code for issues..."
};
return SubAgentFactory.Create(
name: "Review",
description: "Quick code review",
agentConfig: config // Uses parent's provider automatically
);
}This is convenient for most cases—your SubAgents use the same model as the parent without extra configuration.
Overriding the Provider
To use a different provider (e.g., a cheaper model for simple tasks, or a specialized model), explicitly specify Provider in the config:
[SubAgent]
public SubAgent SpecializedAnalyzer()
{
var config = new AgentConfig
{
Name = "Specialized Analyzer",
SystemInstructions = "...",
Provider = new ProviderConfig
{
ProviderKey = "openai",
ModelName = "gpt-4o-mini", // Use cheaper model for this task
ApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
}
};
return SubAgentFactory.Create(
name: "Specialized Analysis",
description: "Uses GPT-4o-mini for cost-effective analysis",
agentConfig: config
);
}Common patterns:
- Use a cheaper/faster model for simple SubAgent tasks
- Use a specialized model (e.g., code-focused) for domain-specific work
- Use a different provider entirely (e.g., parent uses Anthropic, SubAgent uses OpenAI)
Permission Behavior
SubAgents always require permission by default. This is because they can perform multiple autonomous actions.
Unlike AIFunctions where [RequiresPermission] is opt-in, SubAgents are inherently permission-required:
// No [RequiresPermission] needed - it's implicit
[SubAgent]
public SubAgent DangerousOperations()
{
return SubAgentFactory.Create(
name: "System Admin",
description: "Performs system administration tasks",
agentConfig: adminConfig,
typeof(SystemTool)
);
}
// User will ALWAYS be prompted before this SubAgent runsEvent Bubbling
SubAgent events bubble up to the parent agent. This allows the parent to:
- Monitor SubAgent progress
- Log SubAgent actions
- React to SubAgent completions
var agent = new AgentBuilder()
.WithTool<DelegationTool>()
.OnToolCall(e => Console.WriteLine($"Tool called: {e.ToolName}"))
.OnSubAgentStart(e => Console.WriteLine($"SubAgent started: {e.Name}"))
.OnSubAgentComplete(e => Console.WriteLine($"SubAgent done: {e.Result}"))
.Build();Typed Metadata
For compile-time validation, use the generic attribute:
public class DelegationMetadata : IToolMetadata
{
public bool HasSpecializedAgents { get; set; }
// Implementation...
}
public class DelegationTool
{
[SubAgent<DelegationMetadata>]
public SubAgent SpecialAgent()
{
return SubAgentFactory.Create(
name: "Special Agent",
description: "Specialized task handler",
agentConfig: specialConfig
);
}
}→ See 02.1.4 Tool Metadata.md for details.
Conditional SubAgents
Show or hide SubAgents based on runtime conditions:
[SubAgent]
[ConditionalSubAgent("HasSpecializedAgents")]
public SubAgent AdvancedAnalyzer()
{
// Only visible when metadata.HasSpecializedAgents is true
return SubAgentFactory.Create(
name: "Advanced Analysis",
description: "Advanced analysis capabilities",
agentConfig: advancedConfig
);
}→ See 02.1.4 Tool Metadata.md for conditional registration details.
SubAgents vs Skills vs AIFunctions
| Aspect | AIFunction | Skill | SubAgent |
|---|---|---|---|
| Complexity | Single operation | Multi-step workflow | Autonomous reasoning |
| Control | Direct | Guided by instructions | Delegated |
| Memory | None | None | Optional (stateful) |
| Tools | Parent's | Parent's (referenced) | Own set |
| Provider | Parent's | Parent's | Configurable |
| Permission | Opt-in | Opt-in | Always required |
Decision guide:
- AIFunction: "Call this function with these parameters"
- Skill: "Follow these steps using these functions"
- SubAgent: "Figure out how to accomplish this goal"
Best Practices
Give SubAgents clear, focused purposes: A SubAgent should excel at one domain.
Write detailed system instructions: SubAgents rely heavily on their instructions for autonomous work.
Provide only necessary tools: Don't give SubAgents access to tools they don't need.
Use stateful mode sparingly: Shared threads consume more resources; use only when context is genuinely needed.
Consider provider costs: SubAgents make their own LLM calls; expensive models add up.
// Good: Focused, well-instructed, minimal tools
[SubAgent]
public SubAgent SecurityAuditor()
{
var config = new AgentConfig
{
Name = "Security Auditor",
SystemInstructions = @"
You are a security expert. When auditing code:
1. Check for OWASP Top 10 vulnerabilities
2. Identify authentication/authorization issues
3. Look for data exposure risks
4. Provide severity ratings (Critical/High/Medium/Low)
5. Suggest specific fixes for each issue
Be thorough but focused. Don't report style issues."
};
return SubAgentFactory.Create(
name: "Security Audit",
description: "Audits code for security vulnerabilities",
agentConfig: config,
typeof(FileSystemTool) // Only needs to read files
);
}
// Bad: Vague purpose, overpowered
[SubAgent]
public SubAgent DoAnything()
{
return SubAgentFactory.Create(
name: "Helper",
description: "Helps with stuff",
agentConfig: new AgentConfig { SystemInstructions = "Help the user" },
typeof(FileSystemTool),
typeof(DatabaseTool),
typeof(NetworkTool),
typeof(SystemTool) // Way too many capabilities
);
}Next Steps
- 02.1.4 Tool Metadata.md - Dynamic descriptions and conditionals
- 02.1.5 Context Engineering.md - Context management