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
Subagents enable hierarchical agent composition where agents can invoke other specialized agents to handle specific tasks. This supports delegation patterns and complex multi-agent workflows.
Static methods for executing subagents.
object RunSubagent {
/** Execute agent instance as subagent */
fun instance(agent: Agent): SubagentExecutionRequest
/** Execute agent from annotated instance */
fun fromAnnotatedInstance(instance: Any): SubagentExecutionRequest
/** Execute with typed input (Kotlin) */
inline fun <reified I> instance(agent: Agent, input: I): SubagentExecutionRequest
/** Execute with typed input and output (Kotlin) */
inline fun <reified I, reified O> instance(agent: Agent, input: I): SubagentExecutionRequest
}Basic Subagent Execution:
@Agent(name = "orchestrator", provider = "workflow", description = "Orchestrates workflow")
class OrchestratorAgent(
private val dataProcessorAgent: Agent,
private val reportGeneratorAgent: Agent
) {
@Action(description = "Execute complete workflow")
fun executeWorkflow(rawData: RawData): WorkflowResult {
// Delegate to data processor subagent
RunSubagent.instance(dataProcessorAgent)
// The framework handles the subagent execution
// and returns control here after completion
return WorkflowResult.success()
}
}Control flow exception that triggers subagent execution.
class SubagentExecutionRequest(
val agent: Agent,
val input: Any?,
val outputType: Class<*>?
) : SpecialReturnException() {
override fun handle(actionContext: ActionContext): Any {
// Framework executes subagent and returns result
return executeSubagent(agent, input, outputType, actionContext)
}
}Execute subagents via context methods.
fun <O> ExecutingOperationContext.asSubProcess(
outputClass: Class<O>,
agent: Agent
): O
fun <O> ExecutingOperationContext.asSubProcess(
outputClass: Class<O>,
agentScopeBuilder: AgentScopeBuilder
): O
// Kotlin extension with reified generics
inline fun <reified O> ExecutingOperationContext.asSubProcess(
agent: Agent
): OasSubProcess Example:
@Agent(name = "supervisor", provider = "management", description = "Supervises processing")
class SupervisorAgent(
private val validatorAgent: Agent,
private val enrichmentAgent: Agent,
private val transformerAgent: Agent
) {
@Action(description = "Process with subagents")
fun processData(data: RawData, context: ExecutingOperationContext): ProcessedData {
// Step 1: Validate using subagent
val validation = context.asSubProcess(
outputClass = ValidationResult::class.java,
agent = validatorAgent
)
if (!validation.isValid) {
throw ValidationException(validation.errors)
}
// Step 2: Enrich data using subagent
val enriched = context.asSubProcess(
outputClass = EnrichedData::class.java,
agent = enrichmentAgent
)
// Step 3: Transform using subagent
return context.asSubProcess(
outputClass = ProcessedData::class.java,
agent = transformerAgent
)
}
}Reference subagents for configuration and invocation.
data class Subagent(
val agent: Agent?,
val agentName: String?,
val agentType: Class<*>?,
val inputClass: Class<*>
) {
companion object {
/** Create from agent instance */
operator fun invoke(agent: Agent, inputClass: Class<*>): Subagent
/** Create from agent name */
operator fun invoke(agentName: String, inputClass: Class<*>): Subagent
/** Create from agent type */
operator fun invoke(agentType: Class<*>, inputClass: Class<*>): Subagent
}
/** Resolve agent from platform */
fun resolve(agentPlatform: AgentPlatform): Agent
}Subagent Reference Example:
val dataProcessorSubagent = Subagent(
agent = dataProcessorAgent,
inputClass = Dataset::class.java
)
val reportGeneratorSubagent = Subagent(
agentName = "report-generator",
inputClass = ProcessedData::class.java
)
val analyzerSubagent = Subagent(
agentType = AnalyzerAgent::class.java,
inputClass = RawData::class.java
)Delegate specific tasks to specialized agents.
@Agent(name = "customer-service", provider = "support", description = "Customer service")
class CustomerServiceAgent(
private val technicalSupportAgent: Agent,
private val billingAgent: Agent,
private val generalSupportAgent: Agent
) {
@Action(description = "Handle customer inquiry")
fun handleInquiry(
inquiry: CustomerInquiry,
context: ExecutingOperationContext
): InquiryResponse {
// Route to appropriate specialist agent
return when (inquiry.category) {
InquiryCategory.TECHNICAL -> {
context.asSubProcess(
outputClass = InquiryResponse::class.java,
agent = technicalSupportAgent
)
}
InquiryCategory.BILLING -> {
context.asSubProcess(
outputClass = InquiryResponse::class.java,
agent = billingAgent
)
}
else -> {
context.asSubProcess(
outputClass = InquiryResponse::class.java,
agent = generalSupportAgent
)
}
}
}
}Chain multiple subagents in a processing pipeline.
@Agent(name = "pipeline-coordinator", provider = "processing", description = "Coordinates pipeline")
class PipelineCoordinatorAgent(
private val extractorAgent: Agent,
private val transformerAgent: Agent,
private val loaderAgent: Agent
) {
@Action(description = "Execute ETL pipeline")
fun executePipeline(
source: DataSource,
context: ExecutingOperationContext
): LoadResult {
context.updateProgress("Starting ETL pipeline")
// Extract
context.updateProgress("Extracting data...")
val extracted = context.asSubProcess<ExtractedData>(extractorAgent)
// Transform
context.updateProgress("Transforming data...")
val transformed = context.asSubProcess<TransformedData>(transformerAgent)
// Load
context.updateProgress("Loading data...")
return context.asSubProcess<LoadResult>(loaderAgent)
}
}Execute multiple subagents in parallel.
@Agent(name = "parallel-processor", provider = "processing", description = "Parallel processor")
class ParallelProcessorAgent(
private val agentPlatform: AgentPlatform
) {
@Action(description = "Process with parallel subagents")
fun processParallel(
data: ComplexData,
context: OperationContext
): AggregatedResult {
// Define subagents
val analysisAgent = agentPlatform.getAgent("analysis-agent")!!
val validationAgent = agentPlatform.getAgent("validation-agent")!!
val enrichmentAgent = agentPlatform.getAgent("enrichment-agent")!!
// Execute in parallel using fireAgent
val analysisFuture = context.fireAgent(data, AnalysisResult::class.java)
val validationFuture = context.fireAgent(data, ValidationResult::class.java)
val enrichmentFuture = context.fireAgent(data, EnrichmentResult::class.java)
// Wait for all to complete
val analysis = analysisFuture?.join()
val validation = validationFuture?.join()
val enrichment = enrichmentFuture?.join()
return AggregatedResult(analysis, validation, enrichment)
}
}Build agent scopes for dynamic subagent creation.
interface AgentScopeBuilder {
fun withAction(action: Action): AgentScopeBuilder
fun withGoal(goal: Goal): AgentScopeBuilder
fun withCondition(condition: Condition): AgentScopeBuilder
fun build(): AgentScope
}
interface AgentScope {
val actions: List<Action>
val goals: List<Goal>
val conditions: List<Condition>
}Dynamic Subagent Example:
@Action(description = "Execute dynamic subagent")
fun executeDynamic(
task: Task,
context: ExecutingOperationContext
): TaskResult {
// Build agent scope dynamically
val scopeBuilder = AgentScopeBuilder()
.withAction(createValidationAction(task))
.withAction(createProcessingAction(task))
.withGoal(createCompletionGoal(task))
// Execute as subprocess
return context.asSubProcess(
outputClass = TaskResult::class.java,
agentScopeBuilder = scopeBuilder
)
}Pass context and state to subagents.
@Agent(name = "context-aware-coordinator", provider = "coordination", description = "Context-aware coordinator")
class ContextAwareCoordinatorAgent(
private val processorAgent: Agent
) {
@Action(description = "Process with context")
fun processWithContext(
data: Data,
context: ExecutingOperationContext
): ProcessedData {
// Add context to blackboard
context.put("processing_mode", "strict")
context.put("validation_level", "high")
context.put("timestamp", Instant.now())
// Subagent will have access to blackboard context
return context.asSubProcess(
outputClass = ProcessedData::class.java,
agent = processorAgent
)
}
}Handle subagent failures gracefully.
@Agent(name = "resilient-coordinator", provider = "coordination", description = "Resilient coordinator")
class ResilientCoordinatorAgent(
private val primaryAgent: Agent,
private val fallbackAgent: Agent
) {
@Action(description = "Process with fallback")
fun processWithFallback(
data: Data,
context: ExecutingOperationContext
): ProcessResult {
return try {
// Try primary agent
context.asSubProcess(
outputClass = ProcessResult::class.java,
agent = primaryAgent
)
} catch (e: Exception) {
logger.warn("Primary agent failed, using fallback", e)
// Fall back to alternative agent
context.asSubProcess(
outputClass = ProcessResult::class.java,
agent = fallbackAgent
)
}
}
}Create hierarchical agent structures.
@Agent(name = "top-level-coordinator", provider = "coordination", description = "Top coordinator")
class TopLevelCoordinatorAgent(
private val midLevelAgent: Agent
) {
@Action(description = "Execute hierarchical workflow")
fun executeHierarchy(
request: WorkflowRequest,
context: ExecutingOperationContext
): WorkflowResult {
// Top level delegates to mid level
// Mid level may delegate to specialists
return context.asSubProcess(
outputClass = WorkflowResult::class.java,
agent = midLevelAgent
)
}
}
@Agent(name = "mid-level-coordinator", provider = "coordination", description = "Mid coordinator")
class MidLevelCoordinatorAgent(
private val specialistAgent1: Agent,
private val specialistAgent2: Agent
) {
@Action(description = "Coordinate specialists")
fun coordinateSpecialists(
request: WorkflowRequest,
context: ExecutingOperationContext
): WorkflowResult {
// Execute specialist 1
val result1 = context.asSubProcess<SpecialistResult1>(specialistAgent1)
// Execute specialist 2
val result2 = context.asSubProcess<SpecialistResult2>(specialistAgent2)
// Combine results
return WorkflowResult(result1, result2)
}
}Share data between parent and subagents via blackboard.
@Agent(name = "parent-agent", provider = "coordination", description = "Parent agent")
class ParentAgent(
private val childAgent: Agent
) {
@Action(description = "Communicate with subagent")
fun communicateWithSubagent(
data: Data,
context: ExecutingOperationContext
): Result {
// Set up shared state
context.put("parent_context", ParentContext(mode = "production"))
context.put("configuration", Config(strict = true))
// Child agent can access these values from its context
val childResult = context.asSubProcess<ChildResult>(childAgent)
// Child agent may have added values to blackboard
val childMetadata = context.last(ChildMetadata::class.java)
return Result(childResult, childMetadata)
}
}
@Agent(name = "child-agent", provider = "processing", description = "Child agent")
class ChildAgent {
@Action(description = "Process with parent context")
fun process(data: Data, context: ActionContext): ChildResult {
// Access parent's context
val parentContext = context.get("parent_context", ParentContext::class.java)
val config = context.get("configuration", Config::class.java)
// Process using parent's configuration
val result = processor.process(data, config)
// Add metadata for parent
context.put("child_metadata", ChildMetadata(processingTime = 1000))
return result
}
}sealed class SpecialReturnException : RuntimeException() {
abstract fun handle(actionContext: ActionContext): Any
}
interface Agent {
val name: String
val description: String
}
interface AgentPlatform {
fun getAgent(name: String): Agent?
}
interface AgentScope {
val actions: List<Action>
val goals: List<Goal>
val conditions: List<Condition>
}Install with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-apidocs