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

planning-workflows.mddocs/

Planning and Workflows

The Embabel Agent framework supports two planning algorithms (GOAP and condition-based) and provides workflow primitives for parallel processing, consensus building, and iteration.

Capabilities

Planner Types

Configure the planning algorithm for your agent.

enum class PlannerType(val needsGoals: Boolean) {
    /** Goal-Oriented Action Planning (default planner) */
    GOAP(needsGoals = true),

    /** Utility AI planning */
    UTILITY(needsGoals = false),

    /** Supervisor planner */
    SUPERVISOR(needsGoals = true)
}

Planner Configuration:

// GOAP planning (goal-driven)
@Agent(
    name = "task-planner",
    provider = "automation",
    description = "Plans tasks to achieve goals",
    planner = PlannerType.GOAP
)
class TaskPlannerAgent {
    @Action(
        description = "Fetch data",
        post = ["data_fetched"]
    )
    fun fetchData(): Data = dataService.fetch()

    @Action(
        description = "Process data",
        pre = ["data_fetched"],
        post = ["data_processed"]
    )
    fun processData(data: Data): ProcessedData = processor.process(data)

    @Action(description = "Generate report")
    @AchievesGoal(description = "Report generated from data")
    fun generateReport(data: ProcessedData): Report = reportGenerator.generate(data)
}

// Condition-based planning (reactive)
@Agent(
    name = "incident-handler",
    provider = "ops",
    description = "Handles incidents based on conditions",
    planner = PlannerType.UTILITY
)
class IncidentHandlerAgent {
    @Action(
        description = "Detect incident",
        post = ["incident_detected"]
    )
    fun detectIncident(): Incident = incidentDetector.detect()

    @Action(
        description = "Alert team",
        pre = ["incident_detected", "severity_high"]
    )
    fun alertTeam(incident: Incident) = notifier.alert(incident)

    @Condition(name = "severity_high")
    fun isSeverityHigh(context: OperationContext): Boolean {
        return context.last(Incident::class.java)?.severity == Severity.HIGH
    }
}

GOAP Planning

Goal-Oriented Action Planning uses A* search to find optimal action sequences.

// GOAP finds the shortest path from current state to goal state
// based on preconditions, postconditions, and action costs

@Agent(
    name = "deployment-agent",
    provider = "devops",
    description = "Deploys applications",
    planner = PlannerType.GOAP
)
class DeploymentAgent {

    @Action(
        description = "Run tests",
        cost = 0.3,
        post = ["tests_passed"]
    )
    fun runTests(): TestResult = testRunner.run()

    @Action(
        description = "Build application",
        pre = ["tests_passed"],
        cost = 0.5,
        post = ["build_complete"]
    )
    fun build(): BuildArtifact = buildService.build()

    @Action(
        description = "Deploy to staging",
        pre = ["build_complete"],
        cost = 0.2,
        post = ["deployed_to_staging"]
    )
    fun deployStaging(artifact: BuildArtifact): Deployment {
        return deployService.deploy(artifact, Environment.STAGING)
    }

    @Action(
        description = "Run smoke tests",
        pre = ["deployed_to_staging"],
        cost = 0.2,
        post = ["smoke_tests_passed"]
    )
    fun smokeTests(): TestResult = smokeTestRunner.run()

    @Action(
        description = "Deploy to production",
        pre = ["smoke_tests_passed"]
    )
    @AchievesGoal(
        description = "Application deployed to production",
        value = 1.0
    )
    fun deployProduction(artifact: BuildArtifact): Deployment {
        return deployService.deploy(artifact, Environment.PRODUCTION)
    }
}

Condition-Based Planning

Plan based on condition evaluation without explicit goals.

@Agent(
    name = "adaptive-processor",
    provider = "processing",
    description = "Adapts processing based on conditions",
    planner = PlannerType.UTILITY
)
class AdaptiveProcessorAgent {

    @Action(
        description = "Load data",
        post = ["data_loaded"]
    )
    fun loadData(): Dataset = dataLoader.load()

    @Action(
        description = "Quick process (for small datasets)",
        pre = ["data_loaded", "dataset_small"]
    )
    fun quickProcess(dataset: Dataset): Result {
        return quickProcessor.process(dataset)
    }

    @Action(
        description = "Parallel process (for large datasets)",
        pre = ["data_loaded", "dataset_large"]
    )
    fun parallelProcess(dataset: Dataset, context: ActionContext): Result {
        return context.parallelMap(dataset.chunks, maxConcurrency = 10) { chunk ->
            intensiveProcessor.process(chunk)
        }.let { results -> Result.merge(results) }
    }

    @Condition(name = "dataset_small")
    fun isSmall(context: OperationContext): Boolean {
        return context.last(Dataset::class.java)?.size ?: 0 < 1000
    }

    @Condition(name = "dataset_large")
    fun isLarge(context: OperationContext): Boolean {
        return context.last(Dataset::class.java)?.size ?: 0 >= 1000
    }
}

ScatterGather Pattern

Parallel processing pattern for distributing work and collecting results.

interface ScatterGather<ELEMENT, RESULT> {
    /** Scatter work across parallel executions */
    fun scatter(elements: List<ELEMENT>): ScatterGatherBuilder<ELEMENT, RESULT>

    /** Gather results from parallel executions */
    fun gather(): List<RESULT>
}

interface ScatterGatherBuilder<ELEMENT, RESULT> {
    /** Process each element */
    fun process(transform: (ELEMENT) -> RESULT): ScatterGatherBuilder<ELEMENT, RESULT>

    /** Set max concurrency */
    fun withConcurrency(maxConcurrency: Int): ScatterGatherBuilder<ELEMENT, RESULT>

    /** Execute and gather results */
    fun gather(): List<RESULT>
}

ScatterGather Example:

@Action(description = "Process documents in parallel")
fun processDocuments(
    documents: List<Document>,
    context: ActionContext
): List<ProcessedDocument> {
    return context.parallelMap(documents, maxConcurrency = 5) { doc ->
        // Each document processed in parallel
        val analysis = context.promptRunner()
            .createObject<DocumentAnalysis>("Analyze: ${doc.content}")

        ProcessedDocument(doc, analysis)
    }
}

@Action(description = "Generate summaries for multiple topics")
fun generateSummaries(
    topics: List<String>,
    context: ActionContext
): List<Summary> {
    // Scatter: Process each topic in parallel with LLM
    return context.parallelMap(topics, maxConcurrency = 3) { topic ->
        context.promptRunner()
            .withSystemPrompt("You are a technical writer.")
            .createObject<Summary>("Write a summary about: $topic")
    }
}

ConsensusBuilder

Build consensus from multiple LLM responses.

interface ConsensusBuilder<RESULT> {
    /** Add model to consensus */
    fun withModel(model: String): ConsensusBuilder<RESULT>

    /** Add multiple models */
    fun withModels(models: List<String>): ConsensusBuilder<RESULT>

    /** Set consensus strategy */
    fun withStrategy(strategy: ConsensusStrategy): ConsensusBuilder<RESULT>

    /** Build consensus */
    fun build(prompt: String): RESULT
}

enum class ConsensusStrategy {
    MAJORITY_VOTE,
    WEIGHTED_AVERAGE,
    LLM_ARBITRATION
}

Consensus Example:

@Action(description = "Get consensus analysis")
fun getConsensusAnalysis(
    data: ComplexData,
    context: ActionContext
): Analysis {
    // Get responses from multiple models
    val prompt = "Analyze this data: ${data.summary}"

    val claude = context.ai()
        .withLlm(LlmOptions.model(AnthropicModels.CLAUDE_3_OPUS))
        .createObject<Analysis>(prompt)

    val gpt4 = context.ai()
        .withLlm(LlmOptions.model(OpenAiModels.GPT_4))
        .createObject<Analysis>(prompt)

    val gemini = context.ai()
        .withLlm(LlmOptions.model(GeminiModels.GEMINI_PRO))
        .createObject<Analysis>(prompt)

    // Use another LLM to build consensus
    return context.promptRunner()
        .createObject<Analysis>("""
            Three AI models analyzed the data. Build consensus:

            Model 1 (Claude): ${claude.summary}
            Model 2 (GPT-4): ${gpt4.summary}
            Model 3 (Gemini): ${gemini.summary}

            Provide a consensus analysis considering all viewpoints.
        """)
}

RepeatUntil Pattern

Repeat actions until a condition is met.

interface RepeatUntil {
    /** Repeat this action */
    fun repeatAction(action: () -> Any): RepeatUntilBuilder

    /** Until this condition is true */
    fun until(condition: () -> Boolean): Any
}

interface RepeatUntilBuilder {
    /** Set maximum iterations */
    fun maxIterations(max: Int): RepeatUntilBuilder

    /** Set condition */
    fun until(condition: () -> Boolean): Any
}

RepeatUntil Example:

@Action(description = "Refine until acceptable")
fun refineUntilAcceptable(
    draft: Draft,
    context: ActionContext
): RefinedDraft {
    var current = draft
    var iterations = 0
    val maxIterations = 5

    while (iterations < maxIterations) {
        // Check if acceptable
        val evaluation = context.promptRunner()
            .createObject<Evaluation>("Evaluate quality: ${current.content}")

        if (evaluation.isAcceptable) {
            break
        }

        // Refine
        current = context.promptRunner()
            .createObject<Draft>("""
                Improve this draft:
                ${current.content}

                Issues: ${evaluation.issues.joinToString()}
            """)

        iterations++
    }

    return RefinedDraft(current, iterations)
}

RepeatUntilAcceptable Pattern

Specialized pattern for iterative refinement.

interface RepeatUntilAcceptable<RESULT> {
    /** Initial attempt */
    fun attempt(generator: () -> RESULT): RepeatUntilAcceptableBuilder<RESULT>

    /** Acceptance criteria */
    fun acceptable(validator: (RESULT) -> Boolean): RESULT
}

interface RepeatUntilAcceptableBuilder<RESULT> {
    /** Set max attempts */
    fun maxAttempts(max: Int): RepeatUntilAcceptableBuilder<RESULT>

    /** Provide feedback for refinement */
    fun withFeedback(feedback: (RESULT) -> String): RepeatUntilAcceptableBuilder<RESULT>

    /** Set acceptance criteria */
    fun acceptable(validator: (RESULT) -> Boolean): RESULT
}

RepeatUntilAcceptable Example:

@Action(description = "Generate acceptable response")
fun generateAcceptableResponse(
    question: String,
    context: ActionContext
): Response {
    var attempt = 0
    val maxAttempts = 3

    while (attempt < maxAttempts) {
        val response = context.promptRunner()
            .createObject<Response>("Answer: $question")

        // Validate response
        val validation = validateResponse(response)

        if (validation.isAcceptable) {
            return response
        }

        // Provide feedback for next attempt
        context.promptRunner()
            .withSystemPrompt("""
                Previous answer was not acceptable.
                Issues: ${validation.issues.joinToString()}
                Please revise.
            """)

        attempt++
    }

    throw IllegalStateException("Could not generate acceptable response after $maxAttempts attempts")
}

Feedback Loops

Implement feedback loops for iterative improvement.

interface Feedback {
    fun provide(result: Any): String
}

Feedback Loop Example:

@Action(description = "Generate with feedback loop")
fun generateWithFeedback(
    requirements: Requirements,
    context: ActionContext
): Solution {
    var solution = generateInitial(requirements, context)
    var feedback = evaluateSolution(solution, context)

    while (!feedback.isAcceptable && feedback.improvementPossible) {
        solution = context.promptRunner()
            .createObject<Solution>("""
                Improve solution based on feedback:

                Current solution: ${solution.description}
                Feedback: ${feedback.comments}
                Suggestions: ${feedback.suggestions.joinToString()}
            """)

        feedback = evaluateSolution(solution, context)
    }

    return solution
}

fun evaluateSolution(
    solution: Solution,
    context: ActionContext
): Feedback {
    return context.promptRunner()
        .createObject<Feedback>("""
            Evaluate this solution:
            ${solution.description}

            Check: correctness, completeness, efficiency
        """)
}

WorkflowBuilder

Build complex workflows with multiple steps.

interface WorkflowBuilderReturning<T> {
    fun step(action: () -> Any): WorkflowBuilderReturning<T>
    fun conditionalStep(condition: () -> Boolean, action: () -> Any): WorkflowBuilderReturning<T>
    fun parallelSteps(actions: List<() -> Any>): WorkflowBuilderReturning<T>
    fun execute(): T
}

interface WorkflowBuilderConsuming {
    fun step(action: () -> Unit): WorkflowBuilderConsuming
    fun conditionalStep(condition: () -> Boolean, action: () -> Unit): WorkflowBuilderConsuming
    fun execute()
}

Workflow Example:

@Action(description = "Execute complex workflow")
fun executeWorkflow(
    input: WorkflowInput,
    context: ActionContext
): WorkflowResult {
    // Step 1: Validate input
    val validation = validateInput(input)
    if (!validation.isValid) {
        throw IllegalArgumentException("Invalid input: ${validation.errors}")
    }

    // Step 2: Process data in parallel
    val results = context.parallelMap(input.dataSources) { source ->
        processDataSource(source, context)
    }

    // Step 3: Aggregate results
    val aggregated = aggregateResults(results)

    // Step 4: Generate report (conditionally)
    val report = if (input.generateReport) {
        context.promptRunner()
            .createObject<Report>("Generate report from: ${aggregated.summary}")
    } else {
        null
    }

    // Step 5: Notify stakeholders
    notifyStakeholders(aggregated, report)

    return WorkflowResult(aggregated, report)
}

Types

interface Plan {
    val steps: List<Action>
    val cost: Double
    val goalAchieved: Goal?
}

interface Planner {
    fun formulate(worldState: WorldState, goal: Goal): Plan
}

interface WorldState {
    fun conditions(): Set<String>
}

interface ConsensusStrategy {
    fun <T> buildConsensus(results: List<T>): T
}

Install with Tessl CLI

npx tessl i tessl/maven-com-embabel-agent--embabel-agent-api

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