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
Human-in-the-loop (HITL) capabilities allow agents to request human input, confirmation, or approval during execution. This enables semi-autonomous workflows where humans maintain oversight and control.
Configure how much autonomy an agent has.
enum class Autonomy {
/** Agent operates without human interaction */
FULLY_AUTONOMOUS,
/** Human can observe and interact but agent proceeds automatically */
HUMAN_IN_LOOP,
/** Human must approve actions before execution */
HUMAN_APPROVAL_REQUIRED
}Autonomy Configuration:
// Set autonomy level when invoking agent
val result = AgentInvocation.create<Result>(agentPlatform)
.withAgentName("processor")
.withInput(data)
.withAutonomy(Autonomy.HUMAN_IN_LOOP)
.execute()
// For sensitive operations, require approval
val criticalResult = AgentInvocation.create<CriticalResult>(agentPlatform)
.withAgentName("critical-processor")
.withInput(sensitiveData)
.withAutonomy(Autonomy.HUMAN_APPROVAL_REQUIRED)
.execute()Suspend action execution until external response is received.
suspend fun <P> waitFor(awaitable: Awaitable<P, *>): PwaitFor Example:
@Action(description = "Process with human review")
suspend fun processWithReview(
document: Document,
context: ActionContext
): ProcessedDocument {
// Initial processing
val draft = initialProcess(document)
// Wait for human review
val reviewRequest = ReviewRequest(draft, "Please review the processed document")
val review = waitFor<Review>(reviewRequest)
// Apply review feedback
return if (review.approved) {
finalize(draft)
} else {
revise(draft, review.comments)
}
}Request user confirmation for an action or decision.
fun <P> confirm(what: P, description: String): Pconfirm Example:
@Action(description = "Delete resource with confirmation")
fun deleteResource(
resourceId: String,
context: ActionContext
): DeletionResult {
val resource = resourceRepository.findById(resourceId)
// Request confirmation
val confirmed = confirm(
what = resource,
description = "Are you sure you want to delete this resource? This cannot be undone."
)
return if (confirmed != null) {
resourceRepository.delete(resourceId)
DeletionResult.success(resourceId)
} else {
DeletionResult.cancelled()
}
}
@Action(description = "Execute critical operation")
fun executeCriticalOperation(
operation: CriticalOperation,
context: ActionContext
): OperationResult {
// Confirm before execution
confirm(
what = operation,
description = "This operation will affect ${operation.affectedUsers} users. Proceed?"
)
// Execute if confirmed
return operationExecutor.execute(operation)
}Request structured data from user via form.
fun <P> fromForm(title: String): PfromForm Example:
data class DeploymentConfig(
val environment: String,
val version: String,
val rollbackEnabled: Boolean,
val notificationEmails: List<String>
)
@Action(description = "Deploy with user-provided configuration")
fun deployWithConfig(
application: Application,
context: ActionContext
): Deployment {
// Request configuration via form
val config = fromForm<DeploymentConfig>("Deployment Configuration")
// Validate configuration
validateConfig(config)
// Execute deployment
return deploymentService.deploy(application, config)
}
data class CustomerInquiry(
val inquiryType: String,
val priority: String,
val details: String
)
@Action(description = "Handle customer inquiry")
fun handleInquiry(context: ActionContext): InquiryResponse {
// Get inquiry details from user
val inquiry = fromForm<CustomerInquiry>("Customer Inquiry Form")
// Process based on type
return when (inquiry.inquiryType) {
"technical" -> technicalSupport.handle(inquiry)
"billing" -> billingSupport.handle(inquiry)
else -> generalSupport.handle(inquiry)
}
}Generic interface for awaitable responses.
interface Awaitable<P, T> {
/** Type of value being awaited */
val valueType: Class<P>
/** Optional timeout */
val timeout: Duration?
/** Callback when value is provided */
fun onResponse(response: T): P
}Built-in awaitable for confirmation requests.
data class ConfirmationRequest(
val what: Any,
val description: String,
override val timeout: Duration? = null
) : Awaitable<Boolean, Boolean> {
override val valueType: Class<Boolean> = Boolean::class.java
override fun onResponse(response: Boolean): Boolean = response
}ConfirmationRequest Example:
@Action(description = "Process with explicit confirmation")
fun processWithConfirmation(
data: SensitiveData,
context: ActionContext
): ProcessingResult = runBlocking {
val confirmationRequest = ConfirmationRequest(
what = data,
description = "Process sensitive data containing PII?",
timeout = Duration.ofMinutes(5)
)
val approved = waitFor<Boolean>(confirmationRequest)
if (approved) {
processor.process(data)
} else {
ProcessingResult.cancelled()
}
}Built-in awaitable for form-based input.
data class FormBindingRequest<T>(
val title: String,
val type: Class<T>,
override val timeout: Duration? = null
) : Awaitable<T, T> {
override val valueType: Class<T> = type
override fun onResponse(response: T): T = response
}FormBindingRequest Example:
data class ApprovalRequest(
val requestId: String,
val requester: String,
val resource: String,
val justification: String
)
@Action(description = "Handle access request")
fun handleAccessRequest(context: ActionContext) = runBlocking {
// Request approval details via form
val formRequest = FormBindingRequest(
title = "Access Request Approval",
type = ApprovalRequest::class.java,
timeout = Duration.ofHours(24)
)
val approval = waitFor<ApprovalRequest>(formRequest)
// Process approval
accessManager.processApproval(approval)
}Control flow exception for awaiting responses.
class AwaitableResponseException(
val awaitable: Awaitable<*, *>
) : SpecialReturnException() {
override fun handle(actionContext: ActionContext): Any {
// Framework handles awaiting and resuming
return waitForResponse(awaitable)
}
}Create custom awaitable types for specific scenarios.
data class CodeReviewRequest(
val pullRequest: PullRequest,
val reviewers: List<String>
) : Awaitable<CodeReview, CodeReview> {
override val valueType = CodeReview::class.java
override val timeout = Duration.ofDays(2)
override fun onResponse(response: CodeReview): CodeReview = response
}
data class CodeReview(
val approved: Boolean,
val comments: List<String>,
val requestedChanges: List<String>
)
@Action(description = "Merge with review")
fun mergeWithReview(pr: PullRequest, context: ActionContext) = runBlocking {
// Request code review
val reviewRequest = CodeReviewRequest(
pullRequest = pr,
reviewers = listOf("tech-lead", "senior-dev")
)
val review = waitFor<CodeReview>(reviewRequest)
if (review.approved) {
gitService.merge(pr)
} else {
gitService.requestChanges(pr, review.requestedChanges)
}
}Mark types as awaitable for documentation and tooling.
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class WaitFor(
val value: String
)WaitFor Annotation Example:
@WaitFor("manual-approval")
data class ManualApproval(
val approver: String,
val decision: Decision,
val notes: String
)
enum class Decision {
APPROVED,
REJECTED,
NEEDS_MORE_INFO
}Implement complex approval workflows.
@Action(description = "Multi-stage approval process")
suspend fun multiStageApproval(
request: ApprovalRequest,
context: ActionContext
): ApprovalResult {
// Stage 1: Manager approval
val managerApproval = waitFor<Approval>(
ConfirmationRequest(
what = request,
description = "Manager: Approve request?"
)
)
if (!managerApproval) {
return ApprovalResult.rejected("Rejected by manager")
}
// Stage 2: For high-value requests, get executive approval
if (request.value > 10000) {
val executiveApproval = waitFor<Approval>(
ConfirmationRequest(
what = request,
description = "Executive: Approve high-value request?"
)
)
if (!executiveApproval) {
return ApprovalResult.rejected("Rejected by executive")
}
}
// Stage 3: Compliance check
val complianceForm = waitFor<ComplianceCheck>(
FormBindingRequest(
title = "Compliance Verification",
type = ComplianceCheck::class.java
)
)
if (!complianceForm.passed) {
return ApprovalResult.rejected("Failed compliance check")
}
// All approvals obtained
return ApprovalResult.approved()
}Handle timeouts in awaitable operations.
@Action(description = "Process with timeout")
fun processWithTimeout(
data: Data,
context: ActionContext
): ProcessingResult = runBlocking {
try {
val confirmationRequest = ConfirmationRequest(
what = data,
description = "Approve processing?",
timeout = Duration.ofMinutes(5)
)
val approved = waitFor<Boolean>(confirmationRequest)
if (approved) {
processor.process(data)
} else {
ProcessingResult.cancelled()
}
} catch (e: TimeoutException) {
// Handle timeout
logger.warn("Approval request timed out")
ProcessingResult.timeout()
}
}Configuration for agent execution model.
interface AgentProcessExecution {
val autonomy: Autonomy
val outputChannel: OutputChannel
val eventListener: AgenticEventListener?
}List and display plans for human review.
interface PlanLister {
fun listPlan(plan: Plan): String
}
class DefaultPlanLister : PlanLister {
override fun listPlan(plan: Plan): String {
return plan.steps.joinToString("\n") { step ->
"${step.name}: ${step.description}"
}
}
}Approve goal selections in multi-goal scenarios.
interface GoalChoiceApprover {
fun approveGoal(goals: List<Goal>): Goal?
}sealed class SpecialReturnException : RuntimeException() {
abstract fun handle(actionContext: ActionContext): Any
}
interface OutputChannel {
fun send(event: OutputChannelEvent)
}
data class ProcessingResult(
val status: Status,
val data: Any?
) {
companion object {
fun success(data: Any) = ProcessingResult(Status.SUCCESS, data)
fun cancelled() = ProcessingResult(Status.CANCELLED, null)
fun timeout() = ProcessingResult(Status.TIMEOUT, null)
}
enum class Status {
SUCCESS,
CANCELLED,
TIMEOUT,
FAILED
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-apidocs