cuddly-baker-42941
12/18/2025, 10:13 PMtall-airline-16756
12/18/2025, 10:13 PMtall-airline-16756
12/18/2025, 10:14 PMimport { voice, llm, z } from "@livekit/agents";
// Base agent with shared behavior
class StepAgent extends voice.Agent {
constructor(instructions: string) {
super({ instructions });
}
}
// Step 1: collect name
class GetNameAgent extends StepAgent {
constructor() {
super("Collect the user's name.");
}
async onEnter() {
this.session.generateReply({ instructions: "Please tell me your name." });
}
@llm.tool({
description: "Record user's name",
parameters: z.object({ name: z.string() }),
})
async recordName({ name }: { name: string }) {
this.session.userData = { ...(this.session.userData || {}), name };
return llm.handoff({
agent: new GetEmailAgent(),
returns: "Name recorded, handing off to email step.",
});
}
}
// Step 2: collect email
class GetEmailAgent extends StepAgent {
constructor() {
super("Collect the user's email.");
}
async onEnter() {
const name = (this.session.userData as any)?.name ?? "there";
this.session.generateReply({
instructions: `Thanks ${name}. What is your email address?`,
});
}
@llm.tool({
description: "Record user's email",
parameters: z.object({ email: z.string().email() }),
})
async recordEmail({ email }: { email: string }) {
this.session.userData = { ...(this.session.userData || {}), email };
this.session.generateReply({ instructions: "Thanks — we have your info." });
// Optionally finish or shutdown the session
// this.session.shutdown();
}
}
// Entry: start the workflow with the first agent
export default defineAgent({
entry: async (ctx: any) => {
const session = new voice.AgentSession({
vad: ctx.proc.userData.vad,
stt: /* your STT instance */,
llm: /* your LLM instance */,
tts: /* your TTS instance */,
});
await session.start({
room: ctx.room,
agent: new GetNameAgent(),
userData: {}, // initialize shared state
});
},
});
Key points:
• Use session.userData to persist state across agent handoffs.
• Use llm.handoff() to move control to the next step/agent.
• Each step is one agent with its own onEnter prompt and tools to capture data.
If you want, I can:
• Show a conditional handoff example (skip steps based on input),
• Convert this to a simpler callback-only flow (no decorators/tools),
• Or adapt it to your actual STT/LLM/TTS providers — which one are you using? Sources: Agents Framework | livekit/livekit_composite | DeepWiki | Agents and handoffs | LiveKit docs