Agentic AIC#verifiedVerified
Plan-and-Execute Pattern in C#
Separate high-level planning from step-by-step execution: one LLM call produces a structured plan, then individual executor calls carry out each step, with replanning triggered by unexpected results.
How to Implement the Plan-and-Execute Pattern in C#
1Step 1: Define the plan structure
public record PlanStep(string Description, string ToolName, string Input);
public record Plan(IReadOnlyList<PlanStep> Steps);2Step 2: Planner creates a step-by-step plan
public interface IPlanner
{
Task<Plan> CreatePlanAsync(string task);
}3Step 3: Executor runs each step sequentially
public interface IExecutor
{
Task<string> ExecuteStepAsync(PlanStep step);
}
public static class PlanAndExecute
{
public static async Task<string> RunAsync(
string task, IPlanner planner, IExecutor executor)
{
// Phase 1: Plan
var plan = await planner.CreatePlanAsync(task);
// Phase 2: Execute each step
var results = new List<string>();
foreach (var step in plan.Steps)
{
var result = await executor.ExecuteStepAsync(step);
results.Add(result);
}
return string.Join("\n", results);
}
}using Microsoft.Extensions.Logging;
// [step] Define rich plan types with replanning support
public enum StepStatus { Pending, Running, Completed, Failed, Skipped }
public record PlanStep(
int Id, string Description, string ToolName, string Input,
IReadOnlyList<int> DependsOn)
{
public StepStatus Status { get; set; } = StepStatus.Pending;
public string? Result { get; set; }
public string? Error { get; set; }
}
public record Plan(
string Goal, List<PlanStep> Steps, DateTime CreatedAt);
public record ExecutionResult(
Plan Plan, bool Success, TimeSpan Duration,
IReadOnlyList<string> StepResults);
// [step] Planner with replanning capability
public interface IPlanner
{
Task<Plan> CreatePlanAsync(string task, CancellationToken ct = default);
Task<Plan> ReplanAsync(Plan current, int failedStepId,
string error, CancellationToken ct = default);
}
// [step] Executor with dependency resolution
public interface IStepExecutor
{
Task<string> ExecuteAsync(
PlanStep step, IReadOnlyDictionary<int, string> previousResults,
CancellationToken ct = default);
}
// [step] Production orchestrator with replanning and telemetry
public sealed class PlanAndExecuteOrchestrator(
IPlanner planner, IStepExecutor executor,
ILogger<PlanAndExecuteOrchestrator> logger)
{
private const int MaxReplans = 3;
public async Task<ExecutionResult> RunAsync(
string task, CancellationToken ct = default)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var plan = await planner.CreatePlanAsync(task, ct);
var results = new Dictionary<int, string>();
var replans = 0;
logger.LogInformation("Created plan with {Count} steps", plan.Steps.Count);
for (var i = 0; i < plan.Steps.Count; i++)
{
ct.ThrowIfCancellationRequested();
var step = plan.Steps[i];
// Check dependencies
var unmet = step.DependsOn
.Where(d => !results.ContainsKey(d)).ToList();
if (unmet.Count > 0)
{
step.Status = StepStatus.Skipped;
logger.LogWarning("Skipping step {Id}: unmet deps", step.Id);
continue;
}
step.Status = StepStatus.Running;
try
{
var result = await executor.ExecuteAsync(step, results, ct);
step.Result = result;
step.Status = StepStatus.Completed;
results[step.Id] = result;
logger.LogDebug("Step {Id} completed", step.Id);
}
catch (Exception ex) when (replans < MaxReplans)
{
step.Status = StepStatus.Failed;
step.Error = ex.Message;
logger.LogWarning(ex, "Step {Id} failed, replanning", step.Id);
plan = await planner.ReplanAsync(plan, step.Id, ex.Message, ct);
i = -1; // Restart with new plan
replans++;
}
}
return new ExecutionResult(plan,
plan.Steps.All(s => s.Status is StepStatus.Completed or StepStatus.Skipped),
sw.Elapsed, results.Values.ToList().AsReadOnly());
}
}Plan-and-Execute Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Plan-and-Execute Pattern in the Real World
“A building contractor (Planner) reviews the architectural blueprints and produces a phased construction schedule: foundation, framing, electrical, finishing. Individual trade crews (Executors) carry out each phase. If an inspection fails (unexpected result), the contractor revises the remaining schedule rather than demolishing the entire building and starting over.”