mastra-ai/mastra · error

@mastra/livekit: set `agent` or `workflow`, not both — they

Error message

@mastra/livekit: set `agent` or `workflow`, not both — they are mutually exclusive reply generators.

What it means

`agent` and `workflow` are both reply generators for the LiveKit worker and cannot be combined. If both are set, the worker cannot determine which one should handle each turn, so it throws immediately during worker creation.

Source

Thrown at integrations/livekit/src/worker.ts:788

 *   mastra,
 *   stt: 'deepgram/nova-3',
 *   tts: 'cartesia/sonic-3',
 *   turnDetection: 'multilingual',
 * });
 *
 * if (process.argv[1] === fileURLToPath(import.meta.url)) {
 *   runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' });
 * }
 * ```
 */
export function createLiveKitWorker(options: CreateLiveKitWorkerOptions) {
  if (options.generate && (options.agent || options.workflow)) {
    throw new Error(
      '@mastra/livekit: set exactly one reply generator — `generate`, `agent`, or `workflow` — not a combination.',
    );
  }
  if (options.agent && options.workflow) {
    throw new Error(
      '@mastra/livekit: set `agent` or `workflow`, not both — they are mutually exclusive reply generators.',
    );
  }
  if (options.workflow && !options.workflowInput) {
    throw new Error(
      '@mastra/livekit: `workflowInput` is required when `workflow` is set. Map the turn into the ' +
        'workflow inputData, e.g. workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) }).',
    );
  }
  if (options.generate && options.configuration?.endCall) {
    throw new Error(
      '@mastra/livekit: `configuration.endCall` has no effect with `generate` — the worker cannot observe ' +
        'tool calls from a custom reply generator. Detect the end-call tool inside your generator and call ' +
        '`runEndCall` directly instead.',
    );
  }

  const wantsSileroVad = options.vad === undefined || options.vad === 'silero';

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Remove either `agent` or `workflow`, keeping the one you want to run.
  2. If you genuinely need both behaviors, route internally: e.g. run the workflow inside the `generate` callback so only one generator option is set.

Example fix

// before
createLiveKitWorker({ agent: myAgent, workflow: myWorkflow });
// after
createLiveKitWorker({ workflow: myWorkflow, workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) }) });
Defensive patterns

Strategy: validation

Validate before calling

if (options.agent && options.workflow) throw new Error('Choose either agent or workflow, not both, before creating the LiveKit worker');

Type guard

function hasExclusiveReplyHandler(o: { agent?: unknown; workflow?: unknown }): boolean {
  return !(o.agent != null && o.workflow != null);
}

Try / catch

try {
  const worker = createLiveKitWorker(options);
} catch (e) {
  if ((e as Error).message.includes('mutually exclusive reply generators')) {
    console.error('Set either agent or workflow, not both');
  } else throw e;
}

Prevention

When it happens

Trigger: createLiveKitWorker({ agent: someAgent, workflow: someWorkflow }) — both options defined, with or without `generate`.

Common situations: Migrating a voice pipeline from agents to workflows while both fields linger; a generic factory that blindly assigns all configured handlers to options; copying sample code that mentions both options.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/615e5a8f6531ce87. Report an issue: GitHub.