# How to Use the Kotlin DSL for Agentic Flows in Embabel-Agent

> Master Kotlin DSL for agentic flows in Embabel Agent. Declaratively compose autonomous agents using type-safe builders for powerful LLM logic. Start building today.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: how-to-guide
- Published: 2026-08-08

---

**The Kotlin DSL for agentic flows provides type-safe builders like `agent()`, `transformation()`, and `flow()` that let you declaratively compose autonomous agents with pure Kotlin or LLM-driven logic.**

The embabel/embabel-agent repository delivers a declarative, type-safe API for building agentic workflows in Kotlin. Located under `com.embabel.agent.api.dsl`, this domain-specific language enables developers to define agents, actions, and composable flows without sacrificing compile-time safety. Whether orchestrating simple transformations or complex parallel aggregations, the DSL abstracts runtime complexity while maintaining full access to underlying implementation details.

## Core Concepts of the DSL

The Kotlin DSL for agentic flows centers on three architectural primitives that work together to create reusable, executable agents.

### Agent Definition

The `agent { … }` block serves as the entry point for creating an `Agent` instance. Defined in [`embabel-agent-api/src/main/kotlin/com/embabel/agent/api/dsl/agent.kt`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/dsl/agent.kt), the `agent()` function accepts metadata (name, provider, version, description) and a configuration block that mutates an `AgentBuilder`【agent.kt:26‑43】. This builder collects immutable collections of actions, goals, and conditions that define the agent's behavior.

### Action Building

Individual computational steps are registered through specialized builder methods on `AgentBuilder`:
- **`transformation<I, O>()`** – Registers pure Kotlin functions as `TransformationAction` instances for type-safe data processing【AgentBuilder.kt:123‑58】.
- **`promptedTransformer<I, O>()`** – Creates LLM-driven transformations that assemble prompts, execute model calls, and parse responses into typed outputs【AgentBuilder.kt:61‑99】.
- **`flow { … }`** – Embeds a nested scope for complex sub-flows, returning a `TypedAgentScopeBuilder` that supports chaining and aggregation【AgentBuilder.kt:10‑18】.

### Flow Composition

Higher-order builders in `TypedAgentScopeBuilder` (located in [`embabel-agent-api/src/main/kotlin/com/embabel/agent/api/dsl/TypedAgentScopeBuilder.kt`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/dsl/TypedAgentScopeBuilder.kt)) enable sophisticated control flow:
- **`andThen()`** – Sequentially chains transformations, automatically wiring output types to subsequent input types.
- **`aggregate()`** – Executes parallel transformations on identical inputs and merges results into a unified output type【TypedAgentScopeBuilder.kt:9‑31】.
- **`chain()`** – Constructs low-level linked `TransformationAction` pipelines (A→B→C)【TypedAgentScopeBuilder.kt:20‑55】.
- **`branch()`** – Creates conditional splits yielding discriminated unions for pattern matching downstream.

## Architectural Deep Dive

Understanding the internal mechanics of the DSL builders ensures you can leverage advanced features like type reflection and context propagation.

### The agent() Entry Point

When you invoke `agent()`, the DSL creates a mutable `AgentBuilder` scope, applies your configuration block, and invokes `build()` to produce an immutable `Agent` instance:

```kotlin
fun agent(
    name: String,
    provider: String = "embabel",
    version: Semver = Semver(),
    description: String,
    promptContributors: List<PromptContributor> = emptyList(),
    block: AgentBuilder.() -> Unit,
): Agent

```

The resulting `Agent` holds immutable collections of actions, goals, and conditions that the runtime executes according to the declared semantics.

### AgentBuilder Mechanics

`AgentBuilder` maintains three critical mutable collections:
- `actions: MutableList<Action>` – Populated via `transformation`, `promptedTransformer`, and `flow` calls.
- `goals: MutableSet<Goal>` – Declared using `goal(name, …)` to specify completion criteria linked to output types (`satisfiedBy`).
- `conditions: MutableSet<Condition>` – Constraints added via `condition { … }` or `promptedCondition`.

Goals declare what output type satisfies the agent's objective, enabling the platform to detect completion when that type appears on the execution blackboard.

### TypedAgentScopeBuilder for Composable Flows

When `flow { … }` is invoked, the DSL returns a `TypedAgentScopeBuilder<O>` where `O` represents the sub-flow's output type. This scope provides type-safe chaining through reified generic parameters and Java `Class` reflection (e.g., `A::class.java`). The `aggregate()` method exemplifies this by accepting a list of transforms `(I) -> O` and a merge function `(List<O>, I) -> R`, enforcing type constraints at compile time while enabling parallel execution semantics【TypedAgentScopeBuilder.kt:9‑31】.

## Practical Code Examples

### Minimal Agent with Pure Kotlin

Create a simple echo agent using only Kotlin transformations without LLM dependencies:

```kotlin
import com.embabel.agent.api.dsl.agent
import com.embabel.agent.api.dsl.transformation
import com.embabel.agent.domain.io.UserInput

val HelloAgent = agent(
    name = "HelloAgent",
    description = "Echoes the user input"
) {
    transformation<UserInput, String>(name = "echo") { input ->
        "You said: ${input.text}"
    }

    goal(name = "done", description = "Finished", satisfiedBy = String::class)
}

```

The `transformation` consumes `UserInput` and produces `String`, while the `goal` instructs the platform to terminate execution when a `String` value materializes.

### LLM-Driven Transformations

Use `promptedTransformer` to integrate large language models into your agentic flow:

```kotlin
import com.embabel.agent.api.dsl.agent
import com.embabel.agent.api.dsl.promptedTransformer

val Summarizer = agent(
    name = "Summarizer",
    description = "Summarizes a paragraph using the LLM"
) {
    promptedTransformer<UserInput, String>(
        name = "summarize",
        prompt = { ctx -> "Summarize this text in one sentence: ${ctx.input.text}" }
    )
    goal(name = "done", description = "Summary ready", satisfiedBy = String::class)
}

```

This builder constructs the prompt template, manages LLM invocation via `LlmOptions`, and handles response parsing back into the declared output type.

### Parallel Processing with Aggregation

Execute multiple transformations concurrently and merge results using `aggregate`:

```kotlin
import com.embabel.agent.api.dsl.agent
import com.embabel.agent.api.dsl.flow
import com.embabel.agent.api.dsl.aggregate

data class MagicVictim(val name: String)
data class Frog(val name: String)
data class SnakeMeal(val frogs: List<Frog>)

val FrogAggregator = agent(
    name = "FrogAggregator",
    description = "Creates three frogs and aggregates them"
) {
    transformation<UserInput, MagicVictim>(name = "magic") { MagicVictim("Hamish") }

    flow {
        aggregate<MagicVictim, Frog, SnakeMeal>(
            transforms = listOf(
                { mv -> Frog(mv.name) },
                { _ -> Frog("2") },
                { _ -> Frog("3") }
            ),
            merge = { frogs, _ -> SnakeMeal(frogs) }
        )
    }

    goal(name = "done", description = "All frogs collected", satisfiedBy = SnakeMeal::class)
}

```

The `aggregate` builder runs three independent `TransformationAction` instances on the same `MagicVictim` input, then merges the resulting `List<Frog>` into a `SnakeMeal` instance.

### Sequential Chaining

Link transformations sequentially using `andThen`:

```kotlin
val ChainedAgent = agent(
    name = "ChainedAgent",
    description = "Upper-cases then reverses a string"
) {
    transformation<String, String>(name = "upper") { it.uppercase() }
        .andThen { it.reversed() }
    goal(name = "done", description = "Processed string", satisfiedBy = String::class)
}

```

The `andThen` method appends a second step to the pipeline, automatically connecting the `String` output of `upper` to the input of the reversal transformation.

### Executing Agents at Runtime

After defining an agent with the DSL, invoke it through the platform API:

```kotlin
import com.embabel.agent.core.*
import com.embabel.agent.domain.io.UserInput

val platform = dummyAgentPlatform() // Replace with production platform implementation
val result = platform.runAgentFrom(
    agent = HelloAgent,
    processOptions = ProcessOptions(),
    bindings = mapOf("it" to UserInput("Hello DSL!"))
)

println(result.lastResult()) // Output: "You said: Hello DSL!"

```

The `runAgentFrom` helper extracts the single action from the agent (via `asAction`) and executes it within the supplied `TransformationActionContext`, returning the typed output【TypedAgentScopeBuilder.kt:57‑68】.

## Summary

- The **Kotlin DSL for agentic flows** in embabel/embabel-agent provides type-safe builders under `com.embabel.agent.api.dsl` for constructing autonomous agents.
- **`agent { … }`** (defined in [`agent.kt`](https://github.com/embabel/embabel-agent/blob/main/agent.kt)) creates immutable `Agent` instances via the mutable `AgentBuilder` mechanism.
- **Actions** are added via `transformation()` for pure Kotlin logic or `promptedTransformer()` for LLM-augmented steps, both implemented in [`AgentBuilder.kt`](https://github.com/embabel/embabel-agent/blob/main/AgentBuilder.kt).
- **Composable flows** use `flow { … }` returning `TypedAgentScopeBuilder`, enabling `aggregate()` for parallel processing and `andThen()` for sequential chaining.
- **Execution** occurs through platform methods like `runAgentFrom`, which consume the DSL-defined agent and return typed results.

## Frequently Asked Questions

### What is the difference between `transformation` and `promptedTransformer`?

**`transformation`** registers pure Kotlin functions that execute deterministic logic without external service calls, while **`promptedTransformer`** constructs dynamic prompts, invokes the configured LLM through `LlmOptions`, and parses unstructured text responses back into typed objects. Use `transformation` for data validation or business logic, and `promptedTransformer` for natural language understanding or generation tasks.

### How do I execute an agent after defining it with the Kotlin DSL?

After building an `Agent` instance using the DSL, pass it to the platform's execution API such as `runAgentFrom()`. This method requires the agent definition, `ProcessOptions` for runtime configuration, and an initial bindings map containing the input data. The platform extracts the agent's action graph and executes it within a `TransformationActionContext`, returning the final typed output.

### Can I compose multiple agents together using the DSL?

Yes. The DSL supports agent composition through **`flow { … }`** blocks and specific action types like `localAgentAction()` or `referencedAgentAction()`. These methods allow you to embed existing agents as sub-components within larger workflows, treating them as atomic actions within the parent agent's execution graph while maintaining type safety across agent boundaries.

### What types are supported in the DSL's type-safe builders?

The DSL uses **reified generic parameters** and Java `Class` reflection (e.g., `String::class.java`) to enforce type constraints at compile time. Any Kotlin type can serve as input (`I`) or output (`O`) parameters for transformations, including data classes, primitives, and generic collections. The `TypedAgentScopeBuilder` propagates these type parameters through chains and aggregates, ensuring that incompatible transformations fail at compile time rather than runtime.