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.
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
ReleaseContextevolves 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:
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
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.
| Executor | Responsibility | Nature |
|---|---|---|
DeploymentCheckExecutor | Retrieve deployment evidence | Deterministic |
IncidentCheckExecutor | Retrieve incidents through MCP | Integration |
ReleaseDecisionExecutor | Apply release policy | Deterministic |
RiskAnalysisExecutor | Interpret release risk | AI 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.
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:
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.
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:
| Responsibility | Owner |
|---|---|
| Retrieve deployment facts | Deterministic C# |
| Retrieve production incidents | MCP integration |
| Enforce release policy | Deterministic Executor |
| Interpret technical risk | AI Agent |
| Decide execution path | Workflow |
| Enforce security stop | Workflow + deterministic state |
| Request high-risk authorization | Human-controlled workflow path |
| Execute deployment | Deterministic Executor |
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




