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
| Piece | Responsibility |
|---|---|
AgenticEnvironment | An 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. |
Participant | The 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. |
| Capabilities | What 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 viaonMessage(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 viaonInternalEvent/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.
- A capability function (
sendMessage,runInference,executeFunctionCall) is invoked with a participant ascaller. - Messages or items are produced as inference and tool execution proceed (or
sendMessagedelivers a message directly). - Each event is delivered to the environment.
- The environment dispatches the matching handler on every participant — synchronously and without awaiting them, so a slow listener never blocks producers or other listeners.
- 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 callssendMessageand an agent that callsrunInference) and the Quickstart. - Build a reactive agent — see the dedicated Reactive Agent page.
- Add observability without changing agent logic — add a passive observer: a
BaseParticipantthat only overrides handlers. - Multi-agent collaboration —
join()moreParticipants. 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
runInferenceand handleonInternalEvent/onExternalEvent; see Streaming.