Skip to main content

Error Handling in Agents

Handle errors inside your agent logic and emit error events:
const errorEvent = AgentNetworkEvent.of('agent-error', S.Struct({ message: S.String }));

const agent = AgentFactory.run()
  .listensTo([requestEvent])
  .emits([responseEvent, errorEvent])
  .logic(async ({ triggerEvent, emit }) => {
    try {
      const result = await doWork(triggerEvent.payload);
      emit({ name: 'agent-response', payload: { answer: result, done: true } });
    } catch (e) {
      emit({ name: 'agent-error', payload: { message: String(e) } });
    }
  })
  .produce({});
Ensure the client channel streams both response and error events so the UI can show errors.

Event Filtering

Stream only specific events to the client:
const api = network.expose(
  registerSSEStream({
    channel: 'client',
    events: ['agent-response', 'agent-error'],
  }),
);

Observability Hooks

onRequest

Use onRequest to log, trace, or enrich before the start event is published:
const api = network.expose(
  registerSSEStream({
    channel: 'client',
    onRequest: async ({ emitStartEvent, req, payload }) => {
      const traceId = crypto.randomUUID();
      console.log('[trace]', traceId, payload);
      emitStartEvent({
        contextId: req.contextId ?? crypto.randomUUID(),
        runId: req.runId ?? crypto.randomUUID(),
        event: messageEvent.make({ ...payload, traceId }),
      });
    },
  }),
);

Catch-All Logger Agent

Register a catch-all agent to log every event:
const loggerAgent = AgentFactory.run()
  .logic(async ({ triggerEvent }) => {
    console.log('[event]', triggerEvent.name, triggerEvent.meta.runId, triggerEvent.payload);
  })
  .produce({});

registerAgent(loggerAgent).subscribe(main).subscribe(processing);

Event Meta

Every event has meta.runId, meta.contextId, meta.correlationId, meta.causationId, and meta.ts. Use these for distributed tracing and correlation.

NetworkTracer

Core exposes a pluggable NetworkTracer interface. Configure tracing when defining the network; network.expose() inherits those defaults. Agents receive a tracing scope in .logic():
import { AgentNetwork } from '@m4trix/core/matrix';
import { Tracer, TraceStore, toM4trixTracer } from '@m4trix/tracing';

const tracer = Tracer.from(traceStore);

const network = AgentNetwork.setup(
  ({ registerAgent }) => {
    // wire agents and channels
  },
  {
    consoleTracing: true, // opt-in stdout spans + network trace logs
    networkTracer: toM4trixTracer(tracer), // TraceStore / trace-viewer
  },
);

const api = network.expose(
  registerSSEStream({ channel: 'client' }),
);

const agent = AgentFactory.run()
  .listensTo([messageEvent])
  .logic(async ({ tracing }) => {
    const llm = tracing.startRun('llm', 'gpt-4o', { prompt: 'hello' });
    await llm.end({ text: 'hi' });
  })
  .produce({});