Skip to main content

Agent Subagents

Subagents enable hierarchical agent systems where a main agent can delegate tasks to specialized subagents. This allows for complex workflows with division of labor and specialized expertise.

Overview​

Subagents in the Go-AI SDK allow you to:

  • Delegate specialized tasks: Route work to agents optimized for specific domains
  • Build hierarchies: Create multi-level agent structures with subagents having their own subagents
  • Separate concerns: Keep agents focused on their areas of expertise
  • Coordinate workflows: Orchestrate complex tasks across multiple specialized agents

Basic Usage​

Creating Subagents​

// Create main agent
mainConfig := agent.AgentConfig{
Model: model,
System: "You are a coordinator agent",
MaxSteps: 5,
}
mainAgent := agent.NewToolLoopAgent(mainConfig)

// Create research subagent
researchConfig := agent.AgentConfig{
Model: model,
System: "You are a research specialist",
MaxSteps: 3,
}
researchAgent := agent.NewToolLoopAgent(researchConfig)

// Register subagent
if err := mainAgent.AddSubagent("research", researchAgent); err != nil {
log.Fatalf("Failed to add subagent: %v", err)
}

Delegating to Subagents​

// Delegate with a prompt
result, err := mainAgent.DelegateToSubagent(
ctx,
"research",
"Find information about Go concurrency patterns",
)

// Delegate with messages
messages := []types.Message{
{
Role: types.RoleUser,
Content: []types.ContentPart{
types.TextContent{Text: "Analyze this data"},
},
},
}

result, err := mainAgent.DelegateToSubagentWithMessages(
ctx,
"research",
messages,
)

Subagent Registry​

The SubagentRegistry manages a collection of subagents and provides methods for registration, retrieval, and delegation.

Creating a Registry​

registry := agent.NewSubagentRegistry()

Registry Operations​

// Register a subagent
err := registry.Register("research", researchAgent)

// Check if a subagent exists
exists := registry.Has("research")

// Get a subagent
subagent, found := registry.Get("research")

// Execute delegation
result, err := registry.Execute(ctx, "research", "find data")

// Execute with messages
result, err := registry.ExecuteWithMessages(ctx, "research", messages)

// List all subagents
names := registry.List()

// Remove a subagent
registry.Unregister("research")

// Count subagents
count := registry.Count()

// Clear all subagents
registry.Clear()

Complete Example​

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/digitallysavvy/go-ai/pkg/agent"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
openaiProvider := openai.New(openai.Config{APIKey: os.Getenv("OPENAI_API_KEY")})
model, _ := openaiProvider.LanguageModel("gpt-4o-mini")

// Create main coordinator agent
mainConfig := agent.AgentConfig{
Model: model,
System: "You coordinate tasks between specialized agents",
MaxSteps: 5,
}
mainAgent := agent.NewToolLoopAgent(mainConfig)

// Create research subagent
researchConfig := agent.AgentConfig{
Model: model,
System: "You are a research specialist who finds and summarizes information",
MaxSteps: 3,
}
researchAgent := agent.NewToolLoopAgent(researchConfig)

// Create analysis subagent
analysisConfig := agent.AgentConfig{
Model: model,
System: "You are an analysis specialist who provides insights from data",
MaxSteps: 3,
}
analysisAgent := agent.NewToolLoopAgent(analysisConfig)

// Register subagents
mainAgent.AddSubagent("research", researchAgent)
mainAgent.AddSubagent("analysis", analysisAgent)

// Delegate to research subagent
researchResult, err := mainAgent.DelegateToSubagent(
context.Background(),
"research",
"Find information about Go interfaces",
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Research result:", researchResult.Text)

// Delegate to analysis subagent
analysisResult, err := mainAgent.DelegateToSubagent(
context.Background(),
"analysis",
"Analyze the benefits of using interfaces",
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Analysis result:", analysisResult.Text)
}

Hierarchical Agents​

Subagents can have their own subagents, creating multi-level hierarchies:

// Main agent
mainAgent := agent.NewToolLoopAgent(mainConfig)

// Research agent (subagent of main)
researchAgent := agent.NewToolLoopAgent(researchConfig)
mainAgent.AddSubagent("research", researchAgent)

// Deep research agent (subagent of research)
deepResearchAgent := agent.NewToolLoopAgent(deepResearchConfig)

// researchAgent is already a *agent.ToolLoopAgent, so its subagent methods
// are available directly -- no type assertion needed.
researchAgent.AddSubagent("deep_research", deepResearchAgent)

// Delegate through hierarchy
// Main -> Research
result1, _ := mainAgent.DelegateToSubagent(ctx, "research", "broad research task")

// Research -> Deep Research
result2, _ := researchAgent.DelegateToSubagent(ctx, "deep_research", "detailed research task")

Delegation Tracking​

Track delegation history with DelegationTracker:

tracker := agent.NewDelegationTracker()

// Track a delegation
delegation := agent.SubagentDelegation{
SubagentName: "research",
Prompt: "Find data",
Result: result,
}
tracker.Track(delegation)

// Get all delegations
delegations := tracker.GetDelegations()

for _, d := range delegations {
fmt.Printf("Delegated to %s: %s\n", d.SubagentName, d.Prompt)
if d.Result != nil {
fmt.Printf("Result: %s\n", d.Result.Text)
}
}

Best Practices​

1. Clear Specialization​

Give each subagent a clear, focused role:

// Good: Specialized subagents
researchAgent // Finds information
analysisAgent // Analyzes data
summaryAgent // Creates summaries

// Avoid: Generic subagents
helperAgent // Too vague
utilityAgent // Unclear purpose

2. Appropriate System Prompts​

Tailor system prompts to each subagent's specialty:

researchConfig := agent.AgentConfig{
System: `You are a research specialist. Your role is to:
- Find relevant information on requested topics
- Verify sources and accuracy
- Provide comprehensive summaries
- Focus on factual, well-sourced content`,
}

analysisConfig := agent.AgentConfig{
System: `You are a data analysis specialist. Your role is to:
- Analyze patterns and trends in data
- Provide insights and interpretations
- Identify key findings
- Present clear, actionable conclusions`,
}

3. Manage Subagent Scope​

Limit subagent steps to prevent runaway execution:

subagentConfig := agent.AgentConfig{
Model: model,
StopWhen: []ai.StopCondition{ai.IsStepCount(3)}, // Limit subagent steps
}

mainConfig := agent.AgentConfig{
Model: model,
StopWhen: []ai.StopCondition{ai.IsStepCount(10)}, // Main agent can take more steps
}

4. Error Handling​

Handle delegation failures gracefully:

result, err := mainAgent.DelegateToSubagent(ctx, "research", prompt)
if err != nil {
// Log the error
log.Printf("Delegation to research failed: %v", err)

// Try alternative approach
result, err = mainAgent.DelegateToSubagent(ctx, "backup_research", prompt)

// Or handle in main agent
if err != nil {
return mainAgent.Execute(ctx, "Handle this yourself: "+prompt)
}
}

5. Combine with Skills​

Subagents can have their own skills:

// Create subagent with skills
analysisAgent := agent.NewToolLoopAgent(analysisConfig)

// Add skills to subagent
statisticsSkill := &agent.Skill{
Name: "calculate_stats",
Description: "Calculates statistics",
Handler: statsHandler,
}
analysisAgent.AddSkill(statisticsSkill)

// Register as subagent
mainAgent.AddSubagent("analysis", analysisAgent)

// Access subagent's skills
subagent, _ := mainAgent.GetSubagent("analysis")
toolLoopSubagent := subagent.(*agent.ToolLoopAgent)
result, _ := toolLoopSubagent.ExecuteSkill(ctx, "calculate_stats", data)

Design Patterns​

1. Coordinator Pattern​

Main agent coordinates work across subagents:

mainAgent := agent.NewToolLoopAgent(coordinatorConfig)
mainAgent.AddSubagent("research", researchAgent)
mainAgent.AddSubagent("analysis", analysisAgent)
mainAgent.AddSubagent("writer", writerAgent)

// Main agent decides which subagent to use
result, _ := mainAgent.Execute(ctx, "Create a report on AI trends")

2. Pipeline Pattern​

Subagents form a processing pipeline:

// Step 1: Research
researchResult, _ := mainAgent.DelegateToSubagent(ctx, "research", topic)

// Step 2: Analyze research
analysisResult, _ := mainAgent.DelegateToSubagent(ctx, "analysis", researchResult.Text)

// Step 3: Summarize analysis
summaryResult, _ := mainAgent.DelegateToSubagent(ctx, "writer", analysisResult.Text)

3. Expert Panel Pattern​

Multiple subagents provide different perspectives:

// Get opinions from multiple experts
technicalView, _ := mainAgent.DelegateToSubagent(ctx, "technical_expert", question)
businessView, _ := mainAgent.DelegateToSubagent(ctx, "business_expert", question)
legalView, _ := mainAgent.DelegateToSubagent(ctx, "legal_expert", question)

// Main agent synthesizes
synthesis := fmt.Sprintf(
"Technical: %s\nBusiness: %s\nLegal: %s",
technicalView.Text,
businessView.Text,
legalView.Text,
)

4. Hierarchical Delegation​

Multi-level delegation for complex tasks:

// Level 1: Main agent
mainAgent.AddSubagent("operations", operationsAgent)

// Level 2: Operations has sub-specialists
operationsAgent.AddSubagent("data_processing", dataAgent)
operationsAgent.AddSubagent("quality_control", qcAgent)

// Level 3: Data processing has its own subagents
dataAgent.AddSubagent("cleaning", cleaningAgent)
dataAgent.AddSubagent("transformation", transformAgent)

Subagents vs Tools​

When to Use Subagents​

  • Complex specialized tasks: When a task requires its own agent loop
  • Domain expertise: When you need focused, specialized behavior
  • Workflow steps: When breaking a task into distinct phases
  • Resource management: When you want separate token/cost tracking

When to Use Tools​

  • Simple operations: When a single function call suffices
  • LLM-driven decisions: When the model should choose when to call
  • Structured I/O: When you need parameter schemas
  • Native integration: When using model's tool-calling features

Subagents + Tools​

Use both for maximum flexibility:

// Subagent with tools
researchAgent := agent.NewToolLoopAgent(researchConfig)

// Add tools to subagent (AgentConfig.Tools is unexported on ToolLoopAgent;
// use AddTool to add tools after construction)
researchAgent.AddTool(searchTool)
researchAgent.AddTool(summarizerTool)

// Register as subagent
mainAgent.AddSubagent("research", researchAgent)

// Subagent can use its tools when delegated to
result, _ := mainAgent.DelegateToSubagent(ctx, "research", "find information")

Performance Considerations​

Token Usage​

Subagents add overhead:

// Each delegation is a full agent execution
result, _ := mainAgent.DelegateToSubagent(ctx, "research", prompt)
// Uses tokens for: main agent + research agent + context passing

Limiting Costs​

Control subagent resource usage:

subagentConfig := agent.AgentConfig{
Model: model,
MaxSteps: 2, // Limit steps
MaxTokens: &maxTokens, // Limit tokens per step
}

Caching​

Reuse subagents instead of recreating:

// Good: Create once, reuse
researchAgent := agent.NewToolLoopAgent(config)
mainAgent.AddSubagent("research", researchAgent)

// Multiple delegations reuse the same subagent
result1, _ := mainAgent.DelegateToSubagent(ctx, "research", prompt1)
result2, _ := mainAgent.DelegateToSubagent(ctx, "research", prompt2)

See Also​

Examples​

See these examples for complete working code: