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

human-in-the-loop.mddocs/

Human-in-the-Loop

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.

Capabilities

Autonomy Levels

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

waitFor Function

Suspend action execution until external response is received.

suspend fun <P> waitFor(awaitable: Awaitable<P, *>): P

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

confirm Function

Request user confirmation for an action or decision.

fun <P> confirm(what: P, description: String): P

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

fromForm Function

Request structured data from user via form.

fun <P> fromForm(title: String): P

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

Awaitable Interface

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
}

ConfirmationRequest

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

FormBindingRequest

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

AwaitableResponseException

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

Custom Awaitable Types

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

WaitFor Annotation

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
}

Multi-Step Approval Workflow

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

Timeout Handling

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

AgentProcessExecution

Configuration for agent execution model.

interface AgentProcessExecution {
    val autonomy: Autonomy
    val outputChannel: OutputChannel
    val eventListener: AgenticEventListener?
}

PlanLister and DefaultPlanLister

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

GoalChoiceApprover

Approve goal selections in multi-goal scenarios.

interface GoalChoiceApprover {
    fun approveGoal(goals: List<Goal>): Goal?
}

Types

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