Mozaik

Architecture

Environment, participants, and capabilities — the three pieces of Mozaik's event-driven agentic environment.

Mozaik has three moving parts. They are deliberately decoupled so you can change one without touching the others.

flowchart LR
  Human[Participant] -->|"sendMessage(env, text, caller)"| Env(("AgenticEnvironment"))
  Agent[Participant] -->|"runInference / executeFunctionCall"| Env
  Observer[Participant] -->|join| Env
  Env -->|"onMessage / onExternal*"| Human
  Env -->|"onFunctionCall / onReasoning / ..."| Agent
  Env -->|"onExternal*"| Observer

The three pieces

PieceResponsibility
AgenticEnvironmentAn event bus. Receives messages and context items from participants and fans each one out to the appropriate typed handler on every other participant. No central scheduler.
ParticipantThe single abstraction for humans, agents, observers, and tools. Extend BaseParticipant, join an environment, and react to events through typed handlers (onMessage, onFunctionCall, onExternalFunctionCall, onReasoning, onModelMessage, lifecycle hooks, …). Every handler defaults to a no-op — override only the ones you care about.
CapabilitiesWhat a participant can produce, exposed as free functions a participant calls with itself as caller: sendMessage, runInference, executeFunctionCall. They are non-blocking — each returns immediately and emits events in the background.

The role of a participant (human, agent, observer) is just which capabilities it calls and which handlers it overrides — there are no separate BaseHuman / BaseAgent classes.

How events flow

At runtime, the environment fans out three kinds of events:

  • Messages — plain strings, delivered via onMessage(message).
  • ContextItems — typed model internals (FunctionCallItem, FunctionCallOutputItem, ReasoningItem, ModelMessageItem), delivered via the matching typed handler.
  • SemanticEvent<T> — provider stream chunks (type + data) yielded during streaming inference; delivered via onInternalEvent / onExternalEvent. See Streaming.

For every typed context event there are two variants:

  • Self handler (e.g. onFunctionCall) — fires when this participant emits the event.
  • External handler (e.g. onExternalFunctionCall) — fires when another participant emits the event.

The split means a participant can encode "act on my own outputs" separately from "observe others", without inspecting source by hand.

  1. A capability function (sendMessage, runInference, executeFunctionCall) is invoked with a participant as caller.
  2. Messages or items are produced as inference and tool execution proceed (or sendMessage delivers a message directly).
  3. Each event is delivered to the environment.
  4. The environment dispatches the matching handler on every participant — synchronously and without awaiting them, so a slow listener never blocks producers or other listeners.
  5. Listening participants choose to observe, react (by triggering their own capabilities), or ignore.

Because each capability call emits events as they are produced without blocking the caller, multiple participants can produce events into the same environment concurrently.

How to choose a starting point

  • Single agent reacting to user input — start with two BaseParticipants (a human that calls sendMessage and an agent that calls runInference) and the Quickstart.
  • Build a reactive agent — see the dedicated Reactive Agent page.
  • Add observability without changing agent logic — add a passive observer: a BaseParticipant that only overrides handlers.
  • Multi-agent collaborationjoin() more Participants. They all see the same events in real time and react independently. See Agent Swarms for patterns.
  • Scope what an agent reacts to — use selective listening to react only to specific participant types.
  • Live streaming UI — enable streaming on runInference and handle onInternalEvent / onExternalEvent; see Streaming.

On this page