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..3dc85a6d --- /dev/null +++ b/workflow/builder_conditional_edge_test.go @@ -0,0 +1,55 @@ +// 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) + } + + // 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) + } +}