diff --git a/src/ConductorSharp.Engine/Builders/DoWhileTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/DoWhileTaskBuilder.cs
new file mode 100644
index 00000000..4b8c6414
--- /dev/null
+++ b/src/ConductorSharp.Engine/Builders/DoWhileTaskBuilder.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using ConductorSharp.Client.Generated;
+using ConductorSharp.Engine.Interface;
+using ConductorSharp.Engine.Model;
+using ConductorSharp.Engine.Util;
+using ConductorSharp.Engine.Util.Builders;
+
+namespace ConductorSharp.Engine.Builders
+{
+ ///
+ /// Extension methods to add a DO_WHILE loop with multiple inner tasks, based on BaseTaskBuilder.
+ ///
+ public static class DoWhileTaskExtensions
+ {
+ ///
+ /// Adds a DO_WHILE loop to the workflow definition.
+ ///
+ /// Workflow type
+ /// Parent sequence builder
+ /// Expression selecting the workflow property holding the loop's reference name
+ /// Expression creating the input
+ /// Loop condition
+ /// Delegate to add tasks inside the loop
+ public static ITaskOptionsBuilder AddTask(
+ this ITaskSequenceBuilder builder,
+ Expression> reference,
+ Expression> input,
+ string loopCondition,
+ Action> configureBody
+ )
+ where TWorkflow : ITypedWorkflow
+ {
+ // Extract expressions
+ var taskBuilder = new DoWhileTaskBuilder(reference.Body, input.Body, loopCondition, builder.BuildConfiguration);
+ // Register it with the outer sequence
+ builder.AddTaskBuilderToSequence(taskBuilder);
+ // Allow the caller to configure inner loop body tasks
+ configureBody(taskBuilder);
+ return taskBuilder;
+ }
+ }
+
+ ///
+ /// Builder for the DO_WHILE task, extending BaseTaskBuilder for consistent patterns.
+ ///
+ internal sealed class DoWhileTaskBuilder : BaseTaskBuilder, ITaskSequenceBuilder
+ where TWorkflow : ITypedWorkflow
+ {
+ private readonly string _loopCondition;
+ private readonly DoWhileInput _doWhileInput;
+ private readonly List _innerTasks = new();
+ public BuildContext BuildContext { get; } = new();
+ public BuildConfiguration BuildConfiguration { get; }
+ public WorkflowBuildItemRegistry WorkflowBuildRegistry { get; } = new();
+ public IEnumerable ConfigurationProperties { get; } = new List();
+
+ ///
+ public DoWhileTaskBuilder(Expression taskExpression, Expression inputExpression, string loopCondition, BuildConfiguration buildConfiguration)
+ : base(taskExpression, inputExpression, buildConfiguration)
+ {
+ _loopCondition = loopCondition;
+ BuildConfiguration = buildConfiguration;
+ }
+
+ ///
+ public override WorkflowTask[] Build()
+ {
+ var refName = _taskRefferenceName;
+
+ if (_innerTasks.Any(t => t.WorkflowTaskType == WorkflowTaskType.DO_WHILE))
+ {
+ throw new InvalidOperationException("Nested DO_WHILE tasks are not allowed.");
+ }
+
+ var loopTask = new WorkflowTask
+ {
+ Name = refName,
+ TaskReferenceName = refName,
+ WorkflowTaskType = WorkflowTaskType.DO_WHILE,
+ Type = nameof(WorkflowTaskType.DO_WHILE),
+ InputParameters = _inputParameters.ToObject>(),
+ LoopCondition = _loopCondition,
+ LoopOver = _innerTasks,
+ };
+ return [loopTask];
+ }
+
+ public void AddTaskBuilderToSequence(ITaskBuilder builder)
+ {
+ foreach (var task in builder.Build())
+ {
+ _innerTasks.Add(task);
+ }
+ }
+ }
+}
diff --git a/src/ConductorSharp.Engine/Model/DoWhileTaskModel.cs b/src/ConductorSharp.Engine/Model/DoWhileTaskModel.cs
new file mode 100644
index 00000000..f635ae2b
--- /dev/null
+++ b/src/ConductorSharp.Engine/Model/DoWhileTaskModel.cs
@@ -0,0 +1,18 @@
+using ConductorSharp.Engine.Builders;
+using MediatR;
+
+namespace ConductorSharp.Engine.Model
+{
+ ///
+ /// Input for configuration of the DO_WHILE task.
+ ///
+ public class DoWhileInput : IRequest, IWorkflowInput
+ {
+ public object Value { get; set; } = null;
+ }
+
+ ///
+ /// Task Model to reference the do while task in the workflow builders
+ ///
+ public class DoWhileTaskModel : TaskModel { }
+}
diff --git a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
index 182c8412..a67cc2a7 100644
--- a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
+++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
@@ -42,6 +42,7 @@
+
diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
index 3c914816..7c1dcc9d 100644
--- a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
+++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
@@ -166,6 +166,15 @@ public void BuilderReturnsCorrectDefinitionDecisionTask()
Assert.Equal(expectedDefinition, definition);
}
+ [Fact]
+ public void BuilderReturnsCorrectDefinitionDoWhileTask()
+ {
+ var definition = GetDefinitionFromWorkflow();
+ var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/DoWhileTask.json");
+
+ Assert.Equal(expectedDefinition, definition);
+ }
+
[Fact]
public void BuilderReturnsCorrectDefinitionSwitchTask()
{
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.cs
new file mode 100644
index 00000000..df1b5e2e
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.cs
@@ -0,0 +1,31 @@
+namespace ConductorSharp.Engine.Tests.Samples.Workflows
+{
+ public sealed class DoWhileTaskInput : WorkflowInput
+ {
+ public int Loops { get; set; }
+ }
+
+ public sealed class DoWhileTaskOutput : WorkflowOutput { }
+
+ public sealed class DoWhileTask : Workflow
+ {
+ public DoWhileTaskModel DoWhile { get; set; }
+ public CustomerGetV1 GetCustomer { get; set; }
+
+ public DoWhileTask(WorkflowDefinitionBuilder builder)
+ : base(builder) { }
+
+ public override void BuildDefinition()
+ {
+ _builder.AddTask(
+ wf => wf.DoWhile,
+ wf => new() { Value = wf.Input.Loops },
+ "$.do_while.iteration < $.value",
+ builder =>
+ {
+ builder.AddTask(wf => wf.GetCustomer, wf => new CustomerGetV1Input() { CustomerId = "CUSTOMER-1" });
+ }
+ );
+ }
+ }
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.json b/test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.json
new file mode 100644
index 00000000..74bb657a
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/DoWhileTask.json
@@ -0,0 +1,34 @@
+{
+ "name": "do_while_task",
+ "version": 1,
+ "tasks": [
+ {
+ "name": "do_while",
+ "taskReferenceName": "do_while",
+ "inputParameters": {
+ "value": "${workflow.input.loops}"
+ },
+ "type": "DO_WHILE",
+ "loopCondition": "$.do_while.iteration < $.value",
+ "loopOver": [
+ {
+ "name": "CUSTOMER_get",
+ "taskReferenceName": "get_customer",
+ "inputParameters": {
+ "customer_id": "CUSTOMER-1"
+ },
+ "type": "SIMPLE",
+ "optional": false,
+ "workflowTaskType": "SIMPLE"
+ }
+ ],
+ "workflowTaskType": "DO_WHILE"
+ }
+ ],
+ "inputParameters": [
+ "loops"
+ ],
+ "outputParameters": {},
+ "schemaVersion": 2,
+ "timeoutSeconds": 0
+}
\ No newline at end of file
diff --git a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
index 83c8a821..54becee2 100644
--- a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
+++ b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
@@ -1,9 +1,9 @@
-using Newtonsoft.Json;
-using System;
+using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
+using Newtonsoft.Json;
namespace ConductorSharp.Engine.Tests.Util
{