CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-api

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

Overview
Eval results
Files

Embabel Agent API

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.

Package Information

  • Package Name: com.embabel.agent:embabel-agent-api
  • Package Type: maven
  • Language: Kotlin (with natural Java usage patterns)
  • Version: 0.3.3
  • Installation: Add Maven dependency:
<dependency>
    <groupId>com.embabel.agent</groupId>
    <artifactId>embabel-agent-api</artifactId>
    <version>0.3.3</version>
</dependency>

Core Imports

Kotlin

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.*

Java

import com.embabel.agent.api.annotation.*;
import com.embabel.agent.api.common.*;
import com.embabel.agent.api.tool.*;
import com.embabel.agent.core.*;

Basic Usage

Annotation-Based Agent (Kotlin)

@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)
    }
}

DSL-Based Agent (Kotlin)

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
        }
    }
}

Java-Friendly Usage

@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);
    }
}

Architecture

The Embabel Agent framework is built on several key architectural layers:

Core Concepts

  • Agents: Autonomous units that achieve goals through planned action sequences
  • Actions: Executable steps that transform state and produce outputs
  • Goals: Desired outcomes that drive planning
  • Conditions: Boolean predicates evaluated during planning and execution
  • Plans: Dynamically formulated sequences of actions (GOAP or condition-based)
  • Blackboard: Shared state storage for agent processes
  • Tools: LLM-invocable functions with schema definitions

Planning System

The framework supports two planning approaches:

  1. GOAP (Goal-Oriented Action Planning) - Default algorithm using A* search to find optimal action sequences from current state to goal state
  2. Condition-Based Planning - Plan based on condition evaluation without explicit goals

Plans are reformulated after each action execution, enabling adaptive behavior.

Execution Model

Agents follow the OODA loop pattern:

  1. Observe: Gather current state from blackboard and context
  2. Orient: Evaluate conditions and determine available actions
  3. Decide: Formulate plan using planning algorithm
  4. Act: Execute next action in plan, then loop

Capabilities

Agent Definition

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
): Agent

Agent Definition

Actions and Goals

Define 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> = []
)

Actions and Goals

Conditions

Define boolean predicates for planning and execution control.

@Target(AnnotationTarget.FUNCTION)
annotation class Condition(
    val name: String = "",
    val cost: Double = 0.0
)

Conditions

LLM Interaction

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>
}

LLM Interaction

Tools

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
    }
}

Tools

Platform and Process Management

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

Type-Safe Operations

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>
}

Type-Safe Operations

Domain Type System

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
}

Domain Type System

Input/Output Binding

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>
    }
}

Input/Output Binding

Runtime Context

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>
}

Runtime Context

Agent Invocation

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>
}

Agent Invocation

Event System

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?) : AgentProcessEvent

Event System

Planning and Workflows

Configure 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
}

Planning and Workflows

Human-in-the-Loop

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>

Human-in-the-Loop

Subagents

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>
): O

Subagents

State Management

Manage 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)
}

State Management

Validation and Guardrails

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
}

Validation and Guardrails

Model Definitions

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
}

Model Definitions

Streaming Support

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
}

Streaming Support

Chat and Conversation

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)
}

Chat and Conversation

Built-in Tools

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
}

Built-in Tools

Types

Core Types

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
}

Action Types

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
}

Message Types

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>
)

User Types

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
) : User

Output Channel Types

interface 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
) : OutputChannelEvent

Exception Types

class 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-api
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/com.embabel.agent/embabel-agent-api@0.3.x