Mozaik

Reactive Agent

A reactive agent is an event-driven, collaborative agent that adapts to its environment and to other agents. Learn what a reactive agent is and how to build one with Mozaik.

A reactive agent is an event-driven, collaborative agent that adapts to its environment and to other agents in real time. Instead of following a hard-coded pipeline, a reactive agent declares which events it cares about and reacts to them as they arrive — messages from humans, function calls from its own model, reasoning traces from a peer agent, or tool outputs from somewhere else in the environment.

Mozaik is built around this model: AgenticEnvironment is an event bus, and every Participant exposes a set of typed handlers. Reactive agents are the natural unit of work.

What is a reactive agent?

A reactive agent has three properties:

  • Event-driven — It runs in response to events on the bus (onMessage, onFunctionCall, onReasoning, …), not on a schedule and not behind a central orchestrator.
  • Collaborative — It shares an AgenticEnvironment with other reactive agents, observers, humans, and tools. It can react to its own outputs (self handlers) and to others' outputs (onExternal* handlers).
  • Adaptive — Behavior emerges from how it reacts. Add a critic agent and the reactive agent's outputs are reviewed in real time. Add a logger and you get an audit trail. The reactive agent itself does not change.

Under the hood every capability is non-blocking, so a reactive agent never holds up its peers — even on a slow inference call or a long-running tool.

Reactive agents vs. orchestrated pipelines

Orchestrated pipelineReactive agent
A central controller decides whose turn it is.The environment fans events out; participants react on their own.
Adding a step means editing the controller.Adding behavior means join()ing another participant.
One slow step blocks the rest.Non-blocking events let participants act concurrently.
Hard to add cross-cutting concerns (audit, critique, telemetry).Drop in another observer or reactive agent — existing code untouched.

Build a reactive agent with Mozaik

A reactive agent extends BaseParticipant and overrides only the handlers it actually needs. Every handler defaults to a no-op, so a reactive agent stays small and explicit about what it reacts to. Capabilities are the free functions runInference and executeFunctionCall — the participant passes itself as caller.

import {
  BaseParticipant,
  UserMessageItem,
  FunctionCallItem,
  FunctionCallOutputItem,
  ReasoningItem,
  ModelMessageItem,
  AgenticEnvironment,
  ModelContext,
  Tool,
  runInference,
  executeFunctionCall,
} from '@mozaik-ai/core';

export class ReactiveAgent extends BaseParticipant {
  constructor(
    private readonly environment: AgenticEnvironment,
    private readonly context: ModelContext,
    private readonly tools: Tool[] = [],
  ) {
    super();
  }

  async onMessage(message: string): Promise<void> {
    this.context.addContextItem(UserMessageItem.create(message));
    runInference({
      model: 'gpt-5.5',
      context: this.context,
      tools: this.tools,
      caller: this,
      environment: this.environment,
    });
  }

  async onFunctionCall(item: FunctionCallItem): Promise<void> {
    this.context.addContextItem(item);
    const tool = this.tools.find((t) => t.name === item.name);
    if (tool) executeFunctionCall(this.environment, item, tool, this);
  }

  async onFunctionCallOutput(item: FunctionCallOutputItem): Promise<void> {
    this.context.addContextItem(item);
    runInference({
      model: 'gpt-5.5',
      context: this.context,
      tools: this.tools,
      caller: this,
      environment: this.environment,
    });
  }

  async onReasoning(item: ReasoningItem): Promise<void> {
    this.context.addContextItem(item);
  }

  async onModelMessage(item: ModelMessageItem): Promise<void> {
    this.context.addContextItem(item);
  }
}

What each handler does

  • onMessage(message) — Someone sent a plain-text message (a human via sendMessage, another agent, etc.). Record it as a UserMessageItem and trigger inference.
  • onFunctionCall(item) — The agent's own inference returned a tool call. Record it, find the matching Tool, and execute it with executeFunctionCall.
  • onFunctionCallOutput(item) — The tool produced a result. Record it and run inference again so the model can use it.
  • onReasoning(item) / onModelMessage(item) — Keep the local ModelContext in sync with the model's outputs.

Joining the environment

const environment = new AgenticEnvironment();
const context = ModelContext.create('demo');

const agent = new ReactiveAgent(environment, context, tools);

agent.join(environment);

From this point on, the agent reacts to anything that flows through the environment — including events produced by other reactive agents. There is no separate bus to start.

Self handlers vs. external handlers

For every typed event there are two handlers:

SelfExternal
onFunctionCallonExternalFunctionCall
onFunctionCallOutputonExternalFunctionCallOutput
onReasoningonExternalReasoning
onModelMessageonExternalModelMessage
onInternalEvent (SemanticEvent<T>)onExternalEvent (SemanticEvent<T>)

The reactive agent above reacts to its own outputs (loop the model with tool results) and is silent on what other agents emit. To collaborate with peers, override the onExternal* handlers as well — for example, override onExternalModelMessage to critique another agent's reply.

onMessage has no onExternal variant: messages are conversational and reach every participant the same way (except the sender).

Composing reactive agents

Behaviors compose by reaction, not by orchestration:

  • Add a second reactive agent that overrides onExternalModelMessage to critique the first one → you get a critic loop.
  • Add a TranscriptLogger observer → you get a live UI stream or audit log.
  • Add a guardrail participant that watches onExternalFunctionCall and intercepts unsafe calls → you get policy enforcement.

None of these changes touch the reactive agent itself. That is the point.

For multi-agent patterns at scale — planner + executors + reviewer + observers all sharing one environment — see Agent Swarms.

On this page