gentle-church-5117
04/16/2026, 8:48 PM*IMPORTANT*: When calling transferTo{agent}, *DO NOT* say that you are transferring me, as the client should not be aware of the agent flow.)tall-airline-16756
04/16/2026, 8:48 PMtall-airline-16756
04/16/2026, 8:49 PM@function_tool()
async def route_to_billing(self, context: RunContext):
# silently hand off to BillingAgent with preserved chat context
return BillingAgent(chat_ctx=self.chat_ctx.copy(exclude_instructions=True))
2) Node.js (handoff without a returns message)
routeToBilling: llm.tool({
description: 'Route to billing specialist',
execute: async (_, { ctx }) => {
return llm.handoff({ agent: new BillingAgent() }) // no 'returns' = no spoken transfer
},
})
Key points to follow
• Omit any transfer/returns message so no speech is produced.
• Pass the conversation context to the receiving agent (use chat_ctx.copy(exclude_instructions=True) or equivalent) so the subagent has history but can use its own instructions.
• Keep tool descriptions focused on routing conditions (when to route), not how to narrate it.
• Don’t rely on hard prompt instructions to suppress transfer announcements — they’re fragile.
If you want, tell me which SDK/language and whether you need context filtering (what to include/exclude when handing off) and I’ll give a tailored code snippet.
Sources: Agents and handoffs | LiveKit Documentation | The Handoff Pattern for Voice Agents That Replaces IVR Menus | LiveKit, with interactive elementsgentle-church-5117
04/16/2026, 8:55 PMtall-airline-16756
04/16/2026, 8:55 PM// triageAgent.js
const { llm, Agent } = require('livekit-llm'); // adjust import to your setup
class TriageAgent extends Agent {
constructor() {
super({
instructions: `You are a triage agent. Route to specialists when needed. Do not speak any transfer messages; perform silent handoffs via tool returns.`
});
}
routeToBilling = llm.tool({
description: 'Route to billing specialist',
execute: async (_, { ctx }) => {
// Perform a handoff without a `returns` message => silent transfer
return llm.handoff({
agent: new BillingAgent(),
// no `returns` or spoken message provided
});
},
});
routeToTech = llm.tool({
description: 'Route to technical specialist',
execute: async (_, { ctx }) => {
return llm.handoff({
agent: new TechnicalAgent(),
});
},
});
}
module.exports = TriageAgent;
Notes & best practices
• Omit any returns / transfer message in llm.handoff so the framework does not generate speech.
• Ensure the receiving agent gets conversation context. If your SDK requires passing context explicitly, pass a copy of the chat context (preserve history but let the new agent use its own instructions).
• Keep tool descriptions focused on routing conditions (when to call the handoff).
• Avoid relying on prompt-only directives to suppress announcements — use the handoff mechanism above for reliable silence.
If you want, share how you instantiate agents (and whether you need explicit context copying) and I’ll adapt the snippet to your exact setup.
, with interactive elementsgentle-church-5117
04/16/2026, 9:08 PMtall-airline-16756
04/16/2026, 9:08 PMrouteToBilling: llm.tool({
description: 'Route to billing specialist — DO NOT announce or say you are transferring the customer.',
execute: async (_, { ctx }) => {
// silent handoff: no `returns` or spoken message
return llm.handoff({ agent: new BillingAgent(), /* no returns */ });
},
})
Why do both?
• Omitted returns prevents any spoken transfer.
• Clear tool description guides developer intent and helps the model/tooling choose the correct silent behavior.
If you want, share your agent instantiation code and I’ll adapt this to include explicit context copying or filtering.
, with interactive elements