Skip to content

Workflow Patterns ​

This page shows common multi-agent workflow shapes. The examples focus on topology: which agent runs, what it outputs, and how the next node is chosen.

Sequential Pipeline ​

Use a sequential pipeline when each stage depends on the previous stage.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("ResearchWriteReview")
    .AddAgent("research", researchConfig, node => node.WithOutputKey("research"))
    .AddAgent("write", writeConfig, node => node
        .WithInputKey("research")
        .WithOutputKey("draft"))
    .AddAgent("review", reviewConfig, node => node.WithInputKey("draft"))
    .From("research").To("write")
    .From("write").To("review")
    .BuildAsync();

Use this for draft -> review -> final, classify -> handle -> summarize, or extract -> transform -> validate.

Parallel Fan-Out ​

Use fan-out when several agents can work from the same upstream input.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("ParallelReview")
    .AddAgent("draft", draftConfig, node => node.WithOutputKey("draft"))
    .AddAgent("accuracy", accuracyConfig, node => node.WithInputKey("draft"))
    .AddAgent("style", styleConfig, node => node.WithInputKey("draft"))
    .From("draft").To("accuracy", "style")
    .BuildAsync();

The downstream nodes can run in the same layer once draft completes. Render layer events when the UI should show parallel progress.

Fan-In Synthesis ​

Use fan-in when a final node should combine several upstream results.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("ResearchSynthesis")
    .AddAgent("web", webResearchConfig, node => node.WithOutputKey("web_notes"))
    .AddAgent("docs", docsResearchConfig, node => node.WithOutputKey("doc_notes"))
    .AddAgent("synthesize", synthesizeConfig, node => node.WithInputTemplate("""
        Web notes:
        {{web_notes}}

        Documentation notes:
        {{doc_notes}}

        Write one answer from both sources.
        """))
    .From("START").To("web", "docs")
    .From("web", "docs").To("synthesize")
    .BuildAsync();

Use input templates for fan-in. They make the merge contract visible and keep the synthesizer from accidentally reading the original input instead of the upstream outputs.

Field Router ​

Use field routing when one classifier should choose a path by structured output.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("SupportTriage")
    .AddAgent("classify", classifierConfig, node => node
        .StructuredOutput<TicketClassification>())
    .AddAgent("billing", billingConfig)
    .AddAgent("technical", technicalConfig)
    .From("classify").To("billing").WhenEquals("intent", "billing")
    .From("classify").To("technical").WhenEquals("intent", "technical")
    .BuildAsync();

Use this when routing should be inspectable, testable, and serializable.

Router Handoff ​

Use handoff routing when the router agent should choose from generated handoff tools.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("SupportHandoff")
    .AddRouterAgent("router", routerConfig)
        .WithHandoff("billing", "Use for invoices, payments, or plan changes.")
        .WithHandoff("technical", "Use for bugs, errors, or setup problems.")
    .AddAgent("billing", billingConfig)
    .AddAgent("technical", technicalConfig)
    .BuildAsync();

Use this when natural-language target descriptions help the router choose the next specialist.

Review Gate ​

Use a review gate when the workflow should inspect work before continuing.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("DraftGate")
    .AddAgent("draft", draftConfig, node => node.WithOutputKey("draft"))
    .AddAgent("review", reviewConfig, node => node
        .WithInputKey("draft")
        .StructuredOutput<ReviewDecision>())
    .AddAgent("final", finalConfig, node => node.WithInputKey("draft"))
    .AddAgent("revise", reviseConfig, node => node.WithInputKey("draft"))
    .From("draft").To("review")
    .From("review").To("final").WhenEquals("approved", true)
    .From("review").To("revise").WhenEquals("approved", false)
    .BuildAsync();

Use structured output for the reviewer so route fields are stable.

Approval Step ​

Use approval when a node result needs an application or user decision before the workflow uses it.

csharp
.AddAgent("publish", publishConfig, node => node
    .WithInputKey("draft")
    .RequiresApproval("Approve publishing this draft?"))

Run with a WorkflowEventCoordinator when the app needs to approve or deny while streaming:

csharp
var coordinator = new WorkflowEventCoordinator();

await foreach (var evt in workflow.ExecuteStreamingAsync(
    "Prepare the release note.",
    coordinator,
    cancellationToken))
{
    if (evt is NodeApprovalRequestEvent approval &&
        ShouldApprove(approval))
    {
        coordinator.Approve(approval.RequestId, "Looks good.");
    }
}

See Bidirectional Events and Permissions Middleware for the broader event-response pattern.

Validator Loop ​

Use a bounded loop when an agent may need to revise until a validator passes.

csharp
var workflow = await AgentWorkflow.Create()
    .WithName("ReviseUntilValid")
    .WithMaxIterations(4)
    .AddAgent("draft", draftConfig, node => node.WithOutputKey("draft"))
    .AddAgent("validate", validateConfig, node => node
        .WithInputKey("draft")
        .StructuredOutput<ValidationResult>())
    .AddAgent("revise", reviseConfig, node => node
        .WithInputKey("draft")
        .WithOutputKey("draft"))
    .From("draft").To("validate")
    .From("validate").To("revise").WhenEquals("passed", false)
    .From("revise").To("validate")
    .From("validate").To("END").WhenEquals("passed", true)
    .BuildAsync();

Keep loops bounded. Route fields should be simple and testable.

Built for production .NET agent applications.