<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ivannavas</title>
    <description>The latest articles on DEV Community by ivannavas (@ivannavas).</description>
    <link>https://dev.to/ivannavas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3994453%2F12885fd6-3ef5-4a7d-9a57-42b32dab060b.jpg</url>
      <title>DEV Community: ivannavas</title>
      <link>https://dev.to/ivannavas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ivannavas"/>
    <language>en</language>
    <item>
      <title>Sprout: a Spring-style, Spring-compatible framework for building AI tooling in Java</title>
      <dc:creator>ivannavas</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:32:00 +0000</pubDate>
      <link>https://dev.to/ivannavas/sprout-a-spring-style-spring-compatible-framework-for-building-ai-tooling-in-java-f1j</link>
      <guid>https://dev.to/ivannavas/sprout-a-spring-style-spring-compatible-framework-for-building-ai-tooling-in-java-f1j</guid>
      <description>&lt;p&gt;Your backend already exists. It runs on the JVM, it has services, configuration and tests, and increasingly it needs to &lt;em&gt;do AI things&lt;/em&gt; — call a model, expose some tools, run an agent loop. &lt;strong&gt;Sprout&lt;/strong&gt; lets you add those capabilities right where your code already lives, as ordinary beans that drop into the dependency injection, configuration and tests you already use.&lt;/p&gt;

&lt;p&gt;Sprout is a &lt;strong&gt;foundation for building AI tooling in Java&lt;/strong&gt; — a simple, modular, Spring-style and Spring-compatible IoC container with a provider-agnostic model layer, automatic tool/JSON-schema plumbing and Model Context Protocol support, all behind one open extension SPI. A cohesive base like this is still uncommon in the Java ecosystem, where most options are thin API clients. Sprout is meant to be the &lt;strong&gt;cornerstone you keep building AI tooling on&lt;/strong&gt; — and, with the Spring Boot starter, to fit straight into the backend you already maintain.&lt;/p&gt;

&lt;p&gt;It's &lt;a href="https://central.sonatype.com/namespace/io.github.ivannavas" rel="noopener noreferrer"&gt;&lt;code&gt;1.1.0&lt;/code&gt; on Maven Central&lt;/a&gt; and &lt;a href="https://github.com/ivannavas/sprout-ai-framework" rel="noopener noreferrer"&gt;Apache-2.0 on GitHub&lt;/a&gt;. Here's the tour.&lt;/p&gt;

&lt;h2&gt;
  
  
  An agent is just an annotated class
&lt;/h2&gt;

&lt;p&gt;The flagship capability today is &lt;strong&gt;agents&lt;/strong&gt;. An agent is an annotated class that extends &lt;code&gt;AgentExecutor&lt;/code&gt; — declare a model, write &lt;code&gt;@Tool&lt;/code&gt; methods, and Sprout runs the model/tool loop for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Agent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnthropicModelExecutor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are a helpful assistant."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Look up the weather forecast for a city"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Sunny, 25°C in "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;SproutContainer&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SproutApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;AgentExecutor&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSingleton&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"weatherAgentExecutor"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"session-1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"What's the weather in Madrid?"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the symmetry: a &lt;code&gt;@Model&lt;/code&gt; class extends &lt;code&gt;ModelExecutor&lt;/code&gt;, and a &lt;code&gt;@Agent&lt;/code&gt; class extends &lt;code&gt;AgentExecutor&lt;/code&gt; — the agent &lt;em&gt;is&lt;/em&gt; its own executor. The executor loops: it sends the conversation to the model, dispatches any tool calls, feeds the results back, and repeats until the model produces a final answer or &lt;code&gt;maxIterations&lt;/code&gt; is reached.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@Tool&lt;/code&gt; parameter JSON-Schema is &lt;strong&gt;generated automatically from the method signature&lt;/strong&gt; (compile with &lt;code&gt;-parameters&lt;/code&gt;) — primitives, enums and collections map across, and every parameter is required by default. Add &lt;code&gt;@ToolParam&lt;/code&gt; to describe a parameter or mark it optional; a good description noticeably improves how reliably the model fills the argument in. Tools are ordinary methods backed by the services you already have.&lt;/p&gt;

&lt;p&gt;Conversation history is persisted through an &lt;code&gt;AbstractConversationStore&lt;/code&gt; — a thread-safe in-memory one by default; swap in your own to persist it. And to stream a run instead of blocking, call &lt;code&gt;executeStream(conversationId, prompt, listener)&lt;/code&gt;: assistant tokens, tool calls and the final response arrive through a &lt;code&gt;StreamListener&lt;/code&gt; as they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real IoC container, not a bag of clients
&lt;/h2&gt;

&lt;p&gt;Everything above sits on a Spring-style container. You annotate classes and let it discover, instantiate and wire them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Component&lt;/code&gt; / &lt;code&gt;@Service&lt;/code&gt; mark managed singletons.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Autowired&lt;/code&gt; injects by type into fields &lt;strong&gt;or through a constructor&lt;/strong&gt;; &lt;code&gt;@Qualifier&lt;/code&gt; selects a specific bean by name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@PostConstruct&lt;/code&gt; runs initialisation once dependencies are injected.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Value&lt;/code&gt; and &lt;code&gt;@Configuration&lt;/code&gt;/&lt;code&gt;@ConfigurationProperty&lt;/code&gt; bind configuration, with Spring-style &lt;code&gt;${key:default}&lt;/code&gt; placeholders resolved against &lt;code&gt;sprout.properties&lt;/code&gt;, system properties and environment variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The richer stereotypes — &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Model&lt;/code&gt;, &lt;code&gt;@Agent&lt;/code&gt;, &lt;code&gt;@ConversationStore&lt;/code&gt;, &lt;code&gt;@Mcp&lt;/code&gt; — are all meta-annotated with &lt;code&gt;@Component&lt;/code&gt;. So &lt;strong&gt;a model and an agent are ordinary managed singletons too&lt;/strong&gt;: the container builds and wires them, and you inject them anywhere exactly like any other bean. They are not a separate kind of object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provider-agnostic models
&lt;/h2&gt;

&lt;p&gt;A model is a &lt;code&gt;ModelExecutor&lt;/code&gt; subclass annotated with &lt;code&gt;@Model&lt;/code&gt;. &lt;code&gt;sprout-anthropic&lt;/code&gt; and &lt;code&gt;sprout-openai&lt;/code&gt; ship implementations; you can add your own — including offline, deterministic stubs for tests — by extending &lt;code&gt;ModelExecutor&lt;/code&gt; and implementing &lt;code&gt;chat(ModelRequest)&lt;/code&gt;. Swapping providers doesn't touch your agent code: the agent depends on a &lt;code&gt;ModelExecutor&lt;/code&gt;, not on a vendor SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP, both sides
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;sprout-mcp&lt;/code&gt; on the classpath, an &lt;code&gt;@Mcp&lt;/code&gt; bean's &lt;code&gt;@Tool&lt;/code&gt; methods are &lt;strong&gt;published over the Model Context Protocol&lt;/strong&gt; — no agent required. And an &lt;code&gt;@Agent&lt;/code&gt; can &lt;strong&gt;connect to remote MCP servers&lt;/strong&gt; with &lt;code&gt;@UseMcp&lt;/code&gt; and use their tools as if they were its own. The same &lt;code&gt;@Tool&lt;/code&gt; methods that power an agent can be exposed as a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-agent orchestration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sprout-orchestration&lt;/code&gt; adds three patterns that compose around plain &lt;code&gt;AgentExecutor&lt;/code&gt; beans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrent runs.&lt;/strong&gt; An &lt;code&gt;AgentOrchestrator&lt;/code&gt; wraps an agent to run several prompts at once. Each &lt;code&gt;execute&lt;/code&gt; is scheduled on a worker thread and returns immediately, so calls fan out concurrently; a failing run is isolated and never tears down the others:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentOrchestrator&lt;/span&gt; &lt;span class="n"&gt;orchestrator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentOrchestrator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;orchestrator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tell me about Mars"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mars"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mars-session"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tell me about Venus"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"venus"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"venus-session"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitForExecutions&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orchestrator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mars"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delegation.&lt;/strong&gt; One agent delegates to specialists. An &lt;code&gt;AgentDelegation&lt;/code&gt; exposes a set of specialist agents to a supervisor as tools — one per specialist — through the very same &lt;code&gt;ToolProvider&lt;/code&gt; SPI the agent already uses for its &lt;code&gt;@Tool&lt;/code&gt; methods and MCP servers, so the executor needs no special-casing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AgentDelegation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;specialist&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"math"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Solves arithmetic and number problems."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mathAgent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;specialist&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"history"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Answers history and general-knowledge questions."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;historyAgent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attachTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"session"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"What is 6 times 7?"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Hand-off.&lt;/strong&gt; Instead of calling a specialist as an isolated sub-task, an agent &lt;strong&gt;transfers control&lt;/strong&gt;. An &lt;code&gt;AgentHandoff&lt;/code&gt; gives each team member a &lt;code&gt;handoff_to_&amp;lt;member&amp;gt;&lt;/code&gt; tool; the conversation passes to that agent, which continues the &lt;em&gt;same&lt;/em&gt; shared transcript and produces the final answer — while still applying its &lt;em&gt;own&lt;/em&gt; system prompt to its turns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AgentHandoff&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentHandoff&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"triage"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"First point of contact; routes the user."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;triageAgent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"billing"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Handles invoices, payments and refunds."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;billingAgent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tech"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Handles login, passwords and technical errors."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;techAgent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;AgentHandoff&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HandoffResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"I have a question about my invoice."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;     &lt;span class="c1"&gt;// [triage, billing]&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// the billing agent's answer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three compose: because delegation happens &lt;em&gt;inside&lt;/em&gt; an agent's own run, a delegating supervisor is just an &lt;code&gt;AgentExecutor&lt;/code&gt; — so you can orchestrate it concurrently &lt;strong&gt;and&lt;/strong&gt; make it the target of a hand-off, all around one hub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fully Spring-compatible — in both directions
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;sprout-spring-boot-starter&lt;/code&gt; and the container is bootstrapped during context startup, with &lt;strong&gt;no glue code&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sprout → Spring:&lt;/strong&gt; every Sprout bean (agents' executors, models, services...) is registered in the &lt;code&gt;ApplicationContext&lt;/code&gt;, so you can &lt;code&gt;@Autowired&lt;/code&gt; it into any &lt;code&gt;@Controller&lt;/code&gt;/&lt;code&gt;@Service&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring → Sprout:&lt;/strong&gt; a Sprout component can depend on a Spring bean; it is resolved from the &lt;code&gt;BeanFactory&lt;/code&gt; as a lazy proxy, preserving AOP/&lt;code&gt;@Transactional&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So an agent can be backed by an existing Spring &lt;code&gt;@Service&lt;/code&gt; and called straight from a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Agent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnthropicModelExecutor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are a weather assistant."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AgentExecutor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;WeatherService&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// an existing Spring @Service&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;WeatherAgent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WeatherService&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// Spring bean injected into the agent&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Look up the forecast for a city"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;forecast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forecast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runnable Spring example pushes this all the way: a &lt;code&gt;/weather/batch&lt;/code&gt; endpoint is a plain &lt;code&gt;@RestController&lt;/code&gt; that fans a forecast-per-city out &lt;strong&gt;concurrently&lt;/strong&gt; with &lt;code&gt;AgentOrchestrator&lt;/code&gt;, over a Spring-managed &lt;code&gt;@Agent&lt;/code&gt; whose tool is a Spring &lt;code&gt;@Service&lt;/code&gt; and whose conversations persist to a database via JPA. One HTTP call drives concurrent multi-agent work with Spring DI and JPA persistence — all inside the same application, using the framework and the beans you already run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizable to the core
&lt;/h2&gt;

&lt;p&gt;Here's the part I'm most proud of: the processing pipeline is fully open, and it is the &lt;em&gt;same&lt;/em&gt; mechanism the built-in features use — there is no privileged core. To teach Sprout a new annotation, subclass &lt;code&gt;ComponentProcessor&lt;/code&gt; and register it with &lt;code&gt;@Processor(MyAnnotation.class)&lt;/code&gt;; it's discovered automatically and applied to every component carrying that annotation. A whole custom stereotype is only a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nd"&gt;@Component&lt;/span&gt;   &lt;span class="c1"&gt;// a custom stereotype, discovered by scanning&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nd"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="nd"&gt;@Processor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                        &lt;span class="c1"&gt;// applied to every @Plugin component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PluginProcessor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ComponentProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PluginProcessor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SproutContainer&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;beanNames&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;            &lt;span class="c1"&gt;// standard wiring still runs; here we add an alias&lt;/span&gt;
        &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;beanNames&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"plugin:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSimpleName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly how &lt;code&gt;@Agent&lt;/code&gt;, &lt;code&gt;@Model&lt;/code&gt;, &lt;code&gt;@Configuration&lt;/code&gt; and &lt;code&gt;@Mcp&lt;/code&gt; are implemented — each lives in its own module and plugs in without the core knowing about it. The same door is open to your code and to third-party modules, so the framework can grow a module for anything: a new model provider, a transport, a persistence-backed conversation store, custom stereotypes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The modules
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;What it provides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprout-core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoC container, component scanning, DI, configuration, and the agent/model/tool abstractions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprout-anthropic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ModelExecutor&lt;/code&gt; for Anthropic's Messages API (&lt;code&gt;@Model("anthropic")&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprout-openai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ModelExecutor&lt;/code&gt; for OpenAI's Chat Completions API (&lt;code&gt;@Model("openai")&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprout-mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expose &lt;code&gt;@Tool&lt;/code&gt; methods as an MCP server, and consume remote MCP servers from an agent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprout-orchestration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Concurrent runs, supervisor delegation and conversation hand-off.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprout-spring-boot-starter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs Sprout inside Spring Boot, bridging beans and configuration both ways.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt; Java 21, Maven 3.9+.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's headed
&lt;/h2&gt;

&lt;p&gt;This is an early release, so the surface is deliberately focused — expect gaps and rough edges. On the roadmap: nested-object tool parameters, built-in JDBC/Redis conversation stores, real SSE provider streaming, more providers (Gemini, Azure OpenAI, Ollama), memory &amp;amp; RAG, observability/token accounting, structured output, human-in-the-loop guardrails, and GraalVM native image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.ivannavas&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;sprout-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ivannavas/sprout-ai-framework" rel="noopener noreferrer"&gt;https://github.com/ivannavas/sprout-ai-framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maven Central:&lt;/strong&gt; &lt;a href="https://central.sonatype.com/namespace/io.github.ivannavas" rel="noopener noreferrer"&gt;https://central.sonatype.com/namespace/io.github.ivannavas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build AI features on the JVM — or wish you could without leaving it — I'd love your feedback, issues and stars. What would you want a Java-native agent framework to do next?&lt;/p&gt;

</description>
      <category>java</category>
      <category>ai</category>
      <category>agents</category>
      <category>springboot</category>
    </item>
  </channel>
</rss>
