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 Step and Plan structures
enum class StepStatus { Pending, Running, Done, Failed };
struct Step {
std::string id;
std::string description;
StepStatus status = StepStatus::Pending;
std::string result;
};
struct Plan {
std::string goal;
std::vector<Step> steps;
};
using PlannerFn = std::function<std::vector<Step>(const std::string&)>;
using ExecutorFn = std::function<std::string(const Step&)>;2Step 2: Implement the plan-and-execute pipeline
Plan planAndExecute(const std::string& goal,
PlannerFn planner,
ExecutorFn executor) {
Plan plan{goal, planner(goal)};
for (auto& step : plan.steps) {
step.status = StepStatus::Running;
try {
step.result = executor(step);
step.status = StepStatus::Done;
} catch (const std::exception& e) {
step.result = e.what();
step.status = StepStatus::Failed;
break;
}
}
return plan;
}3Step 3: Run with sample data
int main() {
auto plan = planAndExecute(
"Deploy a service",
[](const std::string&) -> std::vector<Step> {
return {{"1", "Build image", StepStatus::Pending, ""},
{"2", "Run tests", StepStatus::Pending, ""},
{"3", "Deploy", StepStatus::Pending, ""}};
},
[](const Step& s) { return "Completed: " + s.description; }
);
for (const auto& s : plan.steps) {
const char* st = s.status == StepStatus::Done ? "DONE" : "FAIL";
std::cout << "[" << st << "] " << s.description << "\n";
}
}#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <functional>
#include <memory>
#include <format>
#include <chrono>
#include <stdexcept>
#include <algorithm>
// [step] Define step status, plan step, and execution plan types
enum class StepStatus { Pending, Blocked, Running, Done, Failed, Skipped };
struct PlanStep {
std::string id;
std::string description;
std::vector<std::string> dependsOn;
StepStatus status = StepStatus::Pending;
std::string result;
std::string error;
int attempts = 0;
std::chrono::steady_clock::time_point startedAt;
std::chrono::steady_clock::time_point completedAt;
};
struct ExecutionPlan {
std::string goal;
std::vector<PlanStep> steps;
int version = 1;
};
struct ExecutorContext {
std::string goal;
std::map<std::string, std::string> completedResults;
const ExecutionPlan& plan;
};
using StepExecutor = std::function<std::string(PlanStep&, const ExecutorContext&)>;
using PlannerFn = std::function<std::vector<PlanStep>(const std::string&,
const std::vector<PlanStep>* failedSteps)>;
using ProgressCallback = std::function<void(const PlanStep&, const ExecutionPlan&)>;
// [step] Build the PlanAndExecuteAgent with replanning and dependency tracking
class PlanAndExecuteAgent {
PlannerFn planner_;
StepExecutor executor_;
ProgressCallback onProgress_;
int maxReplanAttempts_ = 2;
public:
PlanAndExecuteAgent(PlannerFn planner, StepExecutor executor,
ProgressCallback onProgress = nullptr)
: planner_(std::move(planner)),
executor_(std::move(executor)),
onProgress_(std::move(onProgress)) {}
ExecutionPlan run(const std::string& goal) {
ExecutionPlan plan{goal, planner_(goal, nullptr), 1};
int replanCount = 0;
while (true) {
auto ready = getReadySteps(plan);
if (ready.empty()) break;
std::vector<PlanStep*> failed;
for (auto* step : ready) {
step->status = StepStatus::Running;
step->startedAt = std::chrono::steady_clock::now();
step->attempts++;
if (onProgress_) onProgress_(*step, plan);
ExecutorContext ctx{goal, getCompletedResults(plan), plan};
try {
step->result = executor_(*step, ctx);
step->status = StepStatus::Done;
} catch (const std::exception& e) {
step->error = e.what();
step->status = StepStatus::Failed;
failed.push_back(step);
}
step->completedAt = std::chrono::steady_clock::now();
if (onProgress_) onProgress_(*step, plan);
}
if (!failed.empty() && replanCount < maxReplanAttempts_) {
++replanCount;
std::vector<PlanStep> failedCopy;
for (auto* f : failed) failedCopy.push_back(*f);
auto newSteps = planner_(goal, &failedCopy);
for (auto& ns : newSteps) plan.steps.push_back(std::move(ns));
plan.version++;
continue;
}
if (!failed.empty()) break;
}
// Mark remaining as skipped
for (auto& s : plan.steps) {
if (s.status == StepStatus::Pending || s.status == StepStatus::Blocked)
s.status = StepStatus::Skipped;
}
return plan;
}
private:
std::vector<PlanStep*> getReadySteps(ExecutionPlan& plan) {
std::vector<PlanStep*> ready;
for (auto& step : plan.steps) {
if (step.status != StepStatus::Pending) continue;
bool depsOk = std::ranges::all_of(step.dependsOn, [&](const std::string& dep) {
return std::ranges::any_of(plan.steps, [&](const PlanStep& s) {
return s.id == dep && s.status == StepStatus::Done;
});
});
if (depsOk) ready.push_back(&step);
}
return ready;
}
std::map<std::string, std::string> getCompletedResults(const ExecutionPlan& plan) {
std::map<std::string, std::string> results;
for (const auto& s : plan.steps)
if (s.status == StepStatus::Done) results[s.id] = s.result;
return results;
}
};
// [step] Demo with dependent steps
int main() {
PlanAndExecuteAgent agent(
[](const std::string&, const std::vector<PlanStep>*) -> std::vector<PlanStep> {
return {
{"1", "Compile code", {}, StepStatus::Pending, "", "", 0, {}, {}},
{"2", "Run unit tests", {"1"}, StepStatus::Pending, "", "", 0, {}, {}},
{"3", "Deploy to staging", {"2"}, StepStatus::Pending, "", "", 0, {}, {}},
};
},
[](PlanStep& step, const ExecutorContext&) -> std::string {
std::cout << std::format("Executing: {}\n", step.description);
return "OK: " + step.description;
},
[](const PlanStep& step, const ExecutionPlan&) {
const char* status = step.status == StepStatus::Done ? "DONE"
: step.status == StepStatus::Running ? "RUN" : "?";
std::cout << std::format("[{}] {}\n", status, step.description);
}
);
auto plan = agent.run("Ship the release");
std::cout << std::format("Plan v{} complete\n", plan.version);
}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.”