Mozaik

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);
CallEffect
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.

HandlerTriggered when…
onJoinedthis participant joins an environment
onLeftthis participant leaves an environment
onParticipantJoinedanother participant joins the same environment
onParticipantLeftanother participant leaves the same environment
onMessageany participant sends a message
onFunctionCallits own inference returns a function call
onExternalFunctionCallanother agent's inference returns a function call
onFunctionCallOutputits own function call runner returns a result
onExternalFunctionCallOutputanother agent's function call runner returns a result
onReasoningits own inference returns a reasoning item
onExternalReasoninganother agent's inference returns a reasoning item
onModelMessageits own inference returns an assistant message
onExternalModelMessageanother agent's inference returns an assistant message
onInternalEventits own inference emits a SemanticEvent<T>
onExternalEventanother participant emits a SemanticEvent<T>
onErrorone of its own handlers throws
onParticipantErroranother 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 via onMessage(message: string) on every participant except the sender.
  • ContextItems — typed model internals (completed inference and tool output). Delivered via their dedicated handlers:
    • FunctionCallItemonFunctionCall / onExternalFunctionCall
    • FunctionCallOutputItemonFunctionCallOutput / onExternalFunctionCallOutput
    • ReasoningItemonReasoning / onExternalReasoning
    • ModelMessageItemonModelMessage / onExternalModelMessage
  • SemanticEvent<T> — incremental provider stream chunks (type + data) while inference is streaming. The runner yields them; the environment delivers via onInternalEvent / 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 / after hooks. 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.

On this page