Agentic environment
An event-driven bus for messages and typed context items. No central scheduler — reactive agents react to whatever flows through.
AgenticEnvironment is the heart of Mozaik. It is an event bus, not a pipeline. Participants join() it, capability methods stream events into it, and every joined participant sees every event through its typed handlers.
There is no central scheduler. The environment does not decide who runs next, who is allowed to speak, or in what order. Behavior emerges from how participants react to delivered events.
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
Env -->|"onJoined / onLeft / onParticipant*"| All
Lifecycle
import { AgenticEnvironment } from '@mozaik-ai/core';
const environment = new AgenticEnvironment();
human.join(environment);
agent.join(environment);
// ...participants emit events as soon as they join...
agent.leave(environment);| Call | Effect |
|---|---|
new AgenticEnvironment(name?, { silent? }) | Creates an empty environment with no participants. silent controls internal error logging. |
participant.join(environment) | Registers the participant. From now on it will receive every event through its typed handlers; it gets onJoined, others get onParticipantJoined. |
participant.leave(environment) | Unregisters the participant. It gets onLeft, others get onParticipantLeft. |
There is no separate start/stop step — events flow as soon as participants join.
Typed handlers
Each participant exposes one handler per event type. Every handler defaults to a no-op — override only the ones you care about.
| Handler | Triggered when… |
|---|---|
onJoined | this participant joins an environment |
onLeft | this participant leaves an environment |
onParticipantJoined | another participant joins the same environment |
onParticipantLeft | another participant leaves the same environment |
onMessage | any participant sends a message |
onFunctionCall | its own inference returns a function call |
onExternalFunctionCall | another agent's inference returns a function call |
onFunctionCallOutput | its own function call runner returns a result |
onExternalFunctionCallOutput | another agent's function call runner returns a result |
onReasoning | its own inference returns a reasoning item |
onExternalReasoning | another agent's inference returns a reasoning item |
onModelMessage | its own inference returns an assistant message |
onExternalModelMessage | another agent's inference returns an assistant message |
onInternalEvent | its own inference emits a SemanticEvent<T> |
onExternalEvent | another participant emits a SemanticEvent<T> |
onError | one of its own handlers throws |
onParticipantError | another participant's handler throws |
The split between self handlers (onFunctionCall, onReasoning, onModelMessage, onFunctionCallOutput, onInternalEvent) and onExternal* handlers means a participant can encode "act on my own outputs" separately from "observe others", without inspecting source by hand. See Error handling for onError / onParticipantError.
By default a participant reacts to events from every other participant. To scope it to specific participant types, see Selective listening.
What flows on the bus
Three kinds of events can be exchanged:
- Messages — plain
strings, used for conversational input/output. Delivered viaonMessage(message: string)on every participant except the sender. ContextItems — typed model internals (completed inference and tool output). Delivered via their dedicated handlers:FunctionCallItem→onFunctionCall/onExternalFunctionCallFunctionCallOutputItem→onFunctionCallOutput/onExternalFunctionCallOutputReasoningItem→onReasoning/onExternalReasoningModelMessageItem→onModelMessage/onExternalModelMessage
SemanticEvent<T>— incremental provider stream chunks (type+data) while inference is streaming. The runner yields them; the environment delivers viaonInternalEvent/onExternalEvent. See Streaming and Core concepts.
Fan-out semantics
When a participant produces a message or context item, the environment dispatches the matching handler on every joined participant synchronously and without awaiting them. Two practical consequences:
- A slow listener cannot block producers. Even if a logger or a downstream agent is busy, the environment keeps fanning out events.
- Multiple capabilities can produce events into the same environment concurrently. Each capability call returns immediately and emits events as they arrive.
// Both capabilities produce events in parallel — neither blocks the other.
sendMessage(environment, 'Hello', human);
runInference({ model: 'gpt-5.5', context, caller: agent, environment });What the environment is not
- It is not an FSM. There are no states, no transitions, no
before/afterhooks. The only intercept points are the typed handlers. - It is not an orchestrator. It does not decide whose turn it is. If you want a turn-taking convention, encode it in the participants' reactions.
- It is not a queue. Events are delivered as they are produced; backpressure, if any, must be implemented inside a participant.