.NET: feat: add pipeline behavior extension points for workflow and executor execution#3982
Open
gijswalraven wants to merge 23 commits intomicrosoft:mainfrom
Open
Conversation
Introduces IWorkflowBehavior, IExecutorBehavior, WorkflowBehaviorOptions, and BehaviorExecutionException as the public API surface for pipeline behaviors on workflow and executor execution. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds BehaviorPipeline (internal chain-of-responsibility engine) and integrates it into Workflow, WorkflowBuilder, Executor, and InProcessRunner. Includes a zero-overhead fast path when no behaviors are registered. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds XUnit tests for BehaviorExecutionException, BehaviorPipeline, WorkflowBehaviorOptions, and integration tests for the full behavior pipeline. Includes developer documentation (README.md) covering usage, examples, and API reference. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
PostExecution was never set in ExecutorBehaviorContext. Behaviors already support post-execution logic naturally by placing code after the `await continuation()` call. Clarify this in docs and remove the dead value. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Passing null to WithBehaviors previously created an empty WorkflowBehaviorOptions and returned without error. Use Throw.IfNull to match the validation pattern used by all other WorkflowBuilder methods. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The async keyword on BeginStreamAsync caused a state machine to be allocated on every call, even when no behaviors are configured. Split into a sync fast path returning ValueTask directly, and a private async method used only when behaviors are present. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
A new GUID was generated per executor invocation, making it impossible for behaviors to correlate calls within the same workflow run. Pass the run ID alongside BehaviorPipeline through the internal ExecuteAsync overload so behaviors always see the correct run identifier. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…eardown WorkflowStage.Ending was unreachable because ExecuteWorkflowEndBehaviorsAsync was defined but never called. Register it as a callback on InProcessRunnerContext so it fires during EndRunAsync, before executor disposal, symmetrically with the Starting behaviors fired in BeginStreamAsync. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Author
|
@microsoft-github-policy-service agree company="delaware Consulting B.V." |
Replace null! initializer workarounds with the required keyword on ExecutorType, Message, MessageType, and WorkflowContext properties so the compiler enforces initialization at the call site. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add a ValueTask (void) overload of ExecuteWorkflowPipelineAsync to BehaviorPipeline so callers that don't need a return value can avoid the dummy (ct) => new ValueTask<int>(0) continuation pattern. Update both call sites in InProcessRunner to use ValueTask.CompletedTask. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add WorkflowBehavior_BehaviorExecutesAtEndAsync to verify that WorkflowStage.Ending fires during run disposal, and Workflow_WithBehaviors_RunIdIsConsistentAcrossContextsAsync to verify that workflow, executor, and Run all share the same RunId. Also fix net472 build issues introduced by Bugs 6 and 7: - Add InjectRequiredMemberOnLegacy/InjectCompilerFeatureRequiredOnLegacy polyfills to the csproj for the required keyword (Bug 6) - Replace ValueTask.CompletedTask (net5+ only) with default (Bug 7) - Add NullWorkflowContext stub and WorkflowContext initializer to BehaviorPipelineTests to satisfy the now-required property (Bug 6) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace manual if-null-throw pattern in the 3-parameter constructor with Throw.IfNull from Microsoft.Shared.Diagnostics, consistent with the rest of the project. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The tests exercise an in-process pipeline with no external dependencies, making "end-to-end" a more accurate description than "integration". Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Document the known limitation that WorkflowStage.Ending behaviors receive CancellationToken.None, explaining why and what a fix would require. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the ?? throw new ArgumentNullException(...) pattern with Throw.IfNull() from Microsoft.Shared.Diagnostics, consistent with the rest of the project. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Throw InvalidOperationException if a run-ending callback is registered more than once, preventing silent overwrites of the first registration. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add Workflow_WithBothBehaviorTypes_ExecutesInCorrectOrderAsync to verify that workflow and executor behaviors interleave correctly: Starting → PreExecution → executor → Ending. Also fix spurious XML doc warning in WorkflowBehaviorOptions introduced by Bug 11 (use fully-qualified System.ArgumentNullException in cref). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Clarify that continuation should be called exactly once, and explain what happens if it is called multiple times or not at all. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace Throw.IfNull with Throw.IfNullOrEmpty so that empty strings produce a clear ArgumentException rather than silently creating a confusing error message like "Error executing behavior '' at stage ''". Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…iors Add ExecutorPipeline_WithNoBehaviors_FinalHandlerExceptionNotWrappedAsync to document and enforce that when no behaviors are registered, exceptions from the core finalHandler propagate as-is, without being wrapped in BehaviorExecutionException. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enhance Workflow_BehaviorThrowsException_EmitsErrorEventAsync to also assert that BehaviorExecutionException.BehaviorType and Stage are populated correctly, catching regressions in error context propagation. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…lowBehavior.HandleAsync Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ontext doc Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
IWorkflowBehaviorandIExecutorBehaviorinterfaces for injecting custom logic before/after workflow start/end and executor step start/endBehaviorPipeline(internal) with zero-overhead fast path when no behaviors are registeredWorkflow,WorkflowBuilder,Executor, andInProcessRunnervia an opt-in fluent API (WithBehaviors)BehaviorExecutionExceptionfor wrapping behavior failures with stage/type contextTest plan
BehaviorExecutionExceptionconstructors and propertiesBehaviorPipelineexecution order and error handlingWorkflowBehaviorOptionsregistration (instance and factory)References
Closes #3960
🤖 Generated with Claude Code