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

state-management.mddocs/

State Management

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.

Capabilities

State Annotation

Mark classes as agent states.

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class State

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

State Transitions

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

State-Specific Actions

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

State with Provided Context

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

Conditional State Transitions

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
}

State History Tracking

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

State with Blackboard Binding

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

Terminal States

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

State Event Emission

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

Types

interface 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-api@0.3.0

docs

actions-goals.md

agent-definition.md

builtin-tools.md

chat.md

conditions.md

events.md

human-in-the-loop.md

index.md

invocation.md

io-binding.md

llm-interaction.md

models.md

planning-workflows.md

platform-management.md

runtime-context.md

state-management.md

streaming.md

subagents.md

tools.md

type-system.md

typed-operations.md

validation.md

tile.json