AgentClient API

The AgentClient offers a fluent API for communicating with autonomous agents. It provides a familiar interface for Spring developers, following the same patterns as Spring AI’s ChatClient.

The fluent API allows you to build up goal requests that are passed to autonomous agents. Unlike traditional chat models that process conversational prompts, autonomous agents execute goals that can involve reading files, running commands, and making code changes.

1. Creating an AgentClient

The AgentClient is created using an AgentModel instance. Here is a simple example showing the complete flow:

import org.springaicommunity.agents.client.AgentClient;
import org.springaicommunity.agents.client.AgentClientResponse;
import org.springaicommunity.agents.claudecode.ClaudeCodeAgentModel;
import org.springaicommunity.agents.claudecode.ClaudeCodeAgentOptions;
import org.springaicommunity.agents.claudecode.sdk.ClaudeCodeClient;

public class HelloAgentWorld {
    public static void main(String[] args) {
        // 1. Create the Claude Code client
        ClaudeCodeClient claudeClient = ClaudeCodeClient.create();

        // 2. Configure agent options
        ClaudeCodeAgentOptions options = ClaudeCodeAgentOptions.builder()
            .model("claude-sonnet-4-0")
            .yolo(true)
            .build();

        // 3. Create the agent model
        ClaudeCodeAgentModel agentModel = new ClaudeCodeAgentModel(claudeClient, options);

        // 4. Create AgentClient
        AgentClient agentClient = AgentClient.create(agentModel);

        // 5. Execute a goal
        AgentClientResponse response = agentClient.run(
            "Create a simple Hello World Java class"
        );

        // 6. Use the result
        System.out.println("Goal completed: " + response.isSuccessful());
        System.out.println("Result: " + response.getResult());
    }
}

In this example:

  1. We create a ClaudeCodeClient that manages communication with the Claude CLI

  2. We configure options like the model to use and whether to allow changes (yolo)

  3. We wrap the client in a ClaudeCodeAgentModel for Spring AI integration

  4. We create an AgentClient using the agent model

  5. We run a simple goal using the .run() method

  6. We access the results and check if the goal was successful

2. Basic Usage

2.1. Simple Goal Execution

The simplest way to run a goal:

// Assuming you have an AgentClient created as shown above
AgentClientResponse response = agentClient.run("Create a Hello World Java class");

System.out.println("Result: " + response.getResult());
System.out.println("Success: " + response.isSuccessful());

2.2. Fluent API Configuration

For more control over execution:

AgentClientResponse response = agentClient
    .goal("Refactor the UserService class to use dependency injection")
    .workingDirectory("/path/to/project")
    .yolo(true)
    .run();

3. Configuration Options

3.1. Working Directory

Set the directory where the agent operates:

// Using string path
agentClient.goal("Generate unit tests")
    .workingDirectory("/home/user/my-project")
    .run();

// Using Path object
Path projectPath = Paths.get(System.getProperty("user.dir"));
agentClient.goal("Generate unit tests")
    .workingDirectory(projectPath)
    .run();

3.2. YOLO Mode

Enable or disable the agent’s ability to make changes without confirmation:

// Enable YOLO mode for development
agentClient.goal("Fix all compilation errors")
    .yolo(true)
    .run();

// Disable for safe analysis
agentClient.goal("Analyze code quality issues")
    .yolo(false)
    .run();

3.3. Agent-Specific Options

Configure agent-specific behavior:

ClaudeCodeAgentOptions options = ClaudeCodeAgentOptions.builder()
    .model("claude-sonnet-4-0")
    .maxTokens(8192)
    .timeout(Duration.ofMinutes(10))
    .build();

agentClient.goal("Generate comprehensive documentation")
    .options(options)
    .run();

4. Response Handling

4.1. AgentClientResponse

The response object provides access to results and metadata:

AgentClientResponse response = agentClient.run("Generate a README file");

// Check if goal completed successfully
if (response.isSuccessful()) {
    String result = response.getResult();
    System.out.println("Agent completed: " + result);
} else {
    System.err.println("Goal failed: " + response.getResult());
}

// Access metadata
AgentResponseMetadata metadata = response.getMetadata();
Duration duration = metadata.getDuration();
String model = metadata.getModel();

4.2. Error Handling

Handle various error conditions:

try {
    AgentClientResponse response = agentClient.run("Complex refactoring goal");

    if (!response.isSuccessful()) {
        // Goal completed but failed
        System.err.println("Agent reported failure: " + response.getResult());
    }

} catch (AgentExecutionException e) {
    // Agent process failed to start or crashed
    System.err.println("Execution error: " + e.getMessage());

} catch (AgentTimeoutException e) {
    // Goal exceeded timeout
    System.err.println("Goal timed out after: " + e.getTimeout());
}

5. Spring Boot Integration

5.1. Dependency Injection

Configure AgentClient as a Spring bean:

@Configuration
public class AgentConfiguration {

    @Bean
    public ClaudeCodeClient claudeCodeClient() {
        return ClaudeCodeClient.create();
    }

    @Bean
    public ClaudeCodeAgentModel claudeCodeAgentModel(ClaudeCodeClient client) {
        ClaudeCodeAgentOptions options = ClaudeCodeAgentOptions.builder()
            .model("claude-sonnet-4-0")
            .yolo(false) // Safe for production
            .build();
        return new ClaudeCodeAgentModel(client, options);
    }

    @Bean
    public AgentClient agentClient(ClaudeCodeAgentModel agentModel) {
        return AgentClient.create(agentModel);
    }
}

5.2. Controller Usage

Use in REST controllers:

@RestController
public class DevelopmentController {

    private final AgentClient agentClient;

    public DevelopmentController(AgentClient agentClient) {
        this.agentClient = agentClient;
    }

    @PostMapping("/generate-tests")
    public ResponseEntity<String> generateTests(@RequestBody GenerateTestsRequest request) {
        try {
            AgentClientResponse response = agentClient
                .goal("Generate unit tests for " + request.getClassName())
                .workingDirectory(request.getProjectPath())
                .yolo(false) // Safe mode for production
                .run();

            if (response.isSuccessful()) {
                return ResponseEntity.ok(response.getResult());
            } else {
                return ResponseEntity.badRequest().body(response.getResult());
            }

        } catch (Exception e) {
            return ResponseEntity.status(500).body("Goal execution failed: " + e.getMessage());
        }
    }
}

6. Best Practices

6.1. Goal Formulation

Write clear, specific goals:

// Good: Specific and actionable
client.run("Add input validation to the UserController.createUser() method");

// Poor: Vague and ambiguous
client.run("Make the code better");

6.2. Working Directory Management

Always set appropriate working directories:

// For multi-module projects
client.goal("Generate integration tests")
    .workingDirectory(projectRoot.resolve("service-module"))
    .run();

6.3. Resource Management

AgentClient instances are thread-safe and can be reused:

@Component
public class CodeGenerationService {

    private final AgentClient agentClient;

    public CodeGenerationService(AgentClient agentClient) {
        this.agentClient = agentClient;
    }

    // Reuse the same client instance across methods
    public String generateController(String entityName) {
        return agentClient.run("Generate REST controller for " + entityName).getResult();
    }

    public String generateTests(String className) {
        return agentClient.run("Generate unit tests for " + className).getResult();
    }
}

7. Advanced Features

7.1. Timeout Configuration

Configure execution timeouts:

AgentOptions options = AgentOptions.builder()
    .timeout(Duration.ofMinutes(15)) // Long-running refactoring goal
    .build();

client.goal("Refactor entire codebase to use reactive patterns")
    .options(options)
    .run();

7.2. Result Streaming

For long-running tasks, some agents support progress updates:

// Note: Streaming support varies by agent implementation
AgentClientResponse response = client
    .goal("Generate comprehensive test suite")
    .options(AgentOptions.builder().streaming(true).build())
    .run();

// Implementation-specific streaming access
if (response instanceof StreamingAgentResponse streaming) {
    streaming.getProgressUpdates().forEach(System.out::println);
}

8. Next Steps