Workflow orchestration built on event sourcing. Start code-first, extend to visual workflows, run AI agents reliably.
class OrderWorkflow : WorkflowDefinition<OrderInput, OrderOutput>() {
override val kind = "order-processing"
override val name = "Order Processing"
override val cancellable = true
override suspend fun execute(ctx: WorkflowContext, input: OrderInput): OrderOutput {
val payment = ctx.schedule<PaymentResult>("payment-task", input.paymentDetails)
ctx.checkCancellation()
val inventory = ctx.schedule<InventoryResult>("inventory-task", input.items)
if (inventory.outOfStock) {
ctx.run("refund") { refundPayment(payment.transactionId) }
throw NonRetryableException("Inventory unavailable")
}
return OrderOutput(orderId = input.orderId, status = "completed")
}
}Everything you need to build reliable distributed workflows
Write workflows in Kotlin with a simple WorkflowContext API. TypeScript and Python support planned. Event sourcing ensures deterministic replay.
Drag-and-drop interface powered by your custom workers. Write handlers in any language, register via gRPC, compose visually.
Resume agent loops at any point without losing state. Time-travel debugging lets you replay executions with different inputs.
Complete audit trail of all executions. Replay workflows from any point. Understand exactly what happened and why.
Automatic retries, compensation logic, and failure handling. Focus on business logic, not infrastructure complexity.
Multi-tenancy, quota enforcement, rate limiting, and data retention. gRPC communication protocol. Deploy on your infrastructure with full control.
The WorkflowContext API gives you everything you need for reliable execution
// Side effects - cached on replay
val result = ctx.run("fetch-data") {
httpClient.get(url)
}
// Long-running tasks - delegated
val payment = ctx.schedule<PaymentResponse>(
"payment-task",
input
)// Deterministic - same value on replay
val id = ctx.randomUUID()
val now = ctx.currentTimeMillis()
val delay = ctx.random().nextInt(1000, 5000)
// Persisted across restarts
ctx.set("status", "processing")
val status = ctx.get<String>("status")
// Durable timers and signals
ctx.sleep(Duration.ofHours(1))
val signal = ctx.promise<ApprovalResult>(
"manager-approval"
)Flovyn handles the complexity of distributed workflows so you can focus on business logic
Process orders across payment, inventory, and fulfillment with automatic compensation on failures. If delivery fails after payment, Flovyn automatically triggers refunds and inventory restoration.
Handle failed payments with scheduled retries, notification escalation, and support team handoff. Ensures reminders aren't duplicated and maintains exact billing state for compliance.
Agent loops that search web, analyze results, decide next steps, and compile reports. If the agent crashes mid-research, Flovyn resumes exactly where it left off without losing context.
Receive webhooks from payment providers, validate signatures, enrich with database context, update downstream systems. Guarantees exactly-once processing despite webhook retries.
Process thousands of documents in parallel (OCR, ML inference, validation), aggregate results, handle partial failures by retrying specific items.
Build SaaS products with complete tenant isolation, quota enforcement, rate limiting, and team-based access control. Built-in multi-tenancy and RBAC.
Stop reinventing distributed systems primitives. Focus on business logic.
From code to production in three steps
Create task handlers with the @TaskInfo annotation. Each task does one thing well with built-in timeout, retry, and progress reporting.
@Serializable
data class EmailInput(val to: String, val subject: String, val body: String)
@Serializable
data class EmailResult(val messageId: String, val status: String)
class SendEmailTask : TaskDefinition<EmailInput, EmailResult>() {
override val kind = "send-email"
override val name = "Send Email"
override val description = "Sends email via SMTP"
override val timeoutSeconds = 60
override suspend fun execute(input: EmailInput, context: TaskContext): EmailResult {
context.reportProgress(0.1, "Connecting to SMTP...")
context.reportProgress(0.5, "Sending email...")
val messageId = emailService.send(input)
context.reportProgress(1.0, "Email sent")
return EmailResult(messageId, "sent")
}
}Build workflows using the WorkflowContext API. Orchestrate tasks, handle failures, add compensation logic - all with full type safety.
@Serializable
data class UserInput(val email: String, val name: String)
@Serializable
data class OnboardingResult(val userId: String, val status: String)
class OnboardingWorkflow : WorkflowDefinition<UserInput, OnboardingResult>() {
override val kind = "user-onboarding"
override val name = "User Onboarding"
override val description = "Onboards new users with welcome email and account setup"
override suspend fun execute(ctx: WorkflowContext, input: UserInput): OnboardingResult {
val user = ctx.run("create-user") { userService.create(input) }
// Run email and account setup in parallel
val emailTask = ctx.scheduleAsync<EmailResult>("send-email", EmailInput(input.email, "Welcome!", "..."))
val setupTask = ctx.scheduleAsync<SetupResult>("setup-account", SetupInput(user.id))
awaitAll(emailTask, setupTask)
return OnboardingResult(user.id, "completed")
}
}Register your workflows and tasks with the Flovyn client using gRPC. They automatically appear in the visual builder and can be started via API or UI.
fun main() {
val client = FlovynClient.builder()
.serverAddress("localhost", 9090)
.tenantId(myTenantId)
.taskQueue("onboarding-workers")
.registerWorkflow(OnboardingWorkflow())
.registerTask(SendEmailTask())
.registerTask(SetupAccountTask())
.build()
client.start() // Blocks and polls for work
}Agent loops that survive crashes and resume exactly where they left off
Agent crashes mid-research? No problem. Flovyn resumes exactly where it stopped without losing context or duplicating work.
Replay agent executions with different inputs or models. See exactly what decisions the agent made at each step.
@Workflow(name = "research-agent")
class ResearchAgent :
TypedWorkflowDefinition<Query, Report> {
override suspend fun execute(
ctx: WorkflowContext,
input: Query
): Report {
val results = mutableListOf<SearchResult>()
while (results.size < 10) {
val search = ctx.schedule<SearchResult>(
"web-search",
input.query
)
results.add(search)
val decision = ctx.schedule<AgentDecision>(
"llm-decide",
results
)
if (decision.complete) break
// Resume point on crash
ctx.checkCancellation()
}
return ctx.schedule("compile-report", results)
}
}Built-in operational features for production workloads
PostgreSQL + single Flovyn server. Start simple, scale as requirements grow.
Automatic retention with configurable policies. Optional S3-compatible archival for long-term storage.
Built-in tenant isolation with quota enforcement and rate limiting. Perfect for SaaS products.
Run on your infrastructure with full control. No vendor lock-in, deploy anywhere.
Add Redis for advanced features when needed. Start simple, grow as you go.
Start with code, extend to visual workflows, run AI agents reliably in production.