Fluent DSL and Kotlin DSL for building autonomous agents with planning capabilities on the JVM, featuring annotation-based and programmatic configuration for agentic flows with Spring Boot integration
Embabel Agent API is a sophisticated Kotlin/Java library for building autonomous agent applications on the JVM with Spring Boot integration. It provides both annotation-based configuration and an idiomatic Kotlin DSL for authoring agentic flows that seamlessly mix LLM-prompted interactions with code and domain models.
The framework features sophisticated planning capabilities through GOAP (Goal-Oriented Action Planning) and condition-based planning algorithms, enabling dynamic action sequencing that goes beyond finite state machines. It implements an OODA loop pattern (Observe, Orient, Decide, Act) where agents formulate plans dynamically and replan after each action execution.
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-api</artifactId>
<version>0.3.3</version>
</dependency>import com.embabel.agent.api.annotation.*
import com.embabel.agent.api.dsl.*
import com.embabel.agent.api.common.*
import com.embabel.agent.api.tool.*
import com.embabel.agent.core.*import com.embabel.agent.api.annotation.*;
import com.embabel.agent.api.common.*;
import com.embabel.agent.api.tool.*;
import com.embabel.agent.core.*;@Agent(
name = "customer-support",
provider = "support-team",
description = "Handles customer support requests"
)
class CustomerSupportAgent {
@Action(description = "Look up customer account details")
fun lookupCustomer(customerId: String): CustomerAccount {
// Action implementation
return customerService.getAccount(customerId)
}
@Action(
description = "Create a support ticket",
post = ["ticket_created"]
)
fun createTicket(issue: String, customerId: String): Ticket {
// Action implementation
return ticketService.create(issue, customerId)
}
@Condition(name = "customer_exists")
fun customerExists(customerId: String): Boolean {
return customerService.exists(customerId)
}
}val agent = agent(
name = "data-processor",
provider = "analytics",
description = "Processes and analyzes data"
) {
action<DataSet, Report> {
description = "Analyze dataset and generate report"
pre = listOf("dataset_valid")
execute { dataset, context ->
val insights = context.ai().withLlm().creating(Insights::class.java)
.fromPrompt("Analyze this dataset: ${dataset.summary}")
Report(insights, dataset.id)
}
}
condition {
name = "dataset_valid"
evaluate { ctx ->
ctx.last(DataSet::class.java)?.isValid() ?: false
}
}
}@Agent(
name = "order-processor",
provider = "ecommerce",
description = "Processes customer orders"
)
public class OrderProcessingAgent {
@Action(description = "Validate order items")
public ValidationResult validateOrder(Order order) {
return orderValidator.validate(order);
}
@Action(
description = "Process payment",
pre = {"order_validated"},
post = {"payment_processed"}
)
public PaymentReceipt processPayment(Order order, PaymentInfo payment) {
return paymentService.process(order, payment);
}
}The Embabel Agent framework is built on several key architectural layers:
The framework supports two planning approaches:
Plans are reformulated after each action execution, enabling adaptive behavior.
Agents follow the OODA loop pattern:
Define agents using annotations or Kotlin DSL for both Kotlin and Java codebases.
// Annotation-based
@Target(AnnotationTarget.CLASS)
annotation class Agent(
val name: String,
val provider: String,
val description: String,
val version: String = "",
val planner: PlannerType = PlannerType.GOAP,
val scan: Boolean = true,
val beanName: String = "",
val opaque: Boolean = false,
val actionRetryPolicy: ActionRetryPolicy = ActionRetryPolicy.DEFAULT,
val actionRetryPolicyExpression: String = ""
)
// DSL-based
fun agent(
name: String,
provider: String,
description: String,
block: AgentBuilder.() -> Unit
): AgentDefine executable actions with preconditions, postconditions, and goal achievements.
@Target(AnnotationTarget.FUNCTION)
annotation class Action(
val description: String,
val pre: Array<String> = [],
val post: Array<String> = [],
val canRerun: Boolean = true,
val clearBlackboard: Boolean = false,
val outputBinding: String = "",
val cost: Double = 0.0,
val value: Double = 0.0,
val costMethod: String = "",
val valueMethod: String = ""
)
@Target(AnnotationTarget.FUNCTION)
annotation class AchievesGoal(
val description: String,
val value: Double = 0.0,
val tags: Array<String> = [],
val examples: Array<String> = []
)Define boolean predicates for planning and execution control.
@Target(AnnotationTarget.FUNCTION)
annotation class Condition(
val name: String = "",
val cost: Double = 0.0
)Interact with Large Language Models through the PromptRunner API for text generation, object creation, and tool calling.
interface PromptRunner {
fun <T> creating(outputClass: Class<T>): Creating<T>
fun thinking(): Thinking
fun rendering(templateName: String): Rendering
fun supportsStreaming(): Boolean
fun streaming(): StreamingCapability
}
interface Creating<T> {
fun fromPrompt(prompt: String): T
fun fromTemplate(templateName: String, model: Any): T
fun fromMessages(messages: List<Message>): T
fun withExample(description: String, value: T): Creating<T>
fun withValidation(validate: Boolean): Creating<T>
}Create LLM-invocable tools with schema definitions for function calling.
@Target(AnnotationTarget.FUNCTION)
annotation class LlmTool(
val description: String,
val name: String = "",
val returnDirect: Boolean = false,
val category: String = ""
) {
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Param(
val description: String,
val required: Boolean = true
)
}
interface Tool {
fun call(input: String): Result
sealed interface Result {
data class Text(val content: String): Result
data class WithArtifact(val content: String, val artifact: Any): Result
data class Error(val message: String, val cause: Throwable?): Result
}
}Deploy agents and manage agent process lifecycles with fine-grained control over execution, monitoring, and resource management.
interface AgentPlatform {
fun deploy(vararg agents: Agent)
fun agents(): List<Agent>
fun createAgentProcess(agent: Agent, processOptions: ProcessOptions): AgentProcess
fun start(agentProcess: AgentProcess): CompletableFuture<AgentProcess>
fun getAgentProcess(id: String): AgentProcess?
fun killAgentProcess(id: String): AgentProcess?
}
interface AgentProcess {
val id: String
val agent: Agent
val status: AgentProcessStatusCode
val blackboard: Blackboard
fun run(): AgentProcess
fun kill(): AgentProcess
}Platform and Process Management
High-level API for type-safe transformations between arbitrary types using agent capabilities.
interface TypedOps {
fun <I, O> transform(input: I, outputClass: Class<O>, processOptions: ProcessOptions?): O
fun <I, O> asFunction(outputClass: Class<O>): AgentFunction<I, O>
fun <O> handleUserInput(intent: String, outputClass: Class<O>, processOptions: ProcessOptions?): O
}
interface AgentFunction<I, O> : BiFunction<I, ProcessOptions, O> {
val agentScope: AgentScope
val outputClass: Class<O>
}Define and manage domain types with property definitions, validation rules, and semantic annotations.
sealed interface DomainType {
val ownProperties: List<PropertyDefinition>
val parents: List<DomainType>
val creationPermitted: Boolean
val properties: List<PropertyDefinition>
fun isAssignableFrom(other: DomainType): Boolean
}
data class JvmType(val jvmClass: Class<*>): DomainType
data class DynamicType(val name: String, override val ownProperties: List<PropertyDefinition>): DomainType
sealed interface PropertyDefinition {
val name: String
val description: String?
val cardinality: Cardinality
}Configure how data flows between actions through blackboard bindings and aggregation patterns.
data class IoBinding(val variable: String, val type: String) {
val name: String
val typeName: String
val jvmType: JvmType?
companion object {
const val DEFAULT_BINDING = "it"
const val LAST_RESULT_BINDING = "lastResult"
}
}
interface Aggregation
interface SomeOf {
companion object {
fun eligibleFields(outputClass: Class<*>): Set<Field>
}
}Access execution context, blackboard state, and platform services during action execution.
interface ActionContext : ExecutingOperationContext {
val action: Action?
fun domainObjectInstances(): List<Any>
}
interface ExecutingOperationContext : OperationContext {
fun sendMessage(message: Message)
fun updateProgress(message: String)
fun <O> asSubProcess(outputClass: Class<O>, agent: Agent): O
}
interface OperationContext : Blackboard, ToolGroupConsumer {
val processContext: ProcessContext
val agentProcess: AgentProcess
fun promptRunner(): PromptRunner
fun ai(): Ai
fun <T, R> parallelMap(items: List<T>, maxConcurrency: Int, transform: (T) -> R): List<R>
}Invoke agents programmatically with typed inputs and outputs.
interface AgentInvocation<T> {
fun withAgent(agent: Agent): AgentInvocation<T>
fun withAgentName(name: String): AgentInvocation<T>
fun <U> returning(resultType: Class<U>): AgentInvocation<U>
fun withInput(input: Any): AgentInvocation<T>
fun withBlackboard(blackboard: Blackboard): AgentInvocation<T>
fun execute(): T
fun executeAsync(): CompletableFuture<T>
}Observe agent execution through a comprehensive event system.
interface AgenticEventListener {
fun onEvent(event: AgenticEvent)
}
sealed interface AgentProcessEvent : AgenticEvent, InProcess
data class ActionExecutionStartEvent(val action: Action) : AgentProcessEvent
data class ActionExecutionResultEvent(val result: Any?) : AgentProcessEvent
data class LlmRequestEvent<O>(val messages: List<Message>, val tools: List<Tool>) : AgentProcessEvent
data class AgentProcessCompletedEvent(val result: Any?) : AgentProcessEventConfigure planning algorithms and build complex workflows with scatter-gather, consensus, and loops.
enum class PlannerType(val needsGoals: Boolean) {
GOAP(needsGoals = true),
UTILITY(needsGoals = false),
SUPERVISOR(needsGoals = true)
}
interface ScatterGather<ELEMENT, RESULT> {
fun scatter(elements: List<ELEMENT>): ScatterGatherBuilder<ELEMENT, RESULT>
fun gather(): List<RESULT>
}
interface RepeatUntil {
fun repeatAction(action: () -> Any): RepeatUntilBuilder
fun until(condition: () -> Boolean): Any
}Integrate human feedback and approval into agent execution.
suspend fun <P> waitFor(awaitable: Awaitable<P, *>): P
fun <P> confirm(what: P, description: String): P
fun <P> fromForm(title: String): P
enum class Autonomy {
FULLY_AUTONOMOUS,
HUMAN_IN_LOOP,
HUMAN_APPROVAL_REQUIRED
}
data class ConfirmationRequest(
val what: Any,
val description: String
) : Awaitable<Any, Boolean>Execute subagents within actions for hierarchical agent composition.
object RunSubagent {
fun instance(agent: Agent): SubagentExecutionRequest
fun fromAnnotatedInstance(instance: Any): SubagentExecutionRequest
}
// Extension functions
fun <O : Any> ActionContext.asSubProcess(
agent: Agent,
outputClass: Class<O>
): O
fun <O : Any> ActionContext.asSubProcess(
agentScopeBuilder: AgentScopeBuilder,
outputClass: Class<O>
): OManage flow states with state transitions.
@Target(AnnotationTarget.CLASS)
annotation class State
// Returning a State from an action triggers state transition
@Action(description = "Initialize process")
fun initialize(): ProcessingState {
return ProcessingState(data)
}Validate content and enforce guardrails on agent behavior.
interface GuardRail
interface UserInputGuardRail : GuardRail {
fun validate(input: String): ValidationResult
}
interface AssistantMessageGuardRail : GuardRail {
fun validate(message: AssistantMessage): ValidationResult
}
interface ContentValidator<T> {
fun validate(content: T): ValidationResult
}Access pre-configured LLM model definitions for multiple providers.
object OpenAiModels {
val GPT_4_TURBO: String
val GPT_4: String
val GPT_3_5_TURBO: String
}
object AnthropicModels {
val CLAUDE_3_OPUS: String
val CLAUDE_3_SONNET: String
val CLAUDE_3_HAIKU: String
}Stream LLM responses for real-time feedback.
interface StreamingPromptRunner : PromptRunner {
fun streamText(prompt: String): Flux<String>
fun <T> streamObject(prompt: String, outputClass: Class<T>): Flux<T>
}
interface StreamingCapability {
fun supportsTextStreaming(): Boolean
fun supportsObjectStreaming(): Boolean
}Build chatbot interfaces with conversation management.
interface Chatbot {
fun createSession(user: User): ChatSession
}
interface ChatSession {
val conversation: Conversation
fun sendMessage(message: Message): Message
fun close()
}
interface Conversation {
val messages: List<Message>
fun addMessage(message: Message)
}Access pre-built tool implementations for common operations.
object FileReadTools {
fun readFile(path: String): String
fun listDirectory(path: String): List<String>
fun searchPattern(pattern: String, directory: String): List<String>
}
object MathTools {
fun calculate(expression: String): Double
fun evaluate(formula: String, variables: Map<String, Double>): Double
}interface Agent {
val name: String
val description: String
val provider: String
val version: String
val planner: PlannerType
}
interface AgentProcess {
val agent: Agent
val status: OperationStatus
val result: Any?
fun kill()
fun pause()
fun resume()
}
interface AgentPlatform {
fun getAgent(name: String): Agent?
fun <T> executeAgent(agent: Agent, input: Any?, resultType: Class<T>): T
}
interface Blackboard {
fun <T> get(key: String, type: Class<T>): T?
fun put(key: String, value: Any)
fun <T> last(type: Class<T>): T?
fun clear()
}
enum class OperationStatus {
PENDING,
IN_PROGRESS,
COMPLETED,
FAILED
}
enum class PlannerType(val needsGoals: Boolean) {
/** Goal Oriented Action Planning (default planner) */
GOAP(needsGoals = true),
/** Utility AI planning */
UTILITY(needsGoals = false),
/** Supervisor planner */
SUPERVISOR(needsGoals = true)
}
enum class ActionRetryPolicy {
/** Fire only once (maxAttempts = 1) */
FIRE_ONCE,
/** Default retry policy using ActionQos defaults */
DEFAULT
}interface Action {
val name: String
val description: String
val preconditions: List<String>
val postconditions: List<String>
val cost: Double
val value: Double
}
interface Goal {
val name: String
val description: String
val value: Double
val tags: List<String>
}
interface Condition {
val name: String
val cost: Double
fun evaluate(context: OperationContext): Boolean
}interface Message {
val role: MessageRole
val content: String
val timestamp: Instant
}
enum class MessageRole {
USER,
ASSISTANT,
SYSTEM,
TOOL
}
interface AssistantMessage : Message {
val toolCalls: List<ToolCall>?
}
interface ToolCall {
val id: String
val name: String
val arguments: String
}
data class MultimodalContent(
val text: String,
val images: List<Image>
)interface User {
val id: String
val displayName: String
val username: String
val email: String
}
data class SimpleUser(
override val id: String,
override val displayName: String,
override val username: String,
override val email: String
) : Userinterface OutputChannel {
fun send(event: OutputChannelEvent)
}
interface OutputChannelEvent
data class MessageOutputChannelEvent(
val processId: String,
val message: String
) : OutputChannelEvent
data class ProgressOutputChannelEvent(
val processId: String,
val message: String
) : OutputChannelEventclass NoSuchAgentException(message: String) : RuntimeException(message)
class ReplanRequestedException : RuntimeException()
class GuardRailViolationException(
message: String,
val guardRail: GuardRail
) : RuntimeException(message)
sealed class SpecialReturnException : RuntimeException() {
abstract fun handle(actionContext: ActionContext): Any
}
class SubagentExecutionRequest(
val agent: Agent,
val input: Any?
) : SpecialReturnException()
class AwaitableResponseException(
val awaitable: Awaitable<*, *>
) : SpecialReturnException()Install with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-apidocs