diff --git a/sidecar/src/Aryx.AgentHost/Services/WorkflowValidator.cs b/sidecar/src/Aryx.AgentHost/Services/WorkflowValidator.cs index 11cebaa..0e76236 100644 --- a/sidecar/src/Aryx.AgentHost/Services/WorkflowValidator.cs +++ b/sidecar/src/Aryx.AgentHost/Services/WorkflowValidator.cs @@ -320,13 +320,14 @@ public sealed class WorkflowValidator }); } - if (edge.Condition is null || string.Equals(edge.Condition.Type, "always", StringComparison.OrdinalIgnoreCase)) + if ((edge.Condition is null || string.Equals(edge.Condition.Type, "always", StringComparison.OrdinalIgnoreCase)) + && (edge.MaxIterations is null || edge.MaxIterations < 1)) { issues.Add(new WorkflowValidationIssueDto { Field = "graph.edges.condition", EdgeId = edge.Id, - Message = "Loop edges require a non-default condition so the loop can terminate.", + Message = "Loop edges require either a non-default condition or a maxIterations cap so the loop can terminate.", }); } diff --git a/sidecar/tests/Aryx.AgentHost.Tests/WorkflowValidatorTests.cs b/sidecar/tests/Aryx.AgentHost.Tests/WorkflowValidatorTests.cs index d180f99..9424411 100644 --- a/sidecar/tests/Aryx.AgentHost.Tests/WorkflowValidatorTests.cs +++ b/sidecar/tests/Aryx.AgentHost.Tests/WorkflowValidatorTests.cs @@ -152,6 +152,43 @@ public sealed class WorkflowValidatorTests Assert.DoesNotContain(issues, issue => issue.Level == "error"); } + [Fact] + public void Validate_AcceptsLoopWithAlwaysConditionAndMaxIterations() + { + WorkflowDefinitionDto workflow = CreateWorkflow(); + workflow = new WorkflowDefinitionDto + { + Id = workflow.Id, + Name = workflow.Name, + Settings = workflow.Settings, + Graph = new WorkflowGraphDto + { + Nodes = workflow.Graph.Nodes, + Edges = + [ + .. workflow.Graph.Edges, + new WorkflowEdgeDto + { + Id = "edge-loop", + Source = "agent", + Target = "agent", + Kind = "direct", + IsLoop = true, + MaxIterations = 5, + Condition = new EdgeConditionDto + { + Type = "always", + }, + }, + ], + }, + }; + + IReadOnlyList issues = _validator.Validate(workflow); + + Assert.DoesNotContain(issues, issue => issue.Level == "error"); + } + [Fact] public void Validate_AcceptsPhase4ExecutableNodeKinds() {