mastra-ai/mastra · error
@mastra/livekit: MastraVoiceAgent requires `agent` or…
Error message
@mastra/livekit: MastraVoiceAgent requires `agent` or `generate`, not both — they are mutually exclusive reply sources.
What it means
MastraVoiceAgent accepts two mutually exclusive reply sources: `agent` (a Mastra agent instance) or `generate` (a custom reply generator function). The constructor throws this error when both are provided, because the bridge cannot decide which one owns reply generation. Exactly one must be supplied.
Solutions
- Remove one of the two: keep `agent` for Mastra-agent-driven replies, or keep `generate` for custom replies.
- If migrating from agent to generate, delete the `agent` property from the options object.
- Audit config-building code (object spreads, defaults) so only one reply source is ever set.
Example fix
// before
new MastraVoiceAgent({ agent: myAgent, generate: myFn })
// after
new MastraVoiceAgent({ agent: myAgent }) // or { generate: myFn }, never both Defensive patterns
Strategy: validation
Validate before calling
function assertValidVoiceAgentOptions(o: MastraVoiceAgentOptions): void {
if ('agent' in o && o.agent && 'generate' in o && o.generate) {
throw new Error('MastraVoiceAgent accepts `agent` or `generate`, not both');
}
} Type guard
function hasExactlyOneReplySource(o: MastraVoiceAgentOptions): boolean {
return Boolean(o.agent) !== Boolean(o.generate); // XOR: exactly one present
} Prevention
- Build options objects explicitly instead of spreading partial configs that may carry both keys.
- Centralize MastraVoiceAgent construction in one factory that enforces the XOR constraint.
- When migrating from agent to generate, delete the old key, don't just add the new one.
When it happens
Trigger: new MastraVoiceAgent({ agent, generate, ... }) where both keys are present — typically when merging config objects or adding a custom generate override on top of an existing agent-based config.
Common situations: Spreading defaults ({ ...baseOptions }) that already contain `agent` while adding `generate`; refactoring from agent-based to generate-based replies without removing the old key; conditional config building that accumulates both fields.
Related errors
- @mastra/livekit: MastraVoiceAgent requires `agent` or…
- connect( ): "name" is required when connecting without an…
- Invalid input: expected StepParams, Agent, ToolStep, or…
- @mastra/livekit: `configuration.endCall` has no effect with…
- @mastra/livekit: MastraLLM requires exactly one reply…
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/fc58c979a9821c6a.
Report an issue: GitHub.
Appendix: source
Thrown at integrations/livekit/src/bridge.ts:439
/**
* A LiveKit `voice.Agent` whose replies come from a Mastra agent or workflow.
*
* LiveKit keeps ownership of the audio loop (VAD, STT, turn detection, TTS, barge-in) and calls
* `llmNode` once per detected user turn; the node delegates to a {@link VoiceReplyGenerator}
* which streams text deltas back. On barge-in LiveKit cancels the returned stream, which aborts
* the in-flight generation.
*/
export class MastraVoiceAgent extends voice.Agent {
readonly mastraAgent?: MastraAgent;
readonly memory: MastraVoiceAgentMemory | false;
readonly requestContext?: RequestContext;
readonly streamOptions?: MastraStreamOptions;
private readonly replyGenerator: VoiceReplyGenerator;
private readonly reminder?: DisclosureReminder;
constructor(options: MastraVoiceAgentOptions) {
if (options.agent && options.generate) {
throw new Error(
'@mastra/livekit: MastraVoiceAgent requires `agent` or `generate`, not both — they are mutually exclusive reply sources.',
);
}
super({
id: options.id,
instructions: options.instructions ?? DEFAULT_INSTRUCTIONS,
stt: options.stt,
vad: options.vad,
llm: new MastraPlaceholderLLM(),
tts: options.tts,
turnHandling: options.turnHandling,
});
this.memory = options.memory ?? false;
this.requestContext = toRequestContext(options.requestContext);
this.streamOptions = options.streamOptions;
if (options.greetingReminder) {
this.reminder = new DisclosureReminder(
options.greetingReminder.everyMs,View on GitHub (pinned to 75dd419e61)