Agent Swarms
Build collaborative swarms of reactive agents that share one event-driven agentic environment and adapt to each other in real time.
An agent swarm is a group of reactive agents that share one AgenticEnvironment and work toward the same goal — like a team collaborating in real time instead of a single agent doing everything alone. Each agent reacts to events on the bus, contributes its own, and adapts to what the others do.
Mozaik's event-driven model is what makes swarms practical: there is no central scheduler to update when you add an agent, and no slow participant can hold up the others.
From single agent to a swarm
A swarm is the same primitives, scaled up:
- One
AgenticEnvironment— the shared event bus. - Several reactive agents, each with a focused role (planner, executor, reviewer, librarian, auditor, …).
- Optional observers (transcript logger, UI stream, metrics) that listen but don't produce.
Each agent overrides only the handlers relevant to its role. Behavior emerges from how they react to each other.
flowchart LR
Human[Human] -->|onMessage| Env(("AgenticEnvironment"))
Env -->|onMessage| Planner[Planner]
Planner -->|"ModelMessageItem (plan)"| Env
Env -->|onExternalModelMessage| ExecA[Executor A]
Env -->|onExternalModelMessage| ExecB[Executor B]
Env -->|onExternalModelMessage| Reviewer[Reviewer]
ExecA -->|"ModelMessageItem / FunctionCallItem"| Env
ExecB -->|"ModelMessageItem / FunctionCallItem"| Env
Reviewer -->|"ModelMessageItem (critique)"| Env
Env -->|"onMessage / onExternal*"| Logger[TranscriptLogger]
Pattern: planner + executors + reviewer
A common swarm layout:
- Planner — turns a user goal into structured next steps. Reacts to
onMessage, emits aModelMessageItemwith the plan. - Executors — each one watches
onExternalModelMessagefor plan items in its domain and runs the work (often by triggering its ownrunInference+ tools). - Reviewer — watches
onExternalModelMessagefrom the executors and emits a critique (anotherModelMessageItem). The planner can react to that critique and replan. - Observer(s) — a
TranscriptLogger-style participant for UI streaming, audit, or metrics.
A conceptual 2-agent sketch (planner + executor):
import {
BaseParticipant,
Participant,
ModelMessageItem,
UserMessageItem,
AgenticEnvironment,
ModelContext,
runInference,
} from '@mozaik-ai/core';
export class PlannerAgent extends BaseParticipant {
constructor(
private env: AgenticEnvironment,
private ctx: ModelContext,
) {
super();
}
async onMessage(message: string): Promise<void> {
this.ctx.addContextItem(UserMessageItem.create(`Plan this goal: ${message}`));
runInference({ model: 'gpt-5.5', context: this.ctx, caller: this, environment: this.env });
}
}
export class ExecutorAgent extends BaseParticipant {
constructor(
private env: AgenticEnvironment,
private ctx: ModelContext,
) {
super();
}
// React to *another* agent's model message (e.g. the planner's plan).
async onExternalModelMessage(source: Participant, item: ModelMessageItem): Promise<void> {
this.ctx.addContextItem(item);
runInference({ model: 'gpt-5.5', context: this.ctx, caller: this, environment: this.env });
}
}Wire them into the same environment:
const environment = new AgenticEnvironment();
planner.join(environment);
executor.join(environment);
reviewer.join(environment);
transcriptLogger.join(environment);
sendMessage(environment, 'Build a REST API', human); // kicks off the plannerEvery participant sees every event. The planner reacts to the human, the executor reacts to the planner, the reviewer reacts to the executor, and the logger sees all of it. None of them know about each other directly — they only know which handlers they care about.
Why this works
- No scheduler to update. Adding a new specialist agent means
join()ing one more participant. The existing ones don't change. - Concurrent by default. Capability methods are non-blocking, so executors can run in parallel while the planner is already thinking about the next step.
- Composable observability. Drop in a
TranscriptLogger, a metrics exporter, or a websocket pusher as another participant. None of the agents change. - Self vs. external split. Every agent reacts to its own outputs (
onFunctionCall,onModelMessage) separately from peer outputs (onExternal*), without manualsourcechecks.
Production example: baro
The baro project — a Claude agent orchestrator — runs ten specialized participants on the same goal: a planner, multiple executors, a reviewer, a fixer, a librarian, an auditor, and more. They all live in one AgenticEnvironment and work fully concurrently, like a team collaborating in real time instead of a single agent doing everything alone.
That is the shape we expect agent swarms to take in production: many small, focused reactive agents reacting to each other through one event-driven environment.
Tips for building swarms
- Keep handlers small. One handler, one decision. A reactive agent that reacts to too many event types becomes hard to reason about.
- Compose by reaction, not orchestration. If you find yourself writing a central controller, push the logic into another reactive agent that reacts to the relevant events.
- Use separate
ModelContexts per agent. Each role usually wants its own view of the history. The environment is shared, the context is not. - Add observers for everything you'd otherwise log inline. Transcript loggers, metrics, audit trails, UI streams — all participants, none baked into the agents.
- Guardrails are participants too. A policy participant that watches
onExternalFunctionCalland aborts unsafe calls slots in like any other agent.