Skip to main content

Workflow DSL

Workflows help you chain multiple agents together to handle complex tasks that require multiple processing steps. Each agent in the workflow processes the output from the previous agent, creating a seamless pipeline of AI-powered operations.

Creating a Workflow​

The main way to create a workflow is using the thenProcess infix function to chain agents together:

// Creates a Workflow3<InputType, FirstOutput, SecondOutput, OutputType>
val workflow = firstAgent thenProcess secondAgent thenProcess thirdAgent

This creates a workflow where:

  1. firstAgent processes the initial input
  2. secondAgent processes the output from firstAgent
  3. thirdAgent processes the output from secondAgent

A workflow chains at most five agents.

Type Safety​

Workflows are fully type-safe. The output type of each agent must match the input type of the next agent in the workflow:

@AigenticParameter
data class InitialInput(val text: String)

@AigenticParameter
data class ProcessedData(val result: String)

@AigenticParameter
data class FinalOutput(val summary: String)

// This works - types align correctly
val firstAgent = agent<InitialInput, ProcessedData> { /* config */ }
val secondAgent = agent<ProcessedData, FinalOutput> { /* config */ }

// Creates a Workflow2<InitialInput, ProcessedData, FinalOutput>
// Input flows: InitialInput -> ProcessedData -> FinalOutput
val workflow = firstAgent thenProcess secondAgent

Starting a Workflow​

Once you've created a workflow, start it using the start function:

val workflowRun = workflow.start(initialInput)

when (val outcome = workflowRun.outcome) {
is Outcome.Finished -> {
val result = outcome.response
println("Workflow completed: $result")
}
is Outcome.Stuck -> println("Workflow stuck: ${outcome.reason}")
is Outcome.Fatal -> println("Workflow failed: ${outcome.message}")
}

Starting with Attachments​

Workflows also support attachments (PDFs, images, etc.) that are passed to the first agent only. Subsequent agents receive structured output from previous agents:

// With attachments
val pdfAttachment = Attachment.Base64.pdf("base64-encoded-pdf-content")
val imageAttachment = Attachment.Base64.jpeg("base64-encoded-image-content")

val workflowRun = workflow.start(initialInput, pdfAttachment, imageAttachment)

when (val outcome = workflowRun.outcome) {
is Outcome.Finished -> {
val result = outcome.response
println("Workflow completed: $result")
}
is Outcome.Stuck -> println("Workflow stuck: ${outcome.reason}")
is Outcome.Fatal -> println("Workflow failed: ${outcome.message}")
}
note

Attachments are only passed to the first agent in the workflow. This allows the first agent to process documents, images, or other media files, while subsequent agents work with the structured data output from previous agents.

Working with WorkflowRun​

When you run a workflow, you get a WorkflowRun that works just like an AgentRun but also gives you access to each individual agent's results:

val workflowRun = workflow.start(input)

// Handle the final result
when (val outcome = workflowRun.outcome) {
is Outcome.Finished -> println("Final result: ${outcome.response}")
is Outcome.Stuck -> println("Workflow stuck: ${outcome.reason}")
is Outcome.Fatal -> println("Workflow failed: ${outcome.message}")
}

// Access individual agent runs if needed
workflowRun.agentRuns.forEach { agentRun ->
println("Agent used ${agentRun.tokenUsage()} tokens")
}

Complete Example​

Here's a practical example that processes legal documents through multiple stages:

import community.flock.aigentic.core.agent.Attachment
import community.flock.aigentic.core.agent.tool.Outcome
import community.flock.aigentic.core.annotations.AigenticParameter
import community.flock.aigentic.core.dsl.agent
import community.flock.aigentic.core.dsl.thenProcess
import community.flock.aigentic.core.workflow.start
import community.flock.aigentic.gemini.dsl.geminiModel
import community.flock.aigentic.gemini.model.GeminiModelIdentifier

@AigenticParameter
data class RedactedDocument(val redactedText: String)

@AigenticParameter
data class ComplianceReport(
val riskLevel: String,
val potentialIssues: List<String>,
val recommendations: List<String>
)

@AigenticParameter
data class LegalSummary(val summary: String)

suspend fun processLegalDocument(apiKey: String, documentText: String) {
// Create agents for each step
val documentRedactor = agent<String, RedactedDocument> {
geminiModel {
apiKey(apiKey)
modelIdentifier(GeminiModelIdentifier.Gemini3_5Flash)
}
task("Redact sensitive information from legal documents") {
addInstruction("Replace all personal names with [REDACTED NAME]")
addInstruction("Replace all addresses with [REDACTED ADDRESS]")
addInstruction("Replace all phone numbers with [REDACTED PHONE]")
addInstruction("Replace all social security numbers with [REDACTED SSN]")
}
}

val complianceChecker = agent<RedactedDocument, ComplianceReport> {
geminiModel {
apiKey(apiKey)
modelIdentifier(GeminiModelIdentifier.Gemini3_5Flash)
}
task("Analyze legal document for compliance issues") {
addInstruction("Evaluate the document for potential legal compliance issues")
addInstruction("Assess risk level as LOW, MEDIUM, or HIGH")
addInstruction("Identify potential issues and provide recommendations")
}
}

val summarizer = agent<ComplianceReport, LegalSummary> {
geminiModel {
apiKey(apiKey)
modelIdentifier(GeminiModelIdentifier.Gemini3_5Flash)
}
task("Create a comprehensive summary") {
addInstruction("Generate a summary including compliance findings")
addInstruction("Include risk level and main concerns")
addInstruction("Keep the summary under 100 words")
}
}

// Chain the agents into a workflow
// Creates a Workflow3<String, RedactedDocument, ComplianceReport, LegalSummary>
// Input flows: String -> RedactedDocument -> ComplianceReport -> LegalSummary
val workflow = documentRedactor thenProcess complianceChecker thenProcess summarizer

// Execute the workflow
val workflowRun = workflow.start(documentText, Attachment.Base64.pdf("base64-encoded-pdf-content"))

when (val outcome = workflowRun.outcome) {
is Outcome.Finished -> {
outcome.response?.let { summary ->
println("Document processed successfully!")
println("Summary: ${summary.summary}")
}
}
is Outcome.Stuck -> println("Workflow got stuck: ${outcome.reason}")
is Outcome.Fatal -> println("Workflow failed: ${outcome.message}")
}
}

Each agent in the chain logs when it starts, followed by your own output:

Output
🚀 Agent started!
🚀 Agent started!
🚀 Agent started!
Document processed successfully!
Summary: …

Error Handling​

If any agent in the workflow fails or gets stuck, the entire workflow stops at that point:

  • outcome is the Stuck or Fatal outcome of the agent that stopped, with its reason or error message.
  • agentRuns holds every agent run up to and including the one that stopped, so you can see where it went wrong.

Best Practices​

  1. Keep agents focused: Each agent should handle one specific task well
  2. Design clear data contracts: Use well-defined data classes with @AigenticParameter
  3. Test individual agents: Verify each agent works correctly before chaining them together
  4. Structure attachment workflows: Design the first agent to extract and structure data from attachments, then pass structured data to subsequent agents