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
The Embabel Agent framework supports two planning algorithms (GOAP and condition-based) and provides workflow primitives for parallel processing, consensus building, and iteration.
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
}
}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)
}
}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
}
}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")
}
}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.
""")
}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)
}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")
}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
""")
}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)
}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@0.3.0docs