From 2db469c4d3365de4f30044c290e44db3666c83f9 Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Thu, 16 Jul 2026 14:02:19 +0530 Subject: [PATCH 1/2] Only track conditionless edges in the conditionless-edge dedup set Builder.AddDirectEdge appended every connection to conditionlessConnections, including conditional edges, but the duplicate check only fires for condition==nil and treats any entry as a pre-existing conditionless edge. So a conditional edge added first on a source->target pair made a subsequent legitimate conditionless edge on the same pair fail the dedup check with "an edge ... already exists without a condition" (or, via AddChain's idempotent path, be silently dropped). Guard the append with condition==nil so only conditionless edges populate the dedup set, matching the field's name and purpose. Adds tests for both the error path and the idempotent-drop path. --- workflow/builder.go | 7 +++- workflow/builder_conditional_edge_test.go | 43 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 workflow/builder_conditional_edge_test.go diff --git a/workflow/builder.go b/workflow/builder.go index 358816d8..dc3bb14a 100644 --- a/workflow/builder.go +++ b/workflow/builder.go @@ -145,7 +145,12 @@ func (wb *Builder) AddDirectEdge(source ExecutorBinding, target ExecutorBinding, } applyEdgeOptions(&edge, opts) wb.addEdgeForSource(source.ID, edge) - wb.conditionlessConnections = append(wb.conditionlessConnections, conn) + // Only conditionless edges participate in the conditionless-edge dedup set; + // appending conditional edges here would wrongly make a later conditionless + // edge on the same source→target pair look like a duplicate. + if condition == nil { + wb.conditionlessConnections = append(wb.conditionlessConnections, conn) + } return wb } diff --git a/workflow/builder_conditional_edge_test.go b/workflow/builder_conditional_edge_test.go new file mode 100644 index 00000000..257f48e4 --- /dev/null +++ b/workflow/builder_conditional_edge_test.go @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +package workflow_test + +import ( + "testing" + + "github.com/microsoft/agent-framework-go/workflow" +) + +// A conditional edge on a source→target pair must not populate the +// conditionless-edge dedup set: adding a legitimate conditionless edge on the +// same pair afterwards should succeed, not be rejected as a duplicate. +func TestBuilder_ConditionalEdgeDoesNotBlockConditionlessEdge(t *testing.T) { + start := newNoOpExecutor("start") + target := newNoOpExecutor("target") + + _, err := workflow.NewBuilder(start). + AddDirectEdge(start, target, false, func(any) bool { return true }). + AddEdge(start, target). + Build() + if err != nil { + t.Fatalf("conditionless edge after a conditional edge on the same pair should be allowed, got error: %v", err) + } +} + +// The idempotent path (AddChain / idempotent=true) must likewise not silently +// drop a conditionless edge just because a conditional edge preceded it. +func TestBuilder_ConditionalEdgeDoesNotDropIdempotentConditionlessEdge(t *testing.T) { + start := newNoOpExecutor("start") + target := newNoOpExecutor("target") + + wf, err := workflow.NewBuilder(start). + AddDirectEdge(start, target, false, func(any) bool { return true }). + AddDirectEdge(start, target, true, nil). // idempotent conditionless edge + Build() + if err != nil { + t.Fatalf("unexpected build error: %v", err) + } + if wf == nil { + t.Fatal("expected a workflow") + } +} From 0af8deac8bbb9506ff73b1c4a77131c91f9c0d9f Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Thu, 16 Jul 2026 14:54:52 +0530 Subject: [PATCH 2/2] Assert the idempotent conditionless edge is actually added Strengthen the idempotent test to inspect the built workflow's edges and confirm both a conditional and a conditionless edge from start exist, rather than only checking that Build succeeded (which held even when the edge was silently dropped). --- workflow/builder_conditional_edge_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/workflow/builder_conditional_edge_test.go b/workflow/builder_conditional_edge_test.go index 257f48e4..3dc85a6d 100644 --- a/workflow/builder_conditional_edge_test.go +++ b/workflow/builder_conditional_edge_test.go @@ -37,7 +37,19 @@ func TestBuilder_ConditionalEdgeDoesNotDropIdempotentConditionlessEdge(t *testin if err != nil { t.Fatalf("unexpected build error: %v", err) } - if wf == nil { - t.Fatal("expected a workflow") + + // The idempotent conditionless edge must actually be present, not silently + // dropped by the poisoned dedup set: expect both a conditional and a + // conditionless edge from start. + var conditional, conditionless int + for _, e := range wf.Edges()["start"] { + if e.Condition == nil { + conditionless++ + } else { + conditional++ + } + } + if conditional != 1 || conditionless != 1 { + t.Fatalf("edges from start: conditional=%d conditionless=%d, want 1 and 1", conditional, conditionless) } }