Agentic AIC++verifiedVerified
Multi-Agent Orchestration Pattern in C++
Coordinate a network of specialised AI agents under an orchestrator, where each agent owns a distinct capability or domain and agents communicate through structured messages.
How to Implement the Multi-Agent Orchestration Pattern in C++
1Step 1: Define the message and agent interfaces
struct AgentMessage {
std::string from;
std::string to;
std::string type; // "task" | "result" | "error"
std::string payload;
};
class IAgent {
public:
virtual ~IAgent() = default;
virtual std::string id() const = 0;
virtual std::vector<std::string> capabilities() const = 0;
virtual AgentMessage handle(const AgentMessage& msg) = 0;
};2Step 2: Build the Orchestrator with registration and dispatch
class Orchestrator {
std::map<std::string, std::unique_ptr<IAgent>> agents_;
public:
void registerAgent(std::unique_ptr<IAgent> agent) {
agents_.emplace(agent->id(), std::move(agent));
}
IAgent* findByCapability(const std::string& cap) {
for (auto& [_, agent] : agents_)
for (const auto& c : agent->capabilities())
if (c == cap) return agent.get();
return nullptr;
}
std::string dispatch(const std::string& capability,
const std::string& payload) {
auto* agent = findByCapability(capability);
if (!agent) return "No agent for: " + capability;
AgentMessage msg{"orchestrator", agent->id(), "task", payload};
auto reply = agent->handle(msg);
return reply.payload;
}
};3Step 3: Demonstrate with two specialist agents
class WriterAgent : public IAgent {
public:
std::string id() const override { return "writer"; }
std::vector<std::string> capabilities() const override { return {"write"}; }
AgentMessage handle(const AgentMessage& msg) override {
return {id(), msg.from, "result", "Draft: " + msg.payload};
}
};
class ReviewerAgent : public IAgent {
public:
std::string id() const override { return "reviewer"; }
std::vector<std::string> capabilities() const override { return {"review"}; }
AgentMessage handle(const AgentMessage& msg) override {
return {id(), msg.from, "result", "Reviewed: " + msg.payload};
}
};
int main() {
Orchestrator orch;
orch.registerAgent(std::make_unique<WriterAgent>());
orch.registerAgent(std::make_unique<ReviewerAgent>());
auto draft = orch.dispatch("write", "blog post about C++");
auto review = orch.dispatch("review", draft);
std::cout << review << "\n";
}#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <functional>
#include <format>
#include <chrono>
#include <mutex>
#include <any>
#include <stdexcept>
#include <algorithm>
// [step] Define message, capability, and shared state types
enum class MessageType { Task, Result, Error, Broadcast, Heartbeat };
enum class AgentStatus { Idle, Busy, ErrorState, Offline };
struct AgentMessage {
int id;
int correlationId = -1;
std::string from;
std::string to;
MessageType type;
std::string payload;
std::chrono::steady_clock::time_point timestamp;
};
struct Capability {
std::string name;
std::string description;
};
// [step] Implement thread-safe shared state
class SharedState {
std::map<std::string, std::any> store_;
mutable std::mutex mu_;
public:
template <typename T>
void set(const std::string& key, T value) {
std::lock_guard lk(mu_);
store_[key] = std::move(value);
}
template <typename T>
std::optional<T> get(const std::string& key) const {
std::lock_guard lk(mu_);
auto it = store_.find(key);
if (it == store_.end()) return std::nullopt;
return std::any_cast<T>(it->second);
}
void erase(const std::string& key) {
std::lock_guard lk(mu_);
store_.erase(key);
}
};
// [step] Define the BaseAgent with message handling and logging
class BaseAgent {
protected:
AgentStatus status_ = AgentStatus::Idle;
std::vector<AgentMessage> log_;
std::shared_ptr<SharedState> state_;
std::string id_;
std::vector<Capability> caps_;
public:
BaseAgent(std::string id, std::vector<Capability> caps,
std::shared_ptr<SharedState> state)
: id_(std::move(id)), caps_(std::move(caps)), state_(std::move(state)) {}
virtual ~BaseAgent() = default;
const std::string& agentId() const { return id_; }
const std::vector<Capability>& capabilities() const { return caps_; }
AgentStatus status() const { return status_; }
virtual std::string processTask(const std::string& payload) = 0;
AgentMessage receive(const AgentMessage& msg) {
log_.push_back(msg);
status_ = AgentStatus::Busy;
if (msg.type == MessageType::Heartbeat) {
status_ = AgentStatus::Idle;
return reply(msg, MessageType::Result, "alive");
}
try {
auto result = processTask(msg.payload);
status_ = AgentStatus::Idle;
return reply(msg, MessageType::Result, result);
} catch (const std::exception& e) {
status_ = AgentStatus::ErrorState;
return reply(msg, MessageType::Error, e.what());
}
}
private:
static inline int nextId_ = 0;
AgentMessage reply(const AgentMessage& orig, MessageType type,
std::string payload) {
return {nextId_++, orig.id, id_, orig.from, type,
std::move(payload), std::chrono::steady_clock::now()};
}
};
// [step] Build the MultiAgentOrchestrator with routing and pipeline
class MultiAgentOrchestrator {
std::map<std::string, std::unique_ptr<BaseAgent>> agents_;
std::shared_ptr<SharedState> state_;
int nextMsgId_ = 0;
public:
explicit MultiAgentOrchestrator(std::shared_ptr<SharedState> state = nullptr)
: state_(state ? std::move(state) : std::make_shared<SharedState>()) {}
void registerAgent(std::unique_ptr<BaseAgent> agent) {
agents_.emplace(agent->agentId(), std::move(agent));
}
BaseAgent* findByCapability(const std::string& cap) {
for (auto& [_, agent] : agents_)
for (const auto& c : agent->capabilities())
if (c.name == cap) return agent.get();
return nullptr;
}
std::string send(const std::string& toId, const std::string& payload) {
auto it = agents_.find(toId);
if (it == agents_.end())
throw std::runtime_error(std::format("Agent '{}' not found", toId));
AgentMessage msg{nextMsgId_++, -1, "orchestrator", toId,
MessageType::Task, payload,
std::chrono::steady_clock::now()};
auto reply = it->second->receive(msg);
if (reply.type == MessageType::Error)
throw std::runtime_error("Agent error: " + reply.payload);
return reply.payload;
}
std::vector<std::string> runPipeline(
const std::vector<std::pair<std::string, std::string>>& steps) {
std::vector<std::string> results;
std::string lastResult;
for (const auto& [cap, payload] : steps) {
auto* agent = findByCapability(cap);
if (!agent) throw std::runtime_error("No agent for: " + cap);
auto input = payload.empty() ? lastResult : payload;
lastResult = send(agent->agentId(), input);
results.push_back(lastResult);
}
return results;
}
};
// [step] Concrete agents and demo
class AnalystAgent : public BaseAgent {
public:
using BaseAgent::BaseAgent;
std::string processTask(const std::string& payload) override {
state_->set("analysis", std::string("Analyzed: " + payload));
return "Analysis of: " + payload;
}
};
class SummarizerAgent : public BaseAgent {
public:
using BaseAgent::BaseAgent;
std::string processTask(const std::string& payload) override {
auto prev = state_->get<std::string>("analysis");
return std::format("Summary [prev={}]: {}",
prev.value_or("none"), payload);
}
};
int main() {
auto state = std::make_shared<SharedState>();
MultiAgentOrchestrator orch(state);
orch.registerAgent(std::make_unique<AnalystAgent>(
"analyst", std::vector<Capability>{{"analyze", "Data analysis"}}, state));
orch.registerAgent(std::make_unique<SummarizerAgent>(
"summarizer", std::vector<Capability>{{"summarize", "Text summary"}}, state));
auto results = orch.runPipeline({
{"analyze", "Q3 revenue data"},
{"summarize", ""},
});
for (const auto& r : results) std::cout << r << "\n";
}Multi-Agent Orchestration Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Multi-Agent Orchestration Pattern in the Real World
“A film director (Orchestrator) does not personally operate the camera, compose the score, or design costumes. Instead they delegate to specialist department heads — cinematographer, composer, costume designer — each expert in their domain. The director collects their work, gives feedback, and integrates it into a coherent film.”