Skip to main content

What is AgentLoop?

AgentLoop is a ready-to-use agent that packages everything you need to run an autonomous coding task: tools, system prompt, turn limits, cost limits, session memory, and observability. Under the hood it delegates to Spring AI’s ChatClient + AgentLoopAdvisor for the actual tool-calling loop. Think of it as “batteries included” — one class, one builder call, working agent:
AgentLoop agent = AgentLoop.builder()
    .config(AgentLoop.Config.builder()
        .maxTurns(10)
        .workingDirectory(Path.of("/my/project"))
        .build())
    .model(chatModel)
    .build();

AgentLoop.Result result = agent.run("Add unit tests for the UserService class");

System.out.println(result.status());    // COMPLETED
System.out.println(result.output());    // "I've added 5 tests..."
System.out.println(result.totalTokens()); // 12400

Built-in Tools

AgentLoop ships with a full set of SWE tools:
ToolSourceDescription
Bashworkflow-toolsShell commands (git, mvn, docker)
Read / Write / Edit / LSspring-ai-agent-utilsFile operations
Globspring-ai-agent-utilsFind files by pattern
Grepspring-ai-agent-utilsSearch file contents
TodoWritespring-ai-agent-utilsTask planning and tracking
Taskspring-ai-agent-utilsDelegate to sub-agents
SubmitAgentLoop (internal)Submit final answer, terminates the loop

Configuration

AgentLoop.Config is a Java record with a builder:
AgentLoop.Config config = AgentLoop.Config.builder()
    .maxTurns(20)           // default: 20
    .costLimit(1.0)         // default: $1.00
    .commandTimeout(Duration.ofSeconds(30))  // default: 30s
    .workingDirectory(Path.of("."))           // default: user.dir
    .systemPrompt("Custom prompt...")         // default: built-in SWE prompt
    .build();
ParameterDefaultDescription
maxTurns20Max LLM invocations before termination
costLimit1.0Max estimated cost in USD
commandTimeout30sTimeout for shell commands
workingDirectoryuser.dirRoot directory for file/shell tools
systemPromptBuilt-in SWE promptSystem instructions for the agent
Configs are immutable records. Use toBuilder() to derive variants:
AgentLoop.Config extended = config.toBuilder()
    .maxTurns(50)
    .build();

Session Memory

Enable session memory to preserve conversation context across multiple calls:
AgentLoop agent = AgentLoop.builder()
    .config(config)
    .model(chatModel)
    .sessionMemory()  // default in-memory implementation
    .build();

agent.run("Read the UserService class and summarize it");
agent.run("Now add tests for the methods you found");  // remembers context
agent.clearSession();  // reset when done
You can also pass a custom ChatMemory implementation:
.sessionMemory(myJdbcChatMemory)

Interactive Mode

For TUI/CLI applications, interactive mode enables human-in-the-loop via AskUserQuestionTool:
AgentCallback callback = new AgentCallback() {
    @Override
    public void onThinking() { System.out.print("Thinking..."); }

    @Override
    public String onQuestion(List<String> questions) {
        // Present questions to user, return their answer
        return scanner.nextLine();
    }

    @Override
    public void onComplete() { System.out.println("Done."); }
};

AgentLoop agent = AgentLoop.builder()
    .config(config)
    .model(chatModel)
    .interactive(true)
    .agentCallback(callback)
    .sessionMemory()
    .build();

agent.chat("Review this PR and ask me about anything unclear", callback);

Result

AgentLoop.Result is a record with execution details:
AgentLoop.Result result = agent.run(task);

result.status();           // COMPLETED, TURN_LIMIT_REACHED, TIMEOUT, STUCK, etc.
result.output();           // Agent's final text output
result.turnsCompleted();   // Number of LLM invocations
result.toolCallsExecuted();// Total tool calls across all turns
result.totalTokens();      // Total tokens consumed
result.estimatedCost();    // Estimated cost in USD

result.isSuccess();        // true if COMPLETED
result.isFailure();        // true if FAILED

Observability

AgentLoop wires Micrometer observations for tool call tracking. You can also provide a custom ToolCallListener:
AgentLoop agent = AgentLoop.builder()
    .config(config)
    .model(chatModel)
    .toolCallListener(new LoggingToolCallListener())
    .build();

Relationship to LoopPattern

AgentLoop is not a LoopPattern implementation. It’s a higher-level construct:
  • LoopPattern — the abstract interface for loop algorithms (TurnLimitedLoop, EvaluatorOptimizerLoop, StateMachineLoop). Lives in workflow-api.
  • AgentLoop — a concrete, runnable agent that uses AgentLoopAdvisor internally. Lives in workflow-agents.
AgentLoop is what you use when you want to run an agent. LoopPattern is what you implement when you want to create a new loop algorithm.

Maven Coordinates

<dependency>
    <groupId>io.github.markpollack</groupId>
    <artifactId>workflow-agents</artifactId>
    <version>0.3.0</version>
</dependency>