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
The event system provides comprehensive observability into agent execution. Events are emitted for all significant actions including planning, action execution, LLM calls, tool invocations, and process lifecycle changes.
Base marker interface for all agentic events.
interface AgenticEventInterface for observing agent events.
interface AgenticEventListener {
/** Called when any agentic event occurs */
fun onEvent(event: AgenticEvent)
}
class MulticastAgenticEventListener(
private val listeners: List<AgenticEventListener>
) : AgenticEventListener {
override fun onEvent(event: AgenticEvent) {
listeners.forEach { it.onEvent(event) }
}
}Basic Event Listener Example:
class ExecutionLogger : AgenticEventListener {
override fun onEvent(event: AgenticEvent) {
when (event) {
is AgentProcessCreationEvent ->
logger.info("Agent process created")
is ActionExecutionStartEvent ->
logger.info("Action started: ${event.action.name}")
is ActionExecutionResultEvent ->
logger.info("Action result: ${event.result}")
is AgentProcessCompletedEvent ->
logger.info("Agent completed: ${event.result}")
is AgentProcessFailedEvent ->
logger.error("Agent failed", event.cause)
}
}
}
// Register listener
val invocation = AgentInvocation.create<Result>(agentPlatform)
.withAgentName("processor")
.withEventListener(ExecutionLogger())
.execute()Base interface for process-level events.
sealed interface AgentProcessEvent : AgenticEvent, InProcess {
val processId: String
val timestamp: Instant
}Events tracking agent process lifecycle.
/** Agent process created */
data class AgentProcessCreationEvent(
override val processId: String,
val agent: Agent
) : AgentProcessEvent
/** Process ready to formulate plan */
data class AgentProcessReadyToPlanEvent(
override val processId: String
) : AgentProcessEvent
/** Replanning requested */
data class ReplanRequestedEvent(
override val processId: String,
val reason: String
) : AgentProcessEvent
/** Plan formulated and ready to execute */
data class AgentProcessPlanFormulatedEvent(
override val processId: String,
val plan: Plan
) : AgentProcessEvent
/** Agent process completed successfully */
data class AgentProcessCompletedEvent(
override val processId: String,
val result: Any?
) : AgentProcessEvent
/** Agent process failed */
data class AgentProcessFailedEvent(
override val processId: String,
val cause: Throwable
) : AgentProcessEvent
/** Process waiting for external input */
data class AgentProcessWaitingEvent(
override val processId: String,
val waitingFor: String
) : AgentProcessEvent
/** Process paused */
data class AgentProcessPausedEvent(
override val processId: String
) : AgentProcessEvent
/** Process stuck and cannot proceed */
data class AgentProcessStuckEvent(
override val processId: String,
val stuckReason: String
) : AgentProcessEvent
/** Process killed/terminated */
data class ProcessKilledEvent(
override val processId: String
) : AgentProcessEventLifecycle Event Example:
class LifecycleMonitor : AgenticEventListener {
private var startTime: Instant? = null
private var endTime: Instant? = null
override fun onEvent(event: AgenticEvent) {
when (event) {
is AgentProcessCreationEvent -> {
startTime = Instant.now()
logger.info("Process ${event.processId} started for agent ${event.agent.name}")
}
is AgentProcessCompletedEvent -> {
endTime = Instant.now()
val duration = Duration.between(startTime, endTime)
logger.info("Process ${event.processId} completed in ${duration.toMillis()}ms")
}
is AgentProcessFailedEvent -> {
logger.error("Process ${event.processId} failed: ${event.cause.message}")
}
is AgentProcessStuckEvent -> {
logger.warn("Process ${event.processId} stuck: ${event.stuckReason}")
}
}
}
}Events for action execution.
/** Action execution started */
data class ActionExecutionStartEvent(
override val processId: String,
val action: Action
) : AgentProcessEvent
/** Action execution completed */
data class ActionExecutionResultEvent(
override val processId: String,
val action: Action,
val result: Any?
) : AgentProcessEventAction Event Example:
class ActionTracker : AgenticEventListener {
private val actionTimes = mutableMapOf<String, Long>()
override fun onEvent(event: AgenticEvent) {
when (event) {
is ActionExecutionStartEvent -> {
actionTimes[event.action.name] = System.currentTimeMillis()
logger.info("Started: ${event.action.name}")
}
is ActionExecutionResultEvent -> {
val startTime = actionTimes[event.action.name]
if (startTime != null) {
val duration = System.currentTimeMillis() - startTime
logger.info("Completed ${event.action.name} in ${duration}ms")
logger.info("Result: ${event.result}")
}
}
}
}
}Events for LLM requests and responses.
/** LLM request sent */
data class LlmRequestEvent<O>(
override val processId: String,
val messages: List<Message>,
val tools: List<Tool>,
val options: LlmOptions
) : AgentProcessEvent
/** LLM response received */
data class LlmResponseEvent<O>(
override val processId: String,
val result: O,
val tokens: TokenUsage
) : AgentProcessEventLLM Event Example:
class LlmUsageTracker : AgenticEventListener {
private var totalPromptTokens = 0
private var totalCompletionTokens = 0
private var requestCount = 0
override fun onEvent(event: AgenticEvent) {
when (event) {
is LlmRequestEvent<*> -> {
requestCount++
logger.info("LLM request #$requestCount")
logger.info("Messages: ${event.messages.size}")
logger.info("Tools: ${event.tools.size}")
}
is LlmResponseEvent<*> -> {
totalPromptTokens += event.tokens.promptTokens
totalCompletionTokens += event.tokens.completionTokens
logger.info("Total tokens: ${event.tokens.totalTokens}")
}
}
}
fun getTotalCost(): TokenStats {
return TokenStats(
requests = requestCount,
promptTokens = totalPromptTokens,
completionTokens = totalCompletionTokens,
totalTokens = totalPromptTokens + totalCompletionTokens
)
}
}
data class TokenStats(
val requests: Int,
val promptTokens: Int,
val completionTokens: Int,
val totalTokens: Int
)Events for tool invocations.
/** Tool call requested */
data class ToolCallRequestEvent(
override val processId: String,
val toolName: String,
val input: String,
val requestId: String
) : AgentProcessEvent
/** Tool call response received */
data class ToolCallResponseEvent(
override val processId: String,
val toolName: String,
val result: Tool.Result,
val responseId: String
) : AgentProcessEventTool Event Example:
class ToolMonitor : AgenticEventListener {
private val toolUsage = mutableMapOf<String, Int>()
override fun onEvent(event: AgenticEvent) {
when (event) {
is ToolCallRequestEvent -> {
toolUsage[event.toolName] = toolUsage.getOrDefault(event.toolName, 0) + 1
logger.info("Tool called: ${event.toolName}")
logger.info("Request ID: ${event.requestId}")
}
is ToolCallResponseEvent -> {
when (val result = event.result) {
is Tool.Result.Text ->
logger.info("Tool ${event.toolName} returned: ${result.content}")
is Tool.Result.Error ->
logger.error("Tool ${event.toolName} failed: ${result.message}")
is Tool.Result.WithArtifact ->
logger.info("Tool ${event.toolName} returned artifact")
}
}
}
}
fun getMostUsedTool(): String? {
return toolUsage.maxByOrNull { it.value }?.key
}
}Events for state transitions and goal achievements.
/** State transition occurred */
data class StateTransitionEvent(
override val processId: String,
val stateDescription: String,
val fromState: Any?,
val toState: Any
) : AgentProcessEvent
/** Goal achieved */
data class GoalAchievedEvent(
override val processId: String,
val goal: Goal
) : AgentProcessEventState Event Example:
class StateTracker : AgenticEventListener {
private val stateHistory = mutableListOf<String>()
private val achievedGoals = mutableListOf<String>()
override fun onEvent(event: AgenticEvent) {
when (event) {
is StateTransitionEvent -> {
stateHistory.add(event.stateDescription)
logger.info("State transition: ${event.stateDescription}")
}
is GoalAchievedEvent -> {
achievedGoals.add(event.goal.name)
logger.info("Goal achieved: ${event.goal.name}")
}
}
}
fun getProgress(): Progress {
return Progress(
states = stateHistory,
goals = achievedGoals
)
}
}Events for blackboard object operations.
interface ObjectBindingEvent : AgentProcessEvent
/** Object added to blackboard */
data class ObjectAddedEvent(
override val processId: String,
val obj: Any
) : ObjectBindingEvent
/** Object bound to specific name */
data class ObjectBoundEvent(
override val processId: String,
val name: String,
val obj: Any
) : ObjectBindingEventBlackboard Event Example:
class BlackboardMonitor : AgenticEventListener {
private val boundObjects = mutableMapOf<String, Any>()
override fun onEvent(event: AgenticEvent) {
when (event) {
is ObjectAddedEvent -> {
logger.info("Object added: ${event.obj::class.simpleName}")
}
is ObjectBoundEvent -> {
boundObjects[event.name] = event.obj
logger.info("Object bound: ${event.name} = ${event.obj::class.simpleName}")
}
}
}
}Events for progress updates.
/** Progress update */
data class ProgressUpdateEvent(
override val processId: String,
val message: String
) : AgentProcessEventProgress Event Example:
class ProgressReporter : AgenticEventListener {
override fun onEvent(event: AgenticEvent) {
if (event is ProgressUpdateEvent) {
println("Progress: ${event.message}")
}
}
}Platform-level events for ranking and choices.
sealed interface AgentPlatformEvent : AgenticEvent
/** Ranking choice requested */
data class RankingChoiceRequestEvent<T>(
val options: List<T>,
val criteria: RankingCriteria
) : AgentPlatformEvent
/** Ranking choice made */
data class RankingChoiceMadeEvent<T>(
val chosen: T,
val options: List<T>
) : AgentPlatformEvent
/** Ranking choice could not be made */
data class RankingChoiceCouldNotBeMadeEvent<T>(
val options: List<T>,
val reason: String
) : AgentPlatformEventCombine multiple listeners.
fun createCompositeListener(
vararg listeners: AgenticEventListener
): AgenticEventListener {
return MulticastAgenticEventListener(listeners.toList())
}
// Usage
val compositeListener = createCompositeListener(
LifecycleMonitor(),
LlmUsageTracker(),
ToolMonitor(),
ProgressReporter()
)
AgentInvocation.create<Result>(agentPlatform)
.withAgentName("processor")
.withEventListener(compositeListener)
.execute()interface InProcess {
val processId: String
}
interface Plan {
val steps: List<Action>
val cost: Double
}
interface TokenUsage {
val promptTokens: Int
val completionTokens: Int
val totalTokens: Int
}
interface RankingCriteria {
val description: String
}Install with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-api@0.3.0docs