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
State management in Embabel enables agents to transition between different states, with each state holding a subset of available actions. This supports state machine patterns and workflow phases.
Mark classes as agent states.
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class StateBasic State Example:
@State
data class InitialState(
val requestId: String,
val data: RawData
)
@State
data class ProcessingState(
val requestId: String,
val validatedData: ValidatedData,
val startTime: Instant
)
@State
data class CompletedState(
val requestId: String,
val result: ProcessedData,
val completionTime: Instant
)Returning a State from an action triggers a state transition.
@Agent(
name = "stateful-processor",
provider = "processing",
description = "Processes data with state transitions"
)
class StatefulProcessorAgent {
@Action(description = "Initialize processing")
fun initialize(request: ProcessingRequest): InitialState {
return InitialState(
requestId = request.id,
data = request.data
)
}
@Action(description = "Validate and start processing")
fun startProcessing(state: InitialState, context: ActionContext): ProcessingState {
// Validate data
val validated = validator.validate(state.data)
// Transition to processing state
return ProcessingState(
requestId = state.requestId,
validatedData = validated,
startTime = Instant.now()
)
}
@Action(description = "Complete processing")
fun complete(state: ProcessingState, context: ActionContext): CompletedState {
// Process validated data
val result = processor.process(state.validatedData)
// Transition to completed state
return CompletedState(
requestId = state.requestId,
result = result,
completionTime = Instant.now()
)
}
}Actions can be available only in specific states by using state types as parameters.
@Agent(
name = "order-processor",
provider = "ecommerce",
description = "Processes orders through states"
)
class OrderProcessorAgent {
@State
data class DraftOrder(
val orderId: String,
val items: List<Item>,
val customer: Customer
)
@State
data class ValidatedOrder(
val orderId: String,
val items: List<Item>,
val customer: Customer,
val validationResult: ValidationResult
)
@State
data class PaidOrder(
val orderId: String,
val items: List<Item>,
val customer: Customer,
val payment: Payment
)
@State
data class ShippedOrder(
val orderId: String,
val items: List<Item>,
val customer: Customer,
val shipment: Shipment
)
// Only available for DraftOrder state
@Action(description = "Validate order")
fun validateOrder(draft: DraftOrder): ValidatedOrder {
val validation = orderValidator.validate(draft)
return ValidatedOrder(
orderId = draft.orderId,
items = draft.items,
customer = draft.customer,
validationResult = validation
)
}
// Only available for ValidatedOrder state
@Action(description = "Process payment")
fun processPayment(validated: ValidatedOrder): PaidOrder {
val payment = paymentService.process(validated)
return PaidOrder(
orderId = validated.orderId,
items = validated.items,
customer = validated.customer,
payment = payment
)
}
// Only available for PaidOrder state
@Action(description = "Ship order")
fun shipOrder(paid: PaidOrder): ShippedOrder {
val shipment = shippingService.ship(paid)
return ShippedOrder(
orderId = paid.orderId,
items = paid.items,
customer = paid.customer,
shipment = shipment
)
}
}Use @Provided annotation to inject context into state-based actions.
@Agent(
name = "workflow-engine",
provider = "automation",
description = "Executes workflows with states"
)
class WorkflowEngineAgent {
@State
data class WorkflowActive(
val workflowId: String,
val currentStep: Int,
val data: Map<String, Any>
)
@Action(description = "Execute workflow step")
fun executeStep(
@RequireNameMatch("workflow") workflow: WorkflowActive,
@Provided context: ActionContext
): WorkflowActive {
val step = workflowDefinitions.getStep(workflow.currentStep)
// Execute step with LLM if needed
val result = if (step.requiresLlm) {
context.promptRunner().createObject<StepResult>(
"Execute: ${step.description}"
)
} else {
step.execute(workflow.data)
}
// Update workflow data
val updatedData = workflow.data + ("step_${workflow.currentStep}" to result)
// Transition to next step
return WorkflowActive(
workflowId = workflow.workflowId,
currentStep = workflow.currentStep + 1,
data = updatedData
)
}
}Transition to different states based on conditions.
@Agent(
name = "approval-workflow",
provider = "workflow",
description = "Approval workflow with conditional transitions"
)
class ApprovalWorkflowAgent {
@State
data class PendingApproval(
val request: ApprovalRequest,
val submittedAt: Instant
)
@State
data class Approved(
val request: ApprovalRequest,
val approvedBy: String,
val approvedAt: Instant
)
@State
data class Rejected(
val request: ApprovalRequest,
val rejectedBy: String,
val rejectedAt: Instant,
val reason: String
)
@State
data class RequiresAdditionalInfo(
val request: ApprovalRequest,
val requestedInfo: List<String>
)
@Action(description = "Process approval request")
fun processApproval(
pending: PendingApproval,
context: ActionContext
): State {
// Evaluate request with LLM
val evaluation = context.promptRunner()
.createObject<ApprovalEvaluation>("""
Evaluate this approval request:
${pending.request.description}
Determine: approve, reject, or request more info
""")
// Conditional transition
return when (evaluation.decision) {
Decision.APPROVE -> Approved(
request = pending.request,
approvedBy = "system",
approvedAt = Instant.now()
)
Decision.REJECT -> Rejected(
request = pending.request,
rejectedBy = "system",
rejectedAt = Instant.now(),
reason = evaluation.reason
)
Decision.NEEDS_INFO -> RequiresAdditionalInfo(
request = pending.request,
requestedInfo = evaluation.requestedInfo
)
}
}
}
data class ApprovalEvaluation(
val decision: Decision,
val reason: String,
val requestedInfo: List<String>
)
enum class Decision {
APPROVE,
REJECT,
NEEDS_INFO
}Track state transitions for audit and debugging.
@Agent(
name = "tracked-processor",
provider = "processing",
description = "Processor with state history"
)
class TrackedProcessorAgent {
@State
data class ProcessState(
val processId: String,
val currentPhase: Phase,
val data: Any,
val history: List<StateTransition> = emptyList()
)
data class StateTransition(
val fromPhase: Phase,
val toPhase: Phase,
val timestamp: Instant,
val metadata: Map<String, Any>
)
enum class Phase {
INITIALIZED,
VALIDATED,
PROCESSING,
COMPLETED
}
@Action(description = "Transition to next phase")
fun transitionPhase(
state: ProcessState,
nextPhase: Phase,
context: ActionContext
): ProcessState {
// Record transition
val transition = StateTransition(
fromPhase = state.currentPhase,
toPhase = nextPhase,
timestamp = Instant.now(),
metadata = mapOf(
"action" to (context.action?.name ?: "unknown")
)
)
// Create new state with updated history
return state.copy(
currentPhase = nextPhase,
history = state.history + transition
)
}
}Bind state fields to blackboard automatically.
@Agent(
name = "blackboard-aware",
provider = "processing",
description = "Uses blackboard for state"
)
class BlackboardAwareAgent {
@State
data class ProcessingContext(
val sessionId: String,
val configuration: Config,
val metrics: Metrics
)
@Action(description = "Initialize with blackboard binding")
fun initialize(request: Request, context: ActionContext): ProcessingContext {
val state = ProcessingContext(
sessionId = UUID.randomUUID().toString(),
configuration = Config.default(),
metrics = Metrics.empty()
)
// Bind state fields to blackboard
context.put("sessionId", state.sessionId)
context.put("configuration", state.configuration)
context.put("metrics", state.metrics)
return state
}
@Action(description = "Update metrics")
fun updateMetrics(
state: ProcessingContext,
update: MetricUpdate,
context: ActionContext
): ProcessingContext {
val updatedMetrics = state.metrics.update(update)
// Update blackboard
context.put("metrics", updatedMetrics)
return state.copy(metrics = updatedMetrics)
}
}Define terminal states that end agent execution.
@Agent(
name = "lifecycle-agent",
provider = "workflow",
description = "Agent with terminal states"
)
class LifecycleAgent {
@State
data class Active(
val taskId: String,
val progress: Int
)
@State
data class Succeeded(
val taskId: String,
val result: Any,
val completedAt: Instant
) // Terminal state
@State
data class Failed(
val taskId: String,
val error: String,
val failedAt: Instant
) // Terminal state
@Action(description = "Process task")
fun processTask(active: Active, context: ActionContext): State {
return try {
val result = processor.process(active.taskId)
Succeeded(
taskId = active.taskId,
result = result,
completedAt = Instant.now()
)
} catch (e: Exception) {
Failed(
taskId = active.taskId,
error = e.message ?: "Unknown error",
failedAt = Instant.now()
)
}
}
}Emit events on state transitions.
@Agent(
name = "event-emitting-agent",
provider = "processing",
description = "Emits events on state transitions"
)
class EventEmittingAgent {
@State
data class OrderState(
val orderId: String,
val status: OrderStatus
)
enum class OrderStatus {
CREATED,
PROCESSING,
COMPLETED,
CANCELLED
}
@Action(description = "Transition order state")
fun transitionOrder(
state: OrderState,
newStatus: OrderStatus,
context: ExecutingOperationContext
): OrderState {
// Emit state transition event
context.sendOutputChannelEvent(
OrderStateChangedEvent(
orderId = state.orderId,
fromStatus = state.status,
toStatus = newStatus
)
)
// Update progress
context.updateProgress("Order ${state.orderId} transitioned to $newStatus")
return state.copy(status = newStatus)
}
}
data class OrderStateChangedEvent(
val orderId: String,
val fromStatus: OrderStatus,
val toStatus: OrderStatus
) : OutputChannelEventinterface State {
// Marker interface for state classes
}
data class StateTransitionEvent(
override val processId: String,
val stateDescription: String,
val fromState: Any?,
val toState: Any
) : AgentProcessEvent
interface OutputChannelEvent {
// Marker interface for output events
}Install with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-apidocs