Skip to content

Deduplicate repeated target indices within a SwitchBuilder case to avoid duplicate delivery - #706

Merged
George Adams (gdams) merged 1 commit into
microsoft:mainfrom
PratikDhanaveFork:dedup-switch-case-targets
Jul 29, 2026
Merged

Deduplicate repeated target indices within a SwitchBuilder case to avoid duplicate delivery#706
George Adams (gdams) merged 1 commit into
microsoft:mainfrom
PratikDhanaveFork:dedup-switch-case-targets

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

SwitchBuilder.collectTargets deduplicates the aggregated fan-out targets slice via targetIndexByID, but the per-call out slice appended an index for every binding passed with no membership check. Listing the same target twice within a single case or default — AddCase(pred, t1, t1) or WithDefault(t1, t1) — therefore produced indices [0, 0].

Nothing downstream deduplicates either: the assigner yields index 0 twice, selectedTargetIDs appends the same sink ID twice, resolveTargets returns the same executor twice, the type filter keeps both, and MapInto enqueues the envelope once per entry — so the matched message is delivered to that executor twice.

This change tracks a per-call seen set in collectTargets and emits each index at most once. It applies identically to AddCase and WithDefault since both route through collectTargets.

Why

Mirrors the .NET implementation, where a switch case's targets are backed by HashSet<int> so a repeated target collapses to a single delivery. This restores cross-SDK parity: a message routed through a switch should reach each distinct target exactly once regardless of how many times it was listed.

Testing

Added two black-box behavioral tests in workflow/builder_test.go using the existing recordingBinding harness:

  • TestAddSwitch_DeduplicatesRepeatedTargetsWithinCaseAddCase(pred, target, target), asserts the target's handler fires exactly once.
  • TestAddSwitch_DeduplicatesRepeatedDefaultTargetsWithDefault(def, def), asserts the default target fires exactly once.

Both fail before the fix (delivered twice) and pass after. go build ./..., go vet ./workflow/..., and go test ./workflow/... are green.

Copilot AI review requested due to automatic review settings July 24, 2026 02:35
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner July 24, 2026 02:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes duplicate message delivery in SwitchBuilder when the same ExecutorBinding is listed multiple times within a single switch case or default branch. It aligns the Go SDK’s switch routing behavior with the .NET implementation by ensuring each distinct target receives a matched message at most once per case/default.

Changes:

  • Deduplicate per-case/per-default target indices in SwitchBuilder.collectTargets using a per-call seen set.
  • Add black-box tests to verify deduplication for repeated targets in a case and in the default branch.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
workflow/builder.go Adds per-call deduplication in collectTargets to prevent duplicate deliveries when targets repeat within the same case/default.
workflow/builder_test.go Adds behavioral tests ensuring repeated targets in a case/default only receive one delivery.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
@github-actions

This comment has been minimized.

collectTargets appended a target index for every binding passed to
AddCase/WithDefault without a membership check, so listing the same
target twice (e.g. AddCase(pred, t1, t1)) yielded indices [0,0] and the
message was enqueued into that executor twice. Track a per-call seen set
and emit each index at most once, matching .NET's HashSet<int> target
semantics so a message is delivered once per target.
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — ✅ No Issues Found

Scope

This PR fixes a behavioral bug in the unexported SwitchBuilder.collectTargets helper in workflow/builder.go. The exported AddCase and WithDefault method signatures are unchanged; only the runtime behavior of repeated-target delivery is corrected.

Cross-SDK Parity

The fix directly mirrors the upstream .NET implementation:

  • dotnet/src/Microsoft.Agents.AI.Workflows/SwitchBuilder.cs: case indices are stored in a HashSet<int> (per case in _caseMap) and _defaultIndicies is also a HashSet<int>. Inserting a duplicate index into a HashSet is a no-op, so the .NET implementation has always delivered each distinct target exactly once per matched case — even when the same ExecutorBinding is passed multiple times.
  • Go (before this fix): collectTargets appended an index for every binding passed without a membership check, so AddCase(pred, t1, t1) produced [0, 0] and delivered the message twice.
  • Go (after this fix): adds a seen set per call, emitting each index at most once — semantically equivalent to the .NET HashSet<int> approach.

No Python equivalent was found for SwitchBuilder in python/packages/, consistent with the known state of the Python SDK.

Labels

No exported Go API surface changed, so public-api-change is not warranted. The existing parity-approved label correctly reflects this review outcome.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 32.6 AIC · ⌖ 5.61 AIC · ⊞ 5.9K ·

@gdams
George Adams (gdams) added this pull request to the merge queue Jul 29, 2026
Merged via the queue into microsoft:main with commit 1a64c56 Jul 29, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants