This episode turns one agent into a team: learning communication patterns between agents, building an orchestrator that delegates subtasks to workers and merges the results, and devising arbitration and consistency strategies so many agents can work simultaneously.

In episode 16 you expanded the agent's capabilities with custom actions, domain-specific tools, and plugins packaged as modules. Now imagine you have not one but several agents with different expertise: one great at research, one great at data analysis, and another great at communication. The question: how do they work together without tripping each other up?
Here is the roadmap for this episode: recognizing when multi-agent is actually the right call, understanding communication patterns between agents, building an orchestrator that delegates subtasks and merges results, and then devising consistency and arbitration strategies when agent results collide.
One agent has limits: a bounded context window, a ballooning tool set, and all decisions passing through one brain. When a task starts requiring a lot of knowledge at once — compare prices from three sources, analyze large logs, then write the summary — a single agent will be slow and easily "lost" in the middle of a bursting context.
Multi-agent splits the load: each worker has a small context and focused tools, and the orchestrator only manages the traffic. But this is not a free solution. Every agent is a token cost, and coordination adds latency. The practical rule: start with one agent, move to multi-agent only when there is a clear bottleneck — a context window running out, too many tools of one kind, or one agent overwhelmed by parallel tasks.
There are three common communication patterns, and each suits a different situation:
The Hermes bus is configured through channel declarations:
channels:
orders:
type: topic
durability: 24h
support:
type: directUse direct for results that must return to the sender, and topics for events relevant to many agents at once. Shared memory is the most efficient for shared state, but save it for the very end of this episode — its consistency needs special attention.
The most widely used pattern is orchestrator-worker: one orchestrator agent breaks a big task into subtasks, delegates each subtask to a competent worker, waits for results, and merges them. The orchestrator does not need to know the execution details — it only makes sure everything runs and the results are complete.
import { HermesAgent } from "@hermes/sdk";
const orchestrator = new HermesAgent({ profile: "./profiles/orchestrator.ts" });
async function runBatch(task, subtasks) {
const results = await Promise.all(
subtasks.map((sub) =>
orchestrator.spawn({
agent: sub.agent,
task: sub.task,
replyTo: "orchestrator",
}),
),
);
return orchestrator.merge(results);
}The flow: the orchestrator breaks the task into a list of subtasks, each worker is spawned with its task and replyTo as the reply address, results are collected in parallel, and then merge assembles them into one coherent answer. Because each worker has its own context, the model is not overwhelmed by the combined context. In the CLI, manual spawning is available via hermes agent spawn to test one worker before wiring it into a pipeline.
Once several agents touch the same data, consistency becomes a problem. Two workers editing the same record in parallel can overwrite each other. Three of the most practical strategies:
export async function writeOrder(store, orderId, patch) {
const current = await store.get(orderId);
if (patch.baseVersion !== current.version) {
throw new Error("stale write: versi sudah berubah");
}
return store.set(orderId, {
...current,
...patch.data,
version: current.version + 1,
});
}This pattern rejects "stale" writes up front instead of silently overwriting another worker's data. If conflicts still happen, they must be handled explicitly — and that is where arbitration comes in.
When several agents give different answers to the same question, you need rules to pick one. Arbitration is that mechanism. The most common strategies: pick the one with the highest confidence, or ask for a majority — and if none is confident enough, escalate to a human rather than force it.
export function arbitrate(results) {
const ranked = [...results].sort((a, b) => b.confidence - a.confidence);
const best = ranked[0];
if (best.confidence < 0.6) {
return { winner: null, escalate: true };
}
return { winner: best, escalate: false };
}Arbitration rules are best declared, not hard-coded: the configuration contains the strategy (confidence, majority, priority), the threshold that triggers escalation, and where results go up if there is no winner. Consistency maintained from the moment of state writing, plus clear arbitration at the point of collision, are the two sides that make an agent team work like one system — not five applications that happen to share a database.
Info
Do not let arbitration create an agent that never makes a wrong choice. Log every arbitration decision to the observability logs (episode 7) and compare them with the final outcome; that way your arbitration rules keep improving from data, not from feelings.
Episode 17 assembled your agents into a team. You know when multi-agent is worth using, choose a communication pattern between direct channel, message bus, and shared memory, build an orchestrator that splits tasks and delegates subtasks to workers, then maintain consistency with single writer and idempotency and resolve result clashes through confidence-based arbitration.
Key takeaways:
In the next episode 18 we give this agent team self-awareness: Adaptive Behavior & Reflection — a reflective loop for evaluating its own results, monitoring confidence and uncertainty, and changing strategy based on feedback. See you there!