Java Clean Architecture Masterclass

Java Clean Architecture MasterclassNov 20-21

Join

Embabel Agent Framework

Build Quality Gate Status Discord

Kotlin Java Spring Spring Boot Apache Tomcat Apache Maven JUnit ChatGPT Jinja JSON GitHub Actions SonarQube Docker IntelliJ IDEA License Commits

    

Embabel (Em-BAY-bel) is a framework for authoring agentic flows on the JVM that seamlessly mix LLM-prompted interactions with code and domain models. Supports intelligent path finding towards goals. Written in Kotlin but offers a natural usage model from Java. From the creator of Spring.

Key Concepts

Models agentic flows in terms of:

Application developers don't usually have to deal with these concepts directly, as most conditions result from data flow defined in code, allowing the system to infer pre and post conditions.

These concepts underpin these differentiators versus other agent frameworks:

Other benefits:

Flows can be authored in one of two ways:

Either way, flows are backed by a domain model of objects that can have rich behavior.

We are working toward allowing natural language actions and goals to be deployed.

The planning step is pluggable.

The default planning approach is Goal Oriented Action Planning. GOAP is a popular AI planning algorithm used in gaming. It allows for dynamic decision-making and action selection based on the current state of the world and the goals of the agent.

Goals, actions and plans are independent of GOAP. Future planning options include:

The framework executes via an AgentPlatform implementation.

An agent platform supports the following modes of execution:

Open mode is the most powerful, but least deterministic.

In open mode, the platform is capable of finding novel paths that were not envisioned by developers, and even combining functionality from multiple providers.

Even in open mode, the platform will only perform individual steps that have been specified. (Of course, steps may themselves be LLM transforms, in which case the prompts are controlled by user code but the results are still non-deterministic.)

Possible future modes:

Embabel agent systems will also support federation, both with other Embabel systems (allowing planning to incorporate remote actions and goals) and third party agent frameworks.

Quick Start

Get an agent running in under 5 minutes.

Create your own agent repo from our Java or Kotlin GitHub template by clicking the "Use this template" button.

You can also create your own Embabel agent project locally with our quick start tool:

uvx --from git+https://github.com/embabel/project-creator.git project-creator

Choose Java or Kotlin and specify your project name and package name and you'll have an agent running in under a minute, if you already have an OPENAI_API_KEY and have Maven installed.

📚 For examples and tutorials, see the Embabel Agent Examples Repository

🚗 For a realistic example application, see the Tripper travel planner agent

Why Is Embabel Needed?

TL;DR Because the evolution of agent frameworks is early and there's a lot of room for improvement; because an agent framework on the JVM will deliver great business value.

Show Me The Code

In Kotlin or Java, agent implementation code is intuitive and easy to test.

@Agent(description = "Find news based on a person's star sign")
class StarNewsFinder(
    // Services such as Horoscope are injected using Spring
    private val horoscopeService: HoroscopeService,
    private val storyCount: Int = 5,
) {

    @Action
    fun extractPerson(userInput: UserInput): StarPerson =
        // All prompts are typesafe
        PromptRunner().createObject("Create a person from this user input, extracting their name and star sign: $userInput")

    @Action
    fun retrieveHoroscope(starPerson: StarPerson) =
        Horoscope(horoscopeService.dailyHoroscope(starPerson.sign))

    // toolGroups specifies tools that are required for this action to run
    @Action(toolGroups = [ToolGroup.WEB])
    fun findNewsStories(person: StarPerson, horoscope: Horoscope): RelevantNewsStories =
        PromptRunner().createObject(
            """
            ${person.name} is an astrology believer with the sign ${person.sign}.
            Their horoscope for today is:
                <horoscope>${horoscope.summary}</horoscope>
            Given this, use web tools and generate search queries
            to find $storyCount relevant news stories summarize them in a few sentences.
            Include the URL for each story.
            Do not look for another horoscope reading or return results directly about astrology;
            find stories relevant to the reading above.

            For example:
            - If the horoscope says that they may
            want to work on relationships, you could find news stories about
            novel gifts
            - If the horoscope says that they may want to work on their career,
            find news stories about training courses.
        """.trimIndent()
        )

    // The @AchievesGoal annotation indicates that completing this action
    // achieves the given goal, so the agent run will be complete
    @AchievesGoal(
        description = "Write an amusing writeup for the target person based on their horoscope and current news stories",
    )
    @Action
    fun writeup(
        person: StarPerson,
        relevantNewsStories: RelevantNewsStories,
        horoscope: Horoscope,
    ): Writeup =
        // Customize LLM call
        PromptRunner().withTemperature(1.2).createObject(
            """
            Take the following news stories and write up something
            amusing for the target person.

            Begin by summarizing their horoscope in a concise, amusing way, then
            talk about the news. End with a surprising signoff.

            ${person.name} is an astrology believer with the sign ${person.sign}.
            Their horoscope for today is:
                <horoscope>${horoscope.summary}</horoscope>
            Relevant news stories are:
            ${relevantNewsStories.items.joinToString("\n") { "- ${it.url}: ${it.summary}" }}

            Format it as Markdown with links.
        """.trimIndent()
        )

}

The following domain classes ensure type safety:

data class RelevantNewsStories(
    val items: List<NewsStory>
)

data class NewsStory(
    val url: String,

    val summary: String,
)

data class Subject(
    val name: String,
    val sign: String,
)

data class Horoscope(
    val summary: String,
)

data class FunnyWriteup(
    override val text: String,
) : HasContent

Dog Food Policy

We believe that all aspects of software development can and should be greatly accelerated through the use of AI agents. The ultimate decision makers remain human, but they can and should be greatly augmented.

This project practices extreme dogfooding.

Our key principles:

  1. We will use AI agents to help every aspect of the project: coding, documentation, producing marketing copy etc. Any human performing a task should ask why it cannot be automated, and strive toward maximum automation.
  2. Developers retain ultimate control. Developers are responsible for guiding agents toward the solution and iterating as necessary. A developer who commits or merges an agent contribution is responsible for ensuring that it meets the project coding standards, which are independent of the use of agents. For example, code must be human readable.
  3. We will use only open source agents built on the Embabel platform, and contribute any improvements. While commercial coding agents may be more advanced, we believe that our platform is the best general solution for automation and by dogfooding we will improve it fastest. By open sourcing agents used on our open source projects, we will maximize benefit to the community.
  4. We will prioritize agents that help accelerate our progress. Per the flight safety advice to fit your own mask before helping others, we will prioritize agents that help us accelerate our own progress. This will not only produce useful examples, but increase overall project velocity.

Developers must carefully read all code they commit and improve generated code if possible.

Getting Started

Getting the bits

Choose one of the following:

Environment variables

Environment variables are consistent with common usage, rather than Spring AI. For example, we prefer OPENAI_API_KEY to SPRING_AI_OPENAI_API_KEY.

Required:

Optional:

We strongly recommend providing both an OpenAI and Anthropic key, as some examples require both. And it's important to try to find the best LLM for a given task, rather than automatically choose a familiar provider.

Services

You will need a Docker Desktop version >4.43.2. Be sure to activate the following MCP tools from the catalog:

You can also set up your own MCP tools using Spring AI conventions. See the application-docker-desktop.yml file for an example.

If you're running Ollama locally, set the ollama profile and Embabel will automatically connect to your Ollama endpoint and make all models available.

Running

Create your own agent project with

uvx --from git+https://github.com/embabel/project-creator.git project-creator

Example Agents

📚 For examples and tutorials, see the Embabel Agent Examples Repository

# Clone and run examples
git clone https://github.com/embabel/embabel-agent-examples
cd embabel-agent-examples/scripts/kotlin
./shell.sh

Shell Commands

Type help to see available commands. Example:

execute "Lynda is a Scorpio, find news for her" -p -r

Options:

Use chat for interactive conversations with agents.

Tip: Spring Shell supports history - type !! to repeat the last command

Type help to see the available commands.

An example:

execute "Lynda is a Scorpio, find news for her" -p -r

This will look for an agent, choose the star finder agent and run the flow. -p will log prompts -r will log LLM responses. Omit these for less verbose logging.

Use the chat command to enter an interactive chat with the agent. It will retain conversation history, and attempt to run the most appropriate agent for each command.

Spring Shell supports history. Type !! to repeat the last command. This will survive restarts, so is handy when iterating on an agent.

Further examples

Example commands within the shell:

# Perplexity style deep research
# Requires both OpenAI and Anthropic keys and Docker Desktop with the MCP extension (or your own web tools)
execute "research the recent australian federal election. what is the position of the greens party?"

# x is a shortcut for execute
x "fact check the following: holden cars are still made in australia; the koel is a bird native only to australia; fidel castro is justin trudeau's father"

Try the coding agent (separate repo) with commands such as:


x "explain this project for a five your old"

x "take the StarNewsFinder kotlin example of the agent framework. create a parallel .java package beside its and create a java version of the same agent use the same annotations and other classes. use records for the data classes. make it modern java"

x "consider the StarNewsFinder kotlin class. This is intended as an example. Is there anything you could do to make it simpler? Include suggested API changes. Do not change code"

Bringing in additional LLMs

Local models with well-known providers

The Embabel Agent Framework supports local models from:

Custom LLMs

You can define an LLM for any provider for which a Spring AI ChatModel is available.

Simply define Spring beans of type Llm. See the OpenAiConfiguration class as an example.

Remember:

Roadmap

This project is in its early stages, but we have big plans. The milestones and issues in this repository are a good reference. Our key goals:

There is a lot to do, and you are awesome. We look forward to your contribution!

Application Design

Domain objects

Applications center around domain objects. These can be instantiated by LLMs or user code, and manipulated by user code.

Use Jackson annotations to help LLMs with descriptions as well as mark fields to ignore. For example:

@JsonClassDescription("Person with astrology details")
data class StarPerson(
    override val name: String,
    @get:JsonPropertyDescription("Star sign")
    val sign: String,
) : Person

See Java Json Schema Generation - Module Jackson for documentation of the library used.

Domain objects can have behaviors that are automatically exposed to LLMs when they are in scope. Simply annotate methods with the Spring AI @Tool annotation.

When exposing @Tool methods on domain objects, be sure that the tool is safe to invoke. Even the best LLMs can get trigger-happy. For example, be careful about methods that can mutate or delete data. This is likely better modeled via an explicit call to a non-tool method on the same domain class, in a code action.

Using Embabel as an MCP server

You can use the Embabel agent platform as an MCP server from a UI like Claude Desktop. The Embabel MCP server is available over SSE.

Configure Claude Desktop as follows in your claude_desktop_config.yml:

{
  "mcpServers": {
    "embabel": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "http://localhost:8080/sse"
      ]
    }
  }
}

See MCP Quickstart for Claude Desktop Users for how to configure Claude Desktop.

The MCP Inspector is a helpful tool for interacting with your Embabel SSE server, manually invoking tools and checking the exposed prompts and resources.

Start the MCP Inspector with:

npx @modelcontextprotocol/inspector

Consuming MCP Servers

The Embabel Agent Framework provides built-in support for consuming Model Context Protocol (MCP) servers, allowing you to extend your applications with powerful AI capabilities through standardized interfaces.

What is MCP?

Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context and extra functionality to large language models. Introduced by Anthropic, MCP has emerged as the de facto standard for connecting AI agents to tools, functioning as a client-server protocol where:

MCP simplifies integration between AI applications and external tools, transforming an "M×N problem" into an "M+N problem" through standardization - similar to what USB did for hardware peripherals.

Configuring MCP in Embabel Agent

To configure MCP servers in your Embabel Agent application, add the following to your application.yml:

spring:
  ai:
    mcp:
      client:
        enabled: true
        name: embabel
        version: 1.0.0
        request-timeout: 30s
        type: SYNC
        stdio:
          connections:
            docker-mcp:
              command: docker
              args:
                - run
                - -i
                - --rm
                - alpine/socat
                - STDIO
                - TCP:host.docker.internal:8811

This configuration sets up an MCP client that connects to a Docker-based MCP server. The connection uses STDIO transport through Docker's socat utility to connect to a TCP endpoint.

Docker Desktop MCP Integration

Docker has embraced MCP with their Docker MCP Catalog and Toolkit, which provides:

  1. Centralized Discovery - A trusted hub for discovering MCP tools integrated into Docker Hub
  2. Containerized Deployment - Run MCP servers as containers without complex setup
  3. Secure Credential Management - Centralized, encrypted credential handling
  4. Built-in Security - Sandbox isolation and permissions management

The Docker MCP ecosystem includes over 100 verified tools from partners like Stripe, Elastic, Neo4j, and more, all accessible through Docker's infrastructure.

Learn More

A2A

Embabel integrates with the A2A protocol, allowing you to connect to other A2A-enabled agents and services.

Enable the a2a Spring profile to start the A2A server.

You'll need the following environment variable:

Start the Google A2A web interface using the a2a Docker profile:

docker compose --profile a2a up

Go to the web interface running within the container at http://localhost:12000/.

Connect to your agent at host.docker.internal:8080/a2a. Note that localhost:8080/a2a won't work as the server cannot access it when running in a Docker container.

Running Tests

Run the tests via Maven.

mvn test

This will run both unit and integration tests but will not require an internet connection or any external services.

Spring profiles

Spring profiles are used to configure the application for different environments and behaviors.

Interaction profiles:

Model profiles:

Logging profiles:

Testing

A key goal of this framework is ease of testing. Just as Spring eased testing of early enterprise Java applications, this framework facilitates testing of AI applications.

Types of testing:

Logging

All logging in this project is either debug logging in the relevant class itself, or results from the stream of events of type AgentEvent.

Edit application.yml if you want to see debug logging from the relevant classes and packages.

Available logging experiences:

If none of these profiles is chosen, Embabel will use vanilla logging. This makes me sad.

Adding Embabel Agent Framework to Your Project

Maven

The easiest way is to add the Embabel Spring Boot starter dependency to your pom.xml:


<dependency>
    <groupId>com.embabel.agent</groupId>
    <artifactId>embabel-agent-starter</artifactId>
    <version>${embabel-agent.version}</version>
</dependency>

Gradle (Kotlin DSL)

Add the required repositories to your build.gradle.kts:

repositories {
    mavenCentral()
    maven {
        name = "embabel-snapshots"
        url = uri("https://repo.embabel.com/artifactory/libs-snapshot")
        mavenContent {
            snapshotsOnly()
        }
    }
    maven {
        name = "Spring Milestones"
        url = uri("https://repo.spring.io/milestone")
    }
}

Add the Embabel Agent starter:

dependencies {
    implementation('com.embabel.agent:embabel-agent-starter:${embabel-agent.version}')
}

Gradle (Groovy DSL)

Add the required repositories to your build.gradle:

repositories {
    mavenCentral()
    maven {
        name = 'embabel-snapshots'
        url = 'https://repo.embabel.com/artifactory/libs-snapshot'
        mavenContent {
            snapshotsOnly()
        }
    }
    maven {
        name = 'Spring Milestones'
        url = 'https://repo.spring.io/milestone'
    }
}

Add the Embabel Agent starter:

dependencies {
    implementation 'com.embabel.agent:embabel-agent-starter:0.1.0-SNAPSHOT'
}

Note: The Spring Milestones repository is required because the Embabel BOM (embabel-agent-dependencies) has transitive dependencies on experimental Spring components, specifically the mcp-bom. This BOM is not available on Maven Central and is only published to the Spring milestone repository. Unlike Maven, Gradle does not inherit repository configurations declared in parent POMs or BOMs. Therefore, it is necessary to explicitly declare the Spring milestone repository in your repositories block to ensure proper resolution of all transitive dependencies.

Repository

Binary Packages are located in Embabel Maven Repository. You would need to add Embabel Snapshot Repository to your pom.xml or configure in settings.xml


<repositories>
    <repository>
        <id>embabel-snapshots</id>
        <url>https://repo.embabel.com/artifactory/libs-snapshot</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

Contributing

We welcome contributions to the Embabel Agent Framework.

Look at the coding style guide for style guidelines. This file also informs coding agent behavior.

Miscellaneous

Star history

Star History Chart

Contributors

Embabel contributors


(c) Embabel Software Inc 2024-2025.

Join libs.tech

...and unlock some superpowers

GitHub

We won't share your data with anyone else.