Voice Greeting¶
Greet callers automatically when a session starts. RoomKit provides several patterns ranging from zero-code static greetings to fully dynamic LLM-generated responses. The ON_SESSION_STARTED hook fires for both voice and text channels, so the same greeting patterns work across channel types.
Greeting gate¶
When auto_greet is enabled, RoomKit uses a greeting gate to guarantee the greeting is stored in conversation history before the AI processes the first user message. This prevents the race condition where a fast-talking user's message reaches the AI before the greeting lands in context.
- Voice channels: the gate holds AI processing until TTS delivery completes.
- Text channels (SMS, Email, etc.): the
ON_SESSION_STARTEDhook runs synchronously before the first inbound message is processed, so the greeting is always stored first. - Multi-agent rooms: the gate is reference-counted — it releases only when all agents finish their greetings.
When to use each pattern¶
| Pattern | Latency | Flexibility | Best for |
|---|---|---|---|
| 1. Agent auto_greet | Lowest | Static text | Most use cases — zero code |
| 2. Explicit hook + send_greeting() | Low | Static text, custom timing | Conditional greetings, A/B testing |
| 3. Manual say() | Low | Static text | Non-agent greetings, pre-rendered audio |
| 4. LLM-generated | Higher (LLM round-trip) | Dynamic, context-aware | Personalized greetings based on caller data |
Pattern 1: Agent auto_greet (recommended)¶
Set greeting on the Agent and it speaks automatically when the session starts. No hooks or extra code needed.
from roomkit import Agent, RoomKit, VoiceChannel
kit = RoomKit(stt=stt, tts=tts, voice=backend)
voice = VoiceChannel("voice", stt=stt, tts=tts, backend=backend)
agent = Agent(
"agent",
provider=ai_provider,
greeting="Welcome to Acme Support! How can I help you today?",
# auto_greet=True is the default
)
kit.register_channel(voice)
kit.register_channel(agent)
The greeting text is sent via say() (voice) or broadcast (text) — no LLM round-trip, so the caller hears it with near-zero latency. The voice path runs through BEFORE_TTS / AFTER_TTS hooks, so you can intercept or modify the greeting.
Pattern 2: Explicit hook + send_greeting()¶
Register an ON_SESSION_STARTED hook and call send_greeting() yourself. This gives you control over timing and lets you add conditional logic.
from roomkit import Agent, HookExecution, HookTrigger, RoomKit, VoiceChannel
from roomkit.models.session_event import SessionStartedEvent
agent = Agent(
"agent",
provider=ai_provider,
greeting="Welcome to Acme Support!",
auto_greet=False, # We'll trigger it ourselves
)
kit.register_channel(voice)
kit.register_channel(agent)
room = await kit.create_room()
await kit.attach_channel(room.id, "voice")
await kit.attach_channel(room.id, "agent")
@kit.hook(HookTrigger.ON_SESSION_STARTED, HookExecution.ASYNC)
async def on_ready(event: SessionStartedEvent, context: object) -> None:
# Add any conditional logic here
await kit.send_greeting(
room.id,
session=event.session,
channel_type=event.channel_type,
)
Passing session and channel_type lets send_greeting() dispatch correctly — voice sessions get TTS via say(), text sessions get a broadcast. Without these parameters, the greeting falls back to the text broadcast path.
Tip
Use this pattern when you need to check caller metadata, time of day, or other conditions before deciding which greeting to send.
Pattern 3: Manual say()¶
Use the hook to call voice.say() directly. This bypasses the Agent entirely — useful for non-agent greetings or pre-rendered audio.
from roomkit import HookExecution, HookTrigger, VoiceChannel
from roomkit.models.session_event import SessionStartedEvent
voice = VoiceChannel("voice", stt=stt, tts=tts, backend=backend)
kit.register_channel(voice)
@kit.hook(HookTrigger.ON_SESSION_STARTED, HookExecution.ASYNC)
async def on_ready(event: SessionStartedEvent, context: object) -> None:
if event.session is not None:
await voice.say(event.session, "Please hold while we connect you.")
Pattern 4: LLM-generated greeting¶
Inject a synthetic user message via process_inbound() to trigger an LLM round-trip. The AI generates a contextual greeting instead of a static string.
from roomkit import Agent, HookExecution, HookTrigger, RoomKit, VoiceChannel
from roomkit.models.delivery import InboundMessage
from roomkit.models.event import TextContent
from roomkit.models.session_event import SessionStartedEvent
agent = Agent(
"agent",
provider=ai_provider,
system_prompt="You are a helpful assistant. When you receive '[session started]', "
"greet the caller warmly and ask how you can help.",
auto_greet=False, # No static greeting — let the LLM generate one
)
kit.register_channel(voice)
kit.register_channel(agent)
room = await kit.create_room()
await kit.attach_channel(room.id, "voice")
await kit.attach_channel(room.id, "agent")
@kit.hook(HookTrigger.ON_SESSION_STARTED, HookExecution.ASYNC)
async def on_ready(event: SessionStartedEvent, context: object) -> None:
inbound = InboundMessage(
channel_id="voice",
sender_id=event.participant_id,
content=TextContent(body="[session started]"),
metadata={
"voice_session_id": event.session.id if event.session else None,
"source": "greeting",
},
)
await kit.process_inbound(inbound, room_id=room.id)
The synthetic [session started] message flows through the full inbound pipeline: the AI channel sees it, generates a response, and the response is broadcast back through the voice channel as speech.
Note
This pattern adds LLM latency before the caller hears the greeting. Use it when you need personalized greetings based on caller context, time of day, or conversation history. For static greetings, prefer patterns 1–3.
Full example¶
See examples/voice_greeting.py for a runnable example demonstrating all four patterns with mock providers.