Post

Building Agentic Workflows with Microsoft Agent Framework and C#: Executors, Edges and Conditional Routing

Build controlled agentic workflows with Microsoft Agent Framework and C# using executors, edges, shared state, conditional routing, AI reasoning, and multiple execution paths.

Building Agentic Workflows with Microsoft Agent Framework and C#: Executors, Edges and Conditional Routing

From AI Components to an Executing System

In the previous article, Tools and MCP in Microsoft Agent Framework: Connecting AI to Real Systems with C#, we connected our Release Guardian to real operational capabilities.

The system can now retrieve:

1
2
3
4
Deployment Evidence β†’ Deterministic C# Code
Production Incidents β†’ MCP

It can apply release policy through deterministic code.

And it can pass the collected evidence to an AI agent for contextual risk analysis.

But having all of those components does not automatically give us a production workflow.

Something still needs to answer:

What runs first?

What runs next?

Which steps are mandatory?

What happens when the result changes?

Which path should execution follow?

Where is AI allowed to reason, and where must the application remain in control?

That is the problem of orchestration.

In this article, we will open the ReleaseWorkflow.cs implementation and examine how Microsoft Agent Framework coordinates deterministic software, MCP integration, AI reasoning, security controls, and multiple execution paths inside one controlled workflow.


🎯 What You'll Learn

By the end of this article, you'll understand:

  • How Microsoft Agent Framework models execution as a workflow graph.
  • How Executors represent individual units of work.
  • How Edges make execution order explicit.
  • How conditional edges implement controlled routing.
  • How a shared ReleaseContext evolves across the workflow.
  • How deterministic code, MCP integration, AI reasoning, and human-controlled steps can coexist inside one workflow.
  • Why a workflow with many Executors is not automatically a multi-agent system.

πŸ’» Source Code

The complete AI Production Release Guardian implementation used in this series is available on GitHub:

AI Production Release Guardian


The Architecture We Already Have

Before building the workflow graph, let’s recap the responsibilities already implemented in the Release Guardian.

For Release 2.4, the system currently contains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Deployment Status
      ↓
Deterministic C# Tool

Production Incidents
      ↓
MCP

Release Policy
      ↓
Deterministic Code

Risk Interpretation
      ↓
AI Agent

Each component already knows how to perform its own responsibility.

But none of them should independently decide the lifecycle of the release.

For example:

DeploymentCheckExecutor should retrieve deployment evidence.

It should not decide whether the next step is incident analysis.

RiskAnalysisAgent should analyze technical risk.

It should not decide whether required operational checks may be skipped.

DeploymentExecutor should deploy the release.

It should not decide whether approval was required.

The workflow owns that coordination.

This gives us another important separation:

1
2
3
4
5
6
7
Component
    ↓
Knows how to perform work

Workflow
    ↓
Knows how work is coordinated

AI Production Release Guardian workflow architecture β€”

A Workflow Is an Execution Graph

The Release Guardian workflow is built using WorkflowBuilder.

The starting executor is the deployment check:

1
2
WorkflowBuilder builder =
    new(deploymentCheck);

From there, we explicitly connect the first stages:

1
2
3
4
5
6
7
8
9
10
11
12
builder
    .AddEdge(
        deploymentCheck,
        incidentCheck)

    .AddEdge(
        incidentCheck,
        decision)

    .AddEdge(
        decision,
        riskAnalysis);

Conceptually:

flowchart LR

    Deployment["DeploymentCheckExecutor"]
    Incident["IncidentCheckExecutor"]
    Decision["ReleaseDecisionExecutor"]
    Risk["RiskAnalysisExecutor"]

    Deployment --> Incident
    Incident --> Decision
    Decision --> Risk

This graph is much more than a visual representation.

It defines execution control.

The model does not dynamically decide:

Maybe I should check incidents before deployment status.

And it cannot decide:

I already have enough information, so I will skip release policy.

The application has explicitly defined the order:

1
2
3
4
5
6
7
Deployment Evidence
        ↓
Production Incident Evidence
        ↓
Deterministic Release Policy
        ↓
AI Risk Analysis

This is the practical implementation of the architecture principle we introduced earlier in the series:

Agents provide intelligence. Workflows provide control.


🧠 Architecture Insight

A workflow does not exist because the individual components are incapable of executing themselves. It exists because a production system needs an explicit answer to: Who controls what happens next?


Executors: One Abstraction, Different Responsibilities

A useful detail in the Release Guardian is that many workflow nodes are implemented as Executors.

But they do not all perform the same type of work.

Consider the first four.

DeploymentCheckExecutor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public sealed class DeploymentCheckExecutor
    : Executor<ReleaseContext, ReleaseContext>
{
    public override ValueTask<ReleaseContext> HandleAsync(
        ReleaseContext message,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine(
            "WORKFLOW: Checking deployment status...");

        message.DeploymentStatus =
            ReleaseTools.GetDeploymentStatus();

        return ValueTask.FromResult(message);
    }
}

This Executor invokes deterministic application code.

Its responsibility is:

Retrieve deployment facts.


IncidentCheckExecutor

The next Executor retrieves production incident data through MCP:

1
2
3
4
5
IncidentInfo incidents =
    await ReleaseGuardianMcpClient
        .GetOpenIncidentsAsync(cancellationToken);

message.IncidentInfo = incidents;

Its responsibility is different:

Integrate with an external operational capability.


ReleaseDecisionExecutor

Next, deterministic business policy is applied.

For example:

1
2
3
4
5
6
7
8
9
10
if (message.DeploymentStatus.FailedIntegrationTests > 0)
{
    message.CanDeploy = false;

    message.DecisionReason =
        $"Deployment blocked because " +
        $"{message.DeploymentStatus.FailedIntegrationTests} integration tests failed.";

    return ValueTask.FromResult(message);
}

Its responsibility is:

Enforce release policy.

There is no AI reasoning here.

A failed required integration test remains a failed integration test regardless of what the model thinks about it.


RiskAnalysisExecutor

Only after facts have been retrieved and policy evaluated do we invoke the AI layer.

RiskAnalysisExecutor receives an AIAgent:

1
2
3
4
5
6
7
8
9
10
11
public sealed class RiskAnalysisExecutor
    : Executor<ReleaseContext, ReleaseContext>
{
    private readonly AIAgent _agent;

    public RiskAnalysisExecutor(AIAgent agent)
        : base("RiskAnalysisExecutor")
    {
        _agent = agent;
    }
}

The Executor then invokes the agent using the evidence already available in ReleaseContext.

Its responsibility is:

Interpret risk using AI reasoning.

So even though these components participate in the same Workflow, their architectural roles are different.

ExecutorResponsibilityNature
DeploymentCheckExecutorRetrieve deployment evidenceDeterministic
IncidentCheckExecutorRetrieve incidents through MCPIntegration
ReleaseDecisionExecutorApply release policyDeterministic
RiskAnalysisExecutorInterpret release riskAI reasoning

This is an important point.

Workflow orchestration does not require pretending that every step is an AI agent.


The Shared ReleaseContext

As execution moves through the graph, the Executors operate on a shared application context.

The current implementation contains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ReleaseContext
{
    public string Version { get; set; } = string.Empty;

    public DeploymentStatus? DeploymentStatus { get; set; }

    public IncidentInfo? IncidentInfo { get; set; }

    public bool CanDeploy { get; set; }

    public string DecisionReason { get; set; } = string.Empty;

    public RiskAnalysis? RiskAnalysis { get; set; }

    public bool IsSecurityBlocked { get; set; }

    public string SecurityBlockReason { get; set; } = string.Empty;
}

The context starts small.

For example:

1
2
3
ReleaseContext

Version = 2.4

After the deployment check:

1
2
3
4
ReleaseContext
    β”‚
    β”œβ”€β”€ Version
    └── DeploymentStatus

After the incident check:

1
2
3
4
5
ReleaseContext
    β”‚
    β”œβ”€β”€ Version
    β”œβ”€β”€ DeploymentStatus
    └── IncidentInfo

After deterministic policy:

1
2
3
4
5
6
7
ReleaseContext
    β”‚
    β”œβ”€β”€ Version
    β”œβ”€β”€ DeploymentStatus
    β”œβ”€β”€ IncidentInfo
    β”œβ”€β”€ CanDeploy
    └── DecisionReason

And after AI risk analysis:

1
2
3
4
5
6
7
8
ReleaseContext
    β”‚
    β”œβ”€β”€ Version
    β”œβ”€β”€ DeploymentStatus
    β”œβ”€β”€ IncidentInfo
    β”œβ”€β”€ CanDeploy
    β”œβ”€β”€ DecisionReason
    └── RiskAnalysis

This creates a simple execution model:

1
2
3
4
5
6
7
Read Current Context
        ↓
Perform One Responsibility
        ↓
Enrich Context
        ↓
Pass It Forward

The workflow therefore does not need every component to independently reconstruct the release state.

Each step receives the evidence collected so far and contributes its own result.


Edges Make Execution Explicit

The first part of the workflow is sequential:

1
2
3
4
5
6
7
DeploymentCheck
      ↓
IncidentCheck
      ↓
ReleaseDecision
      ↓
RiskAnalysis

For those steps, normal edges are enough.

But production processes are rarely one straight line.

After risk analysis, the Release Guardian may need to do very different things.

A release may be:

  • safe enough to deploy,
  • high-risk and require human approval,
  • or blocked by a security guardrail.

This is where conditional routing becomes important.


Conditional Routing After AI Analysis

After RiskAnalysisExecutor completes, the workflow defines three possible routes.

Conditional routing after AI risk analysis

The important detail is that the routing itself remains explicit application logic.

Let’s examine each branch.


Path 1: Security Violation β†’ Stop

The first conditional edge handles a security guardrail failure:

1
2
3
4
5
builder.AddEdge<ReleaseContext>(
    riskAnalysis,
    securityBlock,
    message =>
        message.IsSecurityBlocked);

If the AI execution was blocked by the security guardrail, the workflow does not ask the model what should happen.

It routes execution directly to SecurityBlockExecutor.

That Executor makes the outcome clear:

1
SECURITY BLOCK

and explicitly states that human override is not allowed for this type of failure.

Architecturally:

1
2
3
4
5
Security Guardrail Failure
          ↓
Explicit Workflow Route
          ↓
Hard Stop

This is different from normal release risk.

A high-risk release may require governance.

A security violation may represent a boundary that the workflow does not allow anyone to bypass.


⚠️ Production Insight

Not every branch should be delegated to an AI model. If the routing rule represents a deterministic security or policy boundary, encode that boundary directly in the workflow.


Path 2: Safe Release β†’ Deploy Directly

The second route handles a release that:

  • is not security blocked,
  • passes deterministic release policy,
  • and is not classified as High or Critical risk.

The edge is:

1
2
3
4
5
6
7
builder.AddEdge<ReleaseContext>(
    riskAnalysis,
    deployment,
    message =>
        !message.IsSecurityBlocked &&
        message.CanDeploy &&
        !IsHighRisk(message));

Conceptually:

1
2
3
4
5
6
7
No Security Block
      +
CanDeploy = true
      +
Risk != High/Critical
      ↓
Deployment

The interesting point here is that the AI risk classification participates in the routing decision, but AI does not own the routing mechanism.

The workflow evaluates the resulting state and follows a predefined branch.

That distinction matters.

The model can contribute:

1
RiskLevel = High

But application code decides what High means for workflow execution.


Path 3: Unsafe or High-Risk β†’ Human Approval

The third route handles a release that is either:

  • blocked by deterministic release policy,
  • or classified by AI as High/Critical risk.
1
2
3
4
5
6
7
8
9
builder.AddEdge<ReleaseContext>(
    riskAnalysis,
    approvalRequest,
    message =>
        !message.IsSecurityBlocked &&
        (
            !message.CanDeploy ||
            IsHighRisk(message)
        ));

Conceptually:

1
2
3
4
5
6
7
8
9
Not Security Blocked
        ↓
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚               β”‚
NO-GO        High/Critical
 β”‚               β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
         ↓
 Human Approval

Notice the architectural boundary.

The AI can identify risk.

The deterministic policy can identify a release violation.

But neither automatically becomes the final authorization mechanism.

Instead, the workflow routes selected cases toward a human-controlled step.

We will explore that mechanism in detail in the next article.


Turning Risk Levels Into Workflow Rules

The Workflow contains a small helper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static bool IsHighRisk(
    ReleaseContext context)
{
    string? riskLevel =
        context.RiskAnalysis?.RiskLevel;

    return
        string.Equals(
            riskLevel,
            "High",
            StringComparison.OrdinalIgnoreCase)
        ||
        string.Equals(
            riskLevel,
            "Critical",
            StringComparison.OrdinalIgnoreCase);
}

This looks like a small implementation detail, but it demonstrates an important architecture principle.

The AI produces a semantic result:

1
RiskLevel = High

The application translates that result into deterministic execution policy:

1
2
3
High
  ↓
Requires human approval

So the ownership remains clear:

1
2
3
4
5
6
7
8
9
10
11
AI
 ↓
Interprets evidence

Application
 ↓
Interprets policy implications

Workflow
 ↓
Controls execution

βœ… Design Principle

Let AI produce reasoning outputs. Let deterministic application logic decide how those outputs affect mandatory business process execution.


Human Interaction Is Also Part of the Graph

The approval path does not live outside the workflow architecture.

After ApprovalRequestExecutor, execution moves to a request port:

1
2
3
builder.AddEdge(
    approvalRequest,
    approvalPort);

The response then returns to a normal Executor:

1
2
3
builder.AddEdge(
    approvalPort,
    approvalRouter);

From there, the workflow branches again.

Approved:

1
2
3
4
5
builder.AddEdge<ApprovalDecision>(
    approvalRouter,
    approvedRelease,
    approvalDecision =>
        approvalDecision.Approved);

Rejected:

1
2
3
4
5
builder.AddEdge<ApprovalDecision>(
    approvalRouter,
    rejection,
    approvalDecision =>
        !approvalDecision.Approved);

And an approved release eventually reaches deployment:

1
2
3
builder.AddEdge(
    approvedRelease,
    deployment);

Conceptually:

flowchart TB

    Approval["ApprovalRequestExecutor"]
    Port["RequestPort"]
    Router["ApprovalRoutingExecutor"]

    Approved["ApprovedReleaseExecutor"]
    Rejected["RejectionExecutor"]
    Deploy["DeploymentExecutor"]

    Approval --> Port
    Port --> Router

    Router -->|"Approved"| Approved
    Router -->|"Rejected"| Rejected

    Approved --> Deploy

This means human governance is not an informal process happening outside the application.

It is represented as part of the workflow graph.

We will go much deeper into pausing execution, external input, approval decisions, and workflow continuation in the next part of this series.


Multiple Terminal Outcomes

A production workflow does not always end with success.

The Release Guardian currently has three possible final outcomes:

1
2
3
Deployment
Rejection
Security Block

The workflow explicitly defines them:

1
2
3
4
builder.WithOutputFrom(
    deployment,
    rejection,
    securityBlock);

This is useful because the system does not assume:

1
2
3
Workflow completed
        =
Deployment happened

Completion may instead mean:

1
Release deployed

or:

1
Release rejected

or:

1
Release blocked by security policy

Those are all legitimate workflow outcomes.

The execution graph owns the path that reaches them.


The Complete Release Guardian Workflow

Putting the pieces together, the current workflow looks conceptually like this:

Complete AI Production Release Guardian workflow with conditional routing and human approval

Now the architecture boundary becomes much easier to see.

The workflow contains:

1
2
3
4
5
6
7
8
9
10
11
12
13
Facts
 +
Integrations
 +
Policy
 +
AI Reasoning
 +
Security Controls
 +
Human Governance
 +
Deployment

But each responsibility remains separate.


Why This Is Still Not a Multi-Agent System

At this point, the Release Guardian contains many Executors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DeploymentCheckExecutor

IncidentCheckExecutor

ReleaseDecisionExecutor

RiskAnalysisExecutor

ApprovalRequestExecutor

ApprovalRoutingExecutor

ApprovedReleaseExecutor

DeploymentExecutor

RejectionExecutor

SecurityBlockExecutor

It may be tempting to look at that graph and describe it as a multi-agent system.

But that would be incorrect.

Executor does not mean Agent.

Executors versus AI agents in the Release Guardian workflow

In the current implementation, only one workflow step wraps an actual AI agent:

1
2
3
RiskAnalysisExecutor
        ↓
RiskAnalysisAgent

The other Executors perform deterministic operations, integration, routing, or terminal actions.

So the architecture is currently:

1
2
3
4
5
Many Executors
      +
One AI Agent
      =
Agentic Workflow

Not:

1
2
3
Many Executors
      =
Multi-Agent System

This distinction is important because otherwise every workflow graph can quickly become mislabeled as β€œmulti-agent.”

And that brings us back to a principle from earlier in the series:

The goal is not to maximize the number of agents.

The goal is to assign each responsibility to the simplest component that can reliably perform it.


🧠 Architecture Insight

A workflow with many Executors is not automatically a multi-agent system. An Executor is an orchestration unit. An Agent is a reasoning component. Those are different architectural concepts.


When Would Multiple Agents Actually Make Sense?

The current Release Guardian does not need multiple agents simply because Microsoft Agent Framework supports multi-agent orchestration.

One risk analysis agent can currently interpret:

  • deployment failures,
  • production incidents,
  • affected services,
  • deterministic release decisions,
  • and potential production impact.

That may be enough.

But imagine the requirements grow.

We may eventually want independent specialized perspectives:

1
2
3
                   β”Œβ”€β”€ Security Risk Agent
Release Evidence ───
                   └── Reliability Risk Agent

Or perhaps one agent performs initial triage and transfers responsibility to the most appropriate specialist.

Those requirements may justify patterns such as:

1
2
3
4
5
6
7
Sequential

Agent A
   ↓
Agent B
   ↓
Agent C
1
2
3
4
5
Concurrent

        β”Œβ”€β”€ Agent A ──┐
Input ───             β”œβ”€β”€ Combined Result
        └── Agent B β”€β”€β”˜
1
2
3
4
5
6
7
Handoff

Triage Agent
     β”‚
     β”œβ”€β”€β†’ Security Agent
     β”‚
     └──→ Reliability Agent

But the architecture decision should come first.

We should ask:

Do these agents genuinely require different responsibilities, context, tools, or independent reasoning?

If one agent with good tools can solve the problem reliably, adding more agents may only increase coordination cost.

So before building a multi-agent system:

Prove that specialization adds architectural value.


Orchestration Is About Responsibility, Not Complexity

It is easy to associate agentic systems with increasingly complex diagrams.

More agents.

More tools.

More branches.

More autonomous behavior.

But production architecture should move in the opposite direction.

Every component should have a clear reason to exist.

For the current Release Guardian:

ResponsibilityOwner
Retrieve deployment factsDeterministic C#
Retrieve production incidentsMCP integration
Enforce release policyDeterministic Executor
Interpret technical riskAI Agent
Decide execution pathWorkflow
Enforce security stopWorkflow + deterministic state
Request high-risk authorizationHuman-controlled workflow path
Execute deploymentDeterministic Executor

The architecture boundary between workflow orchestration and AI reasoning

This gives us explicit architectural boundaries.

And those boundaries matter much more than the number of AI components in the diagram.


What We Added in Part 4

The previous article gave the Release Guardian access to real operational evidence.

In this part, we connected those capabilities into one execution model.

The architecture now contains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Release Request
      ↓
Workflow Graph
      ↓
Deployment Evidence
      ↓
Production Incident Evidence
      ↓
Deterministic Policy
      ↓
AI Risk Analysis
      ↓
Conditional Routing
   β”Œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   ↓      ↓          ↓
Deploy  Approval   Security Block

The important addition is not another AI capability.

It is controlled execution.

We now have explicit answers to:

  • which component runs first,
  • which steps are mandatory,
  • where AI reasoning happens,
  • how AI output affects routing,
  • when deployment can proceed,
  • when execution must stop,
  • and when the process requires external human input.

Next: Human-in-the-Loop AI

Our workflow now contains an interesting boundary.

A release may reach this state:

1
2
3
4
5
6
Deterministic Decision: NO-GO

Risk Level: High

AI Recommendation:
Do not deploy until the production incident is resolved.

But the workflow does not necessarily end there.

For selected conditions, execution moves to:

1
Human Approval Required

That introduces several new engineering questions:

How does a workflow pause while waiting for a human?

How is the approval request represented?

What happens to workflow execution while the application is stopped?

Can we restore the pending approval later?

How does the human decision return to the workflow?

And which decisions should never allow human override?

These questions move us from orchestration into governance.

In the next article, we will build and examine the Human-in-the-Loop path of the Release Guardian.


Final Thoughts

Building an AI agent is only one part of building an agentic system.

Once that agent participates in a real business process, we need explicit answers to:

1
2
3
4
5
6
7
8
9
10
11
What runs?

When?

In what order?

Under which conditions?

Who owns the decision?

What happens when something goes wrong?

Microsoft Agent Framework workflows give us a way to represent those answers as an execution graph.

In the Release Guardian, that graph coordinates:

1
2
3
4
5
6
7
8
9
10
11
Deterministic Functions
        +
MCP Integration
        +
Business Policy
        +
AI Reasoning
        +
Security Controls
        +
Human Governance

without forcing all of those responsibilities into one autonomous agent.

And that leads to the principle I want to carry forward from this article:

Agentic architecture is not about giving AI control of the workflow. It is about designing exactly where AI belongs inside a controlled workflow.

The next challenge is deciding what happens when control must temporarily leave the software entirely and move to a human.


Source Code

You can explore the complete implementation of the AI Production Release Guardian on GitHub:

πŸ‘‰ AI Production Release Guardian


References

This post is licensed under CC BY 4.0 by the author.