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:
firstAgentprocesses the initial inputsecondAgentprocesses the output fromfirstAgentthirdAgentprocesses the output fromsecondAgent
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}")
}
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:
🚀 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:
outcomeis theStuckorFataloutcome of the agent that stopped, with its reason or error message.agentRunsholds every agent run up to and including the one that stopped, so you can see where it went wrong.
Best Practices
- Keep agents focused: Each agent should handle one specific task well
- Design clear data contracts: Use well-defined data classes with
@AigenticParameter - Test individual agents: Verify each agent works correctly before chaining them together
- Structure attachment workflows: Design the first agent to extract and structure data from attachments, then pass structured data to subsequent agents