Event Reference

VoiceRun uses an event-driven architecture. Your agent receives input events and yields output events to control the conversation.

For replace=True, after_speech=True, and terminal stop/transfer ordering, see Speech Playback Control. These options require SDK 0.1.106+ and matching engine/runtime support.

Event Lifecycle#

  1. Event handler is invoked once per input event
  2. Event handler can yield zero or more output events
  3. Each output event is processed in the order emitted

Input Events#

Input events are triggered by user actions or system events and passed to your agent's handler function.

StartEvent#

Emitted when a new voice agent session has started.

class StartEvent(Event): pass

Usage:

if isinstance(event, StartEvent): yield TextToSpeechEvent( text="Hello! How can I help you today?", voice="rachel" )

TextEvent#

Emitted when the user speaks or types text.

class TextEvent(Event): data: { "source": str # Source of input: "speech" or "text" "text": str # The transcribed or typed text "language": str # Language code (optional, provided by STT) }

Usage:

if isinstance(event, TextEvent): user_message = event.data.get("text", "N/A") source = event.data.get("source") # "speech" or "text"

TimeoutEvent#

Emitted when the user does not speak for the configured silence timeout (5 seconds by default). The counter increments with each consecutive timeout. Caller speech and ordinary incoming ExternalEvents reset the counter; background events do not follow that reset path.

The engine currently ends the session after 20 consecutive silence timeouts. For a business deadline, store an elapsed-time deadline in Context and check it on inputs/timeouts rather than relying only on count, which incoming notifications can reset.

class TimeoutEvent(Event): data: { "count": int # Number of consecutive timeouts "ms_since_input": int # Milliseconds since last input }

Usage:

if isinstance(event, TimeoutEvent): count = event.data.get("count", 0) ms_since_input = event.data.get("ms_since_input", 0) if count >= 3: yield TextToSpeechEvent(text="Are you still there?", voice="rachel") elif count == 1: yield TextToSpeechEvent(text="Take your time.", voice="rachel")

DTMFEvent#

Emitted when DTMF tones (phone keypad input) are received.

class DTMFEvent(Event): data: { "digits": str # The DTMF digits pressed (e.g., "123#") }

Usage:

if isinstance(event, DTMFEvent): digits = event.data.get("digits") if digits == "1": yield TextToSpeechEvent(text="You pressed one.", voice="rachel")

Output Events#

Output events are yielded by your agent to perform actions like speaking, playing audio, or transferring sessions.

TextToSpeechEvent#

Converts text to speech and plays it to the user.

class TextToSpeechEvent(Event): def __init__( self, text: str, # Text to speak voice: str | TextToSpeechIdentifier = "nova", # Voice name or identifier cache: bool = True, # Cache generated audio interruptible: bool | None = None, # Can user interrupt instructions: str = "", # Voice styling (OpenAI, Qwen3) speed: float = 1.0, # Playback speed language: str = "en", # Language code stream: bool | None = None, # Enable streaming TTS model: str | None = None, # Override default provider model replace: bool = False, # Clear preceding speech before this speech )

instructions is forwarded only to OpenAI and Qwen3; other providers ignore it. model is ignored by azure, google_chirp, gradium, and xai, which take no model parameter — see Text to Speech for the model ids each provider accepts.

Usage:

# Basic usage yield TextToSpeechEvent( text="Hello, how can I help you?", voice="rachel" ) # With voice styling (OpenAI and Qwen3 only) yield TextToSpeechEvent( text="Welcome to our service!", voice="alloy", instructions="enthusiastic and professional", speed=1.25 ) # Non-interruptible announcement yield TextToSpeechEvent( text="Please do not interrupt this important message.", voice="rachel", interruptible=False ) # Using TextToSpeechIdentifier for specific provider voice yield TextToSpeechEvent( text="G'day mate!", voice={"provider": "azure", "identifier": "en-AU-WilliamNeural"} ) # Override the default model for the provider yield TextToSpeechEvent( text="High-quality narration.", voice={"provider": "openai", "identifier": "nova"}, model="tts-1-hd" )

AudioEvent#

Plays audio from a URL.

class AudioEvent(Event): def __init__( self, path: str, # URL of the audio file interruptible: bool = True, # Can user interrupt loop: bool = False, # Loop the audio )

Usage:

# Play audio file yield AudioEvent(path="https://example.com/audio.mp3") # Play non-interruptible audio yield AudioEvent( path="https://example.com/important.mp3", interruptible=False ) # Loop background music yield AudioEvent( path="https://example.com/hold-music.mp3", loop=True )

SilenceEvent#

Plays silence for a specified duration.

class SilenceEvent(Event): def __init__( self, duration: int # Duration in milliseconds )

Usage:

# Pause for 2 seconds yield SilenceEvent(duration=2000)

InterruptEvent#

InterruptEvent() cancels active synthesis and queued/provider-buffered speech, including interruptible=False speech. It does not cancel the issuing handler or mute background music. There is no force parameter.

yield InterruptEvent() yield TextToSpeechEvent(text="Let me correct that.")

StopEvent#

Commits termination, drains preceding speech, plays any closing message, and ends the session. clear_speech=True clears preceding speech first. See Speech Playback Control for cancellation and failure rules.

Yielding StopEvent requests call termination; receiving an input StopEvent invokes your handler for session-end cleanup. See Session Shutdown and Cleanup for the planned 30-second cleanup contract, context.create_task() behavior, permitted outputs, and rollout status.

class StopEvent(Event): def __init__( self, closing_speech: str | None = None, # Optional goodbye message voice: str | TextToSpeechIdentifier | None = None, # Voice for closing speech speed: float = 1.0, # Playback speed language: str = "en", # Language code clear_speech: bool = False, # Clear preceding speech first )

Usage:

# Drain preceding speech, then end the session yield StopEvent() # End session with closing message yield StopEvent( closing_speech="Thank you for calling. Goodbye!", voice="rachel" )

LogEvent#

Logs a message to the system logs.

class LogEvent(Event): def __init__( self, message: str, # Message to log location: str | None = None # Optional source location )

Usage:

from primfunctions.logger import logger logger.info("User requested account balance")

Tip: Use primfunctions.logger instead of yielding LogEvent directly. The logger can be called from anywhere in your code — not just inside the handler.


TransferSessionEvent#

When a transfer ends the handler session, the handler receives a final input StopEvent. See Session Shutdown and Cleanup for cleanup and persistence rules.

Transfers after preceding speech and any closing message finish. Use clear_speech=True to clear preceding speech before the closing message.

class TransferSessionEvent(Event): def __init__( self, *, phone_number: str | None = None, # Phone number to transfer to data: dict | None = None, # Context data to pass closing_speech: str | None = None, # Message before transfer voice: str | TextToSpeechIdentifier | None = None, # Voice for closing speech speed: float = 1.0, # Playback speed language: str = "en", # Language code clear_speech: bool = False, # Clear preceding speech first )

Usage:

# Phone transfer (cold transfer) yield TransferSessionEvent(phone_number="+15555555555") # Phone transfer with context yield TransferSessionEvent( phone_number="+15555551234", closing_speech="I'm transferring you to a specialist.", voice="rachel", data={"reason": "technical_support", "priority": "high"} )

STTUpdateSettingsEvent#

Dynamically updates Speech-to-Text settings during a conversation. See Speech To Text for model-specific configuration options.

class STTUpdateSettingsEvent(Event): def __init__( self, language: str | None = None, # Language code (e.g., "en", "es", "multi") prompt: str | None = None, # Context prompt for accuracy endpointing: int | None = None, # End-of-speech detection sensitivity noise_reduction_type: str | None = None, # Audio processing type model: str | None = None, # STT model to use )

Usage:

# Switch to Spanish yield STTUpdateSettingsEvent(language="es") # Improve transcription with context yield STTUpdateSettingsEvent( prompt="Technical conversation about software development" ) # Optimize for phone audio yield STTUpdateSettingsEvent( noise_reduction_type="telephony", endpointing=2000 ) # Full configuration update yield STTUpdateSettingsEvent( language="es", model="nova-3", prompt="Conversación técnica en español.", endpointing=500, noise_reduction_type="near_field" )

StartRecordingEvent#

Starts recording the current call session.

class StartRecordingEvent(Event): def __init__( self, status_callback_url: str | None = None # Webhook for recording status )

Usage:

# Start recording yield StartRecordingEvent() # Start recording with webhook yield StartRecordingEvent( status_callback_url="https://your-app.com/webhooks/recording" )

StopRecordingEvent#

Stops the current call recording.

class StopRecordingEvent(Event): pass

Usage:

yield StopRecordingEvent()

InputAllowedEvent#

Enables or disables user input.

class InputAllowedEvent(Event): def __init__( self, allowed: bool # True to enable input, False to disable )

When allowed=False, user input is disabled until either InputAllowedEvent(allowed=True) is yielded or the handler completes.

Usage:

# Disable input during processing yield InputAllowedEvent(allowed=False) # ... do some processing ... # Re-enable input yield InputAllowedEvent(allowed=True)

UpdateAudioSettingsEvent#

Updates the session's background audio track. Use this to start, swap, or stop a looping background track (hold music, ambient noise, etc.) while the call continues.

class UpdateAudioSettingsEvent(Event): def __init__( self, background_track_url: str | None = None, # URL of the background track (None to stop) background_track_volume: float = 1.0, # Volume from 0.0 to 1.0 background_track_loop: bool = True, # Loop the track after_speech: bool = False, # Apply after preceding speech succeeds )

Usage:

# Start a background track yield UpdateAudioSettingsEvent( background_track_url="https://example.com/hold-music.mp3", background_track_volume=0.4 ) # Stop the background track yield UpdateAudioSettingsEvent(background_track_url=None)

ErrorEvent#

Signals that an error has occurred. Useful for surfacing failures from your handler to the platform for logging and observability.

class ErrorEvent(Event): def __init__( self, message: str # Description of the error )

Usage:

try: result = await call_external_api() except Exception as e: yield ErrorEvent(message=f"External API failed: {e}")

UpdateCallEvent#

Updates parameters on the active telephony call (for example, modifying TwiML on a Twilio call mid-session).

class UpdateCallEvent(Event): def __init__( self, data: dict # Provider-specific call update payload )

Usage:

yield UpdateCallEvent(data={"twiml": "<Response><Pause length='2'/></Response>"})

StartSessionEvent#

Starts a second, independent session from inside a live phone call — typically an outbound call placed to a third party while the caller stays on the line. Unlike TransferSessionEvent, the current session keeps running.

The new session is started at an entrypoint, which owns the caller ID and picks the release. There is nothing else to name.

class StartSessionEvent(Event): def __init__( self, *, entrypoint_id: str, # Entrypoint to start the session at input_parameters: dict | None = None, # Channel-specific parameters parameters: dict | None = None, # Context handed to the new handler )

There is no input_type to pass: the entrypoint determines the channel, just as it determines the caller ID and the release. (The argument still exists for the legacy agent form below; supplying one that disagrees with the entrypoint is rejected.)

input_parameters for a phone entrypoint:

KeyRequiredDescription
toPhoneNumberyesNumber to call. phoneNumber is accepted as an alias.
timeoutnoRing timeout in seconds, clamped to 5–600. Defaults to the provider's.
releaseIdnoPin the release instead of letting the entrypoint pick one.
inputDatanoStored on the new session and readable there as context.input_data.

fromPhoneNumber is not accepted: the entrypoint is the persona for the number, so the caller ID comes from its configuration. Passing it fails the spawn with an explicit error rather than placing a call from an unexpected number.

Usage:

yield StartSessionEvent( entrypoint_id="entrypoint_123", input_parameters={"toPhoneNumber": "+15555551234", "timeout": 30}, parameters={"reason": "escalation", "customer_id": "123"}, )

parameters reaches the new session's handler as context.data, alongside the parent linkage the runtime adds automatically (parent_session_id, parent_agent_id, parent_environment).

Handling a rejected spawn:

A spawn can be rejected before any call is placed — the entrypoint is inbound-only, it has no release, or the organization is out of credit. When that happens no child session exists, so none of its events will ever arrive. Rather than leave the handler waiting, the runtime delivers an ExternalEvent named start_session_failed:

if isinstance(event, ExternalEvent) and event.data.get("name") == "start_session_failed": # event.data["data"] carries entrypoint_id, agent_id, environment, and error. # `error` is internal diagnostic text — log it, never speak it to the caller. logger.error(f"spawn failed: {event.data['data']['error']}") yield TextToSpeechEvent(text="I could not reach them just now.")

Note: Sessions can only be started from a phone session. On any other channel the event falls back to a spoken apology.

Legacy: StartSessionEvent also accepts agent_id + environment instead of entrypoint_id. That form remains fully supported with an unchanged signature — entrypoint_id is keyword-only and sits after the legacy parameters, so existing positional calls are unaffected. See Legacy reference.


MergeSessionEvent#

A merge can end both handler sessions while leaving the telephone participants connected. Yielding the event alone is not a success acknowledgment. See MergeSessionEvent and session shutdown.

Requests that the telephony provider bridge the telephone participants of the named sessions. The exact bridge or conference behavior and which handler connections end depend on the provider; it is not a guarantee that both VoiceRun handlers stay in the conversation.

class MergeSessionEvent(Event): def __init__( self, session_id: str # ID of the session to merge with )

Usage:

yield MergeSessionEvent(session_id="session_abc123")

CustomEvent#

Creates a custom event for client-specific functionality.

class CustomEvent(Event): def __init__( self, name: str, # Custom event name data: dict = {} # Custom event data )

Usage:

yield CustomEvent( name="user_action", data={"action": "button_click", "button_id": "submit"} )

ExternalEvent#

Sends an event to a target agent session, including your own session when addressed with context.agent_id and context.session_id. With after_speech=True, sends only after the captured preceding speech completes successfully. Cancellation or failure drops the notification; handlers must provide recovery for required notifications.

class ExternalEvent(Event): def __init__( self, agent_id: str, # Target agent ID session_id: str, # Target session ID name: str, # Event name data: dict, # Event data after_speech: bool = False, # Send after preceding speech succeeds )

Usage:

yield ExternalEvent( agent_id="supervisor_agent", session_id="session_123", name="escalation", data={"reason": "customer_request", "priority": "high"} )

Enums#

TTSProvider#

Supported Text-to-Speech providers for use with TextToSpeechIdentifier.

class TTSProvider(str, Enum): AZURE = "azure" CARTESIA = "cartesia" CUSTOM = "custom" ELEVENLABS = "elevenlabs" FISH_AUDIO = "fish_audio" GOOGLE_CHIRP = "google_chirp" GRADIUM = "gradium" INWORLD = "inworld" MINIMAX = "minimax" OPENAI = "openai" QWEN3 = "qwen3" XAI = "xai"

Types#

TextToSpeechIdentifier#

Voice specification with provider and identifier for direct provider access.

class TextToSpeechIdentifier(TypedDict): provider: TTSProvider # The TTS provider identifier: str # Voice identifier for that provider

Examples:

{"provider": "azure", "identifier": "en-AU-WilliamNeural"} {"provider": "cartesia", "identifier": "6f84f4b8-58a2-430c-8c79-688dad597532"} {"provider": "custom", "identifier": "my_voicerun_custom_voice"} {"provider": "elevenlabs", "identifier": "21m00Tcm4TlvDq8ikWAM"} {"provider": "fish_audio", "identifier": "d13f84b987ad4f22b56d2b47f4eb838e"} {"provider": "google_chirp", "identifier": "laomedeia"} {"provider": "gradium", "identifier": "YTpq7expH9539ERJ"} {"provider": "inworld", "identifier": "Alex"} {"provider": "minimax", "identifier": "English_Aussie_Bloke"} {"provider": "openai", "identifier": "nova"} {"provider": "qwen3", "identifier": "Serena"} {"provider": "xai", "identifier": "eve"}
eventsapireference