Skip to content

Task Tools - Extensible Sub-Agent System

Overview

The Task Tools provide an extensible hierarchical sub-agent system for Spring AI, inspired by Claude Code's sub-agents. This architecture enables your main AI agent to delegate complex, multi-step tasks to specialized sub-agents while supporting multiple execution backends (Claude-based, A2A, custom implementations).

Key Concepts

What Are Sub-Agents?

Sub-agents are specialized AI assistants that your main agent can delegate tasks to. Each operates with:

  • Dedicated context window - Separate from the main conversation, preventing context pollution
  • Custom system prompt - Tailored instructions for specific domains (exploration, planning, custom expertise)
  • Configurable tool access - Limited to only the necessary capabilities for their role
  • Independent execution - Works autonomously and returns results to the parent agent

Extensible Architecture

The system is built on pluggable abstractions that allow:

  • Multiple subagent types - Claude-based, A2A protocol, or custom implementations
  • Pluggable resolvers - Load subagent definitions from various sources (files, classpath, remote)
  • Pluggable executors - Execute subagents using different backends (Spring AI ChatClient, A2A, etc.)
  • Multi-model support - Different ChatClient configurations per model (sonnet, opus, haiku)

Benefits

Benefit Details
Context Preservation Keeps main conversation focused on high-level objectives while sub-agents handle details
Specialized Expertise Fine-tuned for specific domains with higher success rates
Extensibility Add new subagent types by implementing simple interfaces
Multi-Model Route to different models based on subagent configuration
Flexible Permissions Granular control over tool access per sub-agent
Async Execution Run sub-agents in background for long-running tasks

Architecture

Module Structure

The subagent system is split across three modules:

spring-ai-agent-utils-common      # Core SPI: SubagentDefinition, SubagentResolver,
                                   #   SubagentExecutor, SubagentType, SubagentReference, TaskCall

spring-ai-agent-utils              # TaskTool, TaskOutputTool, ClaudeSubagentType,
                                   #   ClaudeSubagentExecutor, ClaudeSubagentResolver

spring-ai-agent-utils-a2a          # A2ASubagentDefinition, A2ASubagentResolver,
                                   #   A2ASubagentExecutor (optional dependency)

Core Components

TaskTool.builder()
    ├── SubagentReference[] ──────► (URIs pointing to subagent definitions)
    ├── SubagentType[] ───────────► (bundles resolver + executor per kind)
    │   └── SubagentType
    │       ├── SubagentResolver ─► (parse references into SubagentDefinition)
    │       └── SubagentExecutor ─► (execute subagents)
    └── ClaudeSubagentType.builder()
        ├── ClaudeSubagentResolver  (auto-registered)
        └── ClaudeSubagentExecutor
            └── Map<String, ChatClient.Builder> (model routing)

Abstractions

These interfaces live in the spring-ai-agent-utils-common module and are shared across all subagent implementations.

Interface Purpose
SubagentDefinition Core interface representing a subagent with name, description, kind, and reference
SubagentReference Record holding URI, kind, and metadata for locating a subagent definition
SubagentResolver Strategy for resolving SubagentReferenceSubagentDefinition
SubagentExecutor Strategy for executing a TaskCall against a SubagentDefinition
SubagentType Record bundling a SubagentResolver and SubagentExecutor together for a specific kind
TaskCall Input record describing the task to execute (prompt, subagent type, model, etc.)

Main Components

  1. TaskTool - Launches and manages sub-agents using pluggable resolvers and executors
  2. TaskOutputTool - Retrieves results from background sub-agents
  3. ClaudeSubagentType - Convenience builder for configuring the Claude subagent type with tools, skills, and model routing

Built-in Sub-Agents

When a ClaudeSubagentType is registered, TaskTool automatically adds four built-in Claude subagents.

General-Purpose Sub-Agent

A versatile agent for complex research and execution tasks:

---
name: general-purpose
description: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks
---

Capabilities: - Searching code and configurations across large codebases - Analyzing multiple files to understand system architecture - Performing multi-step research tasks - Full read/write access to tools

Explore Sub-Agent

A fast, read-only agent specialized for codebase exploration:

---
name: Explore
description: Fast agent specialized for exploring codebases. Use for finding files, searching code, or answering questions about the codebase
---

Capabilities: - Rapidly finding files using glob patterns - Searching code with regex patterns - Reading and analyzing file contents - Strictly read-only - no file modifications

Thoroughness Levels: - quick - Basic searches, minimal exploration - medium - Moderate exploration, multiple search strategies - very thorough - Comprehensive analysis across multiple locations

Plan Sub-Agent

A software architect agent specialized for designing implementation plans:

---
name: Plan
description: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task.
---

Capabilities: - Exploring codebases to understand existing patterns and architecture - Identifying critical files that need modification or creation - Designing step-by-step implementation approaches - Considering architectural trade-offs and presenting alternatives - Surfacing potential risks or challenges early - Strictly read-only - planning only, no file modifications

Bash Sub-Agent

A command execution specialist for terminal operations:

---
name: Bash
description: Command execution specialist for running bash commands. Use this for git operations, command execution, and other terminal tasks.
---

Capabilities: - Git operations (commits, branches, merges, status checks) - Build and test commands (npm, cargo, make, pytest) - Package management operations - System operations and environment setup - DevOps tasks (Docker, deployment scripts)

Guidelines: - Follows git safety protocols (no force push to main, no destructive commands without confirmation) - Limited to Bash tool only - file reading/editing handled by parent agent

Quick Start

Basic Setup (Claude subagents only)

import org.springaicommunity.agent.tools.task.TaskTool;
import org.springaicommunity.agent.tools.task.claude.ClaudeSubagentType;

@Configuration
public class AgentConfig {

    @Bean
    CommandLineRunner demo(ChatClient.Builder chatClientBuilder) {
        return args -> {
            // Configure Task tool with Claude subagents
            var taskTool = TaskTool.builder()
                .subagentTypes(ClaudeSubagentType.builder()
                    .chatClientBuilder("default", chatClientBuilder)
                    .build())
                .build();

            // Build main chat client with Task tool
            ChatClient chatClient = chatClientBuilder
                .defaultToolCallbacks(taskTool)
                .build();

            // Use naturally - agent will delegate to sub-agents
            String response = chatClient
                .prompt("Explore the authentication module and explain how it works")
                .call()
                .content();
        };
    }
}

Configuration

Using ClaudeSubagentType

ClaudeSubagentType is a convenience builder that creates a SubagentType bundling the Claude resolver and executor with default tools (Grep, Glob, Shell, FileSystem, WebFetch, TodoWrite) and optional extras (BraveWebSearch, Skills):

SubagentType claudeType = ClaudeSubagentType.builder()
    // Required: At least one ChatClient builder with key "default"
    .chatClientBuilder("default", chatClientBuilder)

    // Optional: Additional model-specific ChatClient builders
    .chatClientBuilder("opus", opusChatClientBuilder)
    .chatClientBuilder("haiku", haikuChatClientBuilder)

    // Optional: Skills for sub-agents (preloaded into system prompt)
    .skillsResources(skillResources)

    // Optional: For web search
    .braveApiKey(System.getenv("BRAVE_API_KEY"))

    .build();

Using TaskTool.builder()

Register one or more SubagentType instances, plus any additional subagent references:

ToolCallback taskTool = TaskTool.builder()
    // Register Claude subagent type (includes built-in subagents)
    .subagentTypes(claudeType)

    // Optional: Custom subagent references (for Claude-based subagents)
    .subagentReferences(ClaudeSubagentReferences.fromRootDirectory("/path/to/agents"))

    // Optional: Custom task storage
    .taskRepository(new DefaultTaskRepository())

    .build();

Combining Claude and A2A Subagents

import org.springaicommunity.agent.common.task.subagent.SubagentReference;
import org.springaicommunity.agent.common.task.subagent.SubagentType;
import org.springaicommunity.agent.subagent.a2a.A2ASubagentDefinition;
import org.springaicommunity.agent.subagent.a2a.A2ASubagentExecutor;
import org.springaicommunity.agent.subagent.a2a.A2ASubagentResolver;

ToolCallback taskTool = TaskTool.builder()
    // Local Claude subagents
    .subagentTypes(ClaudeSubagentType.builder()
        .chatClientBuilder("default", chatClientBuilder)
        .skillsResources(skillPaths)
        .braveApiKey(braveApiKey)
        .build())

    // Remote A2A subagent
    .subagentReferences(new SubagentReference("http://localhost:10001/myagent", A2ASubagentDefinition.KIND))
    .subagentTypes(new SubagentType(new A2ASubagentResolver(), new A2ASubagentExecutor()))

    .build();

Multi-Provider and Multi-Model Configuration

Route subagents to different LLM providers and models based on their frontmatter model field. The chatClientBuilder keys represent named providers — these can be any Spring AI supported LLM provider (Anthropic, OpenAI, Ollama, etc.), not just Claude variants:

SubagentType claudeType = ClaudeSubagentType.builder()
    .chatClientBuilder("default", anthropicBuilder)  // Fallback provider
    .chatClientBuilder("openai", openAiBuilder)      // OpenAI provider
    .chatClientBuilder("ollama", ollamaBuilder)      // Ollama provider
    .build();

The model frontmatter field supports two formats:

  • model — Uses the default provider with the specified model. Short aliases (opus, haiku, sonnet) are mapped to their full Claude model identifiers. The model can also be the full model name of any provider (e.g., gpt-4o).
  • provider:model — Uses the named provider with the specified model (e.g., openai:gpt-4o, ollama:llama3).

If the specified provider is not found in the builder map, or if no model is specified, the default builder is used as a fallback.

Loading Subagent References

From Directories

import org.springaicommunity.agent.tools.task.claude.ClaudeSubagentReferences;

List<SubagentReference> refs = ClaudeSubagentReferences.fromRootDirectory("/path/to/agents");

From Spring Resources

@Value("${agent.tasks.paths}")
List<Resource> agentPaths;

List<SubagentReference> refs = ClaudeSubagentReferences.fromResources(agentPaths);

Multiple Sources

List<SubagentReference> refs = ClaudeSubagentReferences.fromRootDirectories(List.of(
    "src/main/resources/agents",
    System.getProperty("user.home") + "/.claude/agents"
));

Creating Custom Sub-Agents

File Structure

Custom sub-agents are defined as Markdown files with YAML front matter:

project-root/
├── .claude/
│   └── agents/
│       ├── code-reviewer.md
│       ├── test-runner.md
│       └── spring-ai-expert.md

Sub-Agent File Format

---
name: your-sub-agent-name
description: When and how to use this subagent. Include trigger keywords and example scenarios.
tools: Read, Edit, Grep, Glob       # Optional: inherits all if omitted
disallowedTools: Bash, Shell        # Optional: explicitly deny specific tools
model: sonnet                        # Optional: sonnet, opus, haiku
skills: ai-tutor                     # Optional: skills to preload
permissionMode: default              # Optional: permission handling mode
---

# Your Sub-Agent's System Prompt

You are a [role description]. You specialize in [domain].

**Your Primary Responsibilities:**
1. [Responsibility 1]
2. [Responsibility 2]

**Guidelines:**
- [Guideline 1]
- [Guideline 2]

Configuration Fields

Field Required Description
name Yes Unique identifier (lowercase with hyphens)
description Yes Natural language purpose description with usage examples
tools No Comma-separated list of allowed tool names (inherits all if omitted)
disallowedTools No Comma-separated list of tools to explicitly deny
model No Model specification: a short alias (sonnet, opus, haiku), a full model name (gpt-4o), or provider:model (openai:gpt-4o)
skills No Comma-separated list of skill names to preload into the subagent's system prompt
permissionMode No Permission handling mode (default: default)

Example: Code Reviewer Sub-Agent

---
name: code-reviewer
description: Expert code reviewer. Use proactively after writing or modifying code. Focuses on code quality, security, and best practices.
tools: Read, Grep, Glob, Bash
disallowedTools: Edit, Write
model: sonnet
---

You are a senior code reviewer with expertise in software quality and security.

**When Invoked:**
1. Run `git diff` to see recent changes
2. Focus analysis on modified files
3. Check context of surrounding code

**Review Checklist:**
- Code clarity and readability
- Proper naming conventions
- No code duplication
- Comprehensive error handling
- Security (no exposed secrets, SQL injection, XSS)

**Output Format:**
Provide clear, actionable feedback with file references and line numbers.

Extending the System

Creating a Custom Subagent Type

To add support for a new subagent protocol, implement three interfaces from spring-ai-agent-utils-common and register them as a SubagentType. See the Subagent Framework documentation for the full SPI reference, or the spring-ai-agent-utils-a2a module for a complete A2A protocol implementation.

// 1. Implement SubagentDefinition, SubagentResolver, SubagentExecutor
// 2. Bundle them into a SubagentType
SubagentType myType = new SubagentType(new MyResolver(), new MyExecutor());

// 3. Register with TaskTool
TaskTool.builder()
    .subagentReferences(new SubagentReference("my://agent-1", "MY_KIND"))
    .subagentTypes(myType)
    .build();

Usage Patterns

Automatic Delegation

The main agent automatically delegates to sub-agents based on their descriptions:

String response = chatClient
    .prompt("How does authentication work in this codebase?")
    .call()
    .content();
// Main agent recognizes this as exploration task and delegates to Explore sub-agent

Explicit Invocation

Users can explicitly request specific sub-agents:

String response = chatClient
    .prompt("Use the code-reviewer subagent to review my recent changes")
    .call()
    .content();

Background Execution

Long-running tasks can execute in the background:

// Main agent can launch background sub-agents
String response = chatClient
    .prompt("Run the test-runner in the background and let me know when tests complete")
    .call()
    .content();
// Returns task_id for later retrieval

// Later, retrieve results via TaskOutputTool
String results = chatClient
    .prompt("Get the results from task task_12345")
    .call()
    .content();

Tool Parameters

When the main agent calls TaskTool, it uses these parameters (defined in TaskCall):

public record TaskCall(
    String description,        // Short 3-5 word description
    String prompt,            // The task for the sub-agent
    String subagent_type,     // Which sub-agent to use
    String model,             // Optional: override model
    String resume,            // Optional: resume previous sub-agent
    Boolean run_in_background // Optional: run async
) {}

Background Task Management

TaskRepository

The TaskRepository manages background task execution:

public interface TaskRepository {
    BackgroundTask putTask(String taskId, Supplier<String> taskExecution);
    BackgroundTask getTasks(String taskId);
}

Default Implementation

import org.springaicommunity.agent.tools.task.repository.DefaultTaskRepository;

TaskRepository repository = new DefaultTaskRepository();

TaskTool.builder() uses DefaultTaskRepository by default. Override with .taskRepository(...) for custom implementations.

Custom Implementation

For distributed systems or persistence:

@Component
public class RedisTaskRepository implements TaskRepository {

    @Override
    public BackgroundTask putTask(String taskId, Supplier<String> taskExecution) {
        // Store in Redis, execute via message queue, etc.
    }

    @Override
    public BackgroundTask getTasks(String taskId) {
        return redisTemplate.opsForValue().get(taskId);
    }
}

Complete Example

@SpringBootApplication
public class Application {

    @Bean
    CommandLineRunner demo(
            ChatClient.Builder chatClientBuilder,
            @Value("${agent.skills.paths}") List<Resource> skillPaths,
            @Value("${BRAVE_API_KEY:#{null}}") String braveApiKey) {

        return args -> {
            // Configure Task tool with Claude subagents
            var taskTool = TaskTool.builder()
                .subagentTypes(ClaudeSubagentType.builder()
                    .chatClientBuilder("default",
                        chatClientBuilder.clone().defaultAdvisors(new MyLoggingAdvisor(0, "[TASK]")))
                    .skillsResources(skillPaths)
                    .braveApiKey(braveApiKey)
                    .build())
                .build();

            // Build main chat client
            ChatClient chatClient = chatClientBuilder
                .defaultToolCallbacks(taskTool)
                .defaultTools(
                    FileSystemTools.builder().build(),
                    GrepTool.builder().build(),
                    GlobTool.builder().build(),
                    ShellTools.builder().build(),
                    TodoWriteTool.builder().build()
                )
                .defaultAdvisors(
                    ToolCallAdvisor.builder()
                        .conversationHistoryEnabled(false)
                        .build(),
                    MessageChatMemoryAdvisor.builder(
                        MessageWindowChatMemory.builder().maxMessages(500).build()
                    ).build()
                )
                .build();

            // Interactive chat loop
            try (Scanner scanner = new Scanner(System.in)) {
                while (true) {
                    System.out.print("\nUSER: ");
                    String response = chatClient
                        .prompt(scanner.nextLine())
                        .call()
                        .content();
                    System.out.println("\nASSISTANT: " + response);
                }
            }
        };
    }
}

Best Practices

1. Design Focused Sub-Agents

Each sub-agent should have a single, clear responsibility.

2. Use disallowedTools for Safety

Explicitly deny dangerous tools for read-only agents:

---
name: explorer
tools: Read, Grep, Glob
disallowedTools: Edit, Write, Bash, Shell
---

3. Leverage Multi-Provider and Multi-Model Routing

Use faster/cheaper models for simple tasks:

---
name: quick-search
model: haiku
---

Use more capable models for complex analysis:

---
name: deep-analyzer
model: opus
---

Route to a different provider entirely:

---
name: local-searcher
model: ollama:llama3
---

4. Register Custom Subagent Types

Extend the system for new protocols like A2A rather than modifying core code.

5. Version Control Custom Agents

git add .claude/agents/
git commit -m "Add code-reviewer and test-runner sub-agents"

API Reference

TaskTool

public class TaskTool {

    public static Builder builder() { ... }

    public static class Builder {
        Builder subagentReferences(List<SubagentReference> refs);
        Builder subagentReferences(SubagentReference... refs);
        Builder subagentTypes(List<SubagentType> types);
        Builder subagentTypes(SubagentType... types);
        Builder taskRepository(TaskRepository taskRepository);
        Builder taskDescriptionTemplate(String template);

        ToolCallback build();
    }
}

ClaudeSubagentType

public class ClaudeSubagentType {

    public static Builder builder() { ... }

    public static class Builder {
        Builder chatClientBuilder(String modelId, ChatClient.Builder builder);
        Builder chatClientBuilders(Map<String, ChatClient.Builder> builders);
        Builder skillsResources(List<Resource> resources);
        Builder skillsResource(Resource resource);
        Builder skillsDirectories(List<String> dirs);
        Builder skillsDirectories(String dir);
        Builder braveApiKey(String apiKey);

        SubagentType build();  // Returns SubagentType (resolver + executor pair)
    }
}

Subagent Interfaces (from spring-ai-agent-utils-common)

public interface SubagentDefinition {
    String getName();
    String getDescription();
    String getKind();
    SubagentReference getReference();
    default String toSubagentRegistrations() { ... }
}

public interface SubagentResolver {
    boolean canResolve(SubagentReference ref);
    SubagentDefinition resolve(SubagentReference ref);
}

public interface SubagentExecutor {
    String getKind();
    String execute(TaskCall taskCall, SubagentDefinition subagent);
}

public record SubagentReference(String uri, String kind, Map<String, String> metadata) {
    public SubagentReference(String uri, String kind) { this(uri, kind, Map.of()); }
}

public record SubagentType(SubagentResolver resolver, SubagentExecutor executor) {
    public String kind() { return executor.getKind(); }
}

public record TaskCall(
    String description, String prompt, String subagent_type,
    String model, String resume, Boolean run_in_background
) {}

ClaudeSubagentDefinition

The built-in Claude subagent definition uses the kind constant "CLAUDE":

public class ClaudeSubagentDefinition implements SubagentDefinition {
    public static final String KIND = "CLAUDE";

    // Additional Claude-specific methods:
    public String getModel();           // Model override (sonnet, opus, haiku)
    public List<String> tools();        // Allowed tool names
    public List<String> disallowedTools(); // Tools to deny
    public List<String> skills();       // Skills to preload
    public String permissionMode();     // Permission handling mode
    public String getContent();         // System prompt content
}

ClaudeSubagentReferences

public class ClaudeSubagentReferences {
    static List<SubagentReference> fromRootDirectory(String rootDirectory);
    static List<SubagentReference> fromRootDirectories(List<String> rootDirectories);
    static List<SubagentReference> fromResource(Resource resource);
    static List<SubagentReference> fromResources(List<Resource> resources);
    static List<SubagentReference> fromResources(Resource... resources);
}

References