millions-gold-86808
03/14/2026, 7:54 AMtall-airline-16756
03/14/2026, 7:54 AMmillions-gold-86808
03/14/2026, 7:54 AM@server.rtc_session(agent_name=AGENT_NAME)
async def handle_job(ctx: JobContext) -> None:
"""
Entrypoint executed for every job dispatched to voice-agent-ayush.
Metadata fields injected by dispatcher.py:
call_id – database primary key of the Call record
to – destination phone number
human_number – human-readable caller number (optional)
prompt – per-call system prompt override (optional)
"""
# ── Parse dispatch metadata ──────────────────────────────────────────────
metadata: dict = json.loads(ctx.job.metadata or "{}")
call_id = metadata.get("call_id")
to = metadata.get("to")
human_number = metadata.get("human_number")
prompt = metadata.get("prompt") or EXAMPLE_PROMPT
language = metadata.get("language", "English")
recordingFile = metadata.get("recordingFile") or None
language_code = LANGUAGE_CODES.get(language, "en")
<http://logger.info|logger.info>(
"Job received — call_id=%s to=%s human_number=%s",
call_id,
to,
human_number,
)
# ── Connect to the LiveKit room ──────────────────────────────────────────
await ctx.connect()
# ── Build agent session with STT / LLM / TTS ────────────────────────────
session = AgentSession(
stt=deepgram.STT(model="nova-3", language=language_code),
llm=google.LLM(model="gemini-2.5-flash"),
tts=deepgram.TTS(model="aura-2-asteria-en"),
vad=silero.VAD.load(),
)
# ── Transcript accumulator ───────────────────────────────────────────────
transcript_turns: list[str] = []
await session.start(
room=ctx.room,
agent=OutboundAssistant(instructions=prompt),
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=lambda params: (
noise_cancellation.BVCTelephony()
if params.participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP
else noise_cancellation.BVC()
),
),
),
)tall-airline-16756
03/14/2026, 7:54 AMroom_config.egress pointing to your S3 bucket (or instructions for the dashboard).
• Notes on how to verify recordings (webhook events or dashboard) and where the file will appear in S3.
• Security recommendation for using AssumeRole (no long-term keys). Sources: sip | livekit/livekit_composite | DeepWiki | Universal Egress | LiveKitmillions-gold-86808
03/14/2026, 7:57 AMtall-airline-16756
03/14/2026, 7:57 AMroom_config.egress to output the room composite to S3. No changes are needed in your agent code — recordings start automatically when the room is active.
1) Example: create a SIP dispatch rule with auto‑egress to S3 (async Python)
from livekit import api
import asyncio
async def create_sip_dispatch_rule(lk_url, api_key, api_secret,
trunk_id, s3_bucket, s3_region,
s3_prefix=None, access_key=None, secret=None):
lk_api = api.LiveKitAPI(url=lk_url, api_key=api_key, api_secret=api_secret)
# Build S3 upload config; omit access_key/secret if using IAM role (AssumeRole)
s3_upload = api.S3Upload(
access_key=access_key,
secret=secret,
bucket=s3_bucket,
region=s3_region,
prefix=s3_prefix or ""
)
encoded_output = api.EncodedFileOutput(
file_type=api.EncodedFileType.MP4,
s3=s3_upload
)
room_composite = api.AutoEgressConfiguration.RoomComposite(
file_outputs=[encoded_output]
)
egress_cfg = api.AutoEgressConfiguration(room_composite=room_composite)
room_config = api.RoomConfiguration(egress=egress_cfg)
dispatch_request = api.CreateSIPDispatchRuleRequest(
trunk_ids=[trunk_id],
rule=api.SIPDispatchRule(
dispatch_rule_individual=api.SIPDispatchRuleIndividual(
room_prefix="call" # adjust as needed
)
),
room_config=room_config
)
dispatch_rule = await lk_api.sip.create_sip_dispatch_rule(dispatch_request)
return dispatch_rule
# Example usage:
# asyncio.run(create_sip_dispatch_rule(
# lk_url="<https://your-livekit.example.com>",
# api_key="LK_API_KEY",
# api_secret="LK_API_SECRET",
# trunk_id="sip-trunk-123",
# s3_bucket="your-recordings-bucket",
# s3_region="us-west-2",
# s3_prefix="recordings/",
# access_key="YOUR_AWS_KEY", # or None if using AssumeRole
# secret="YOUR_AWS_SECRET" # or None if using AssumeRole
# ))
2) Where this fits with your agent code
• Keep your current agent code that calls await ctx.connect() and starts the session — auto‑egress will begin recording the room composite automatically because the dispatch rule’s room_config.egress is configured.
• No additional recording logic is required inside handle_job.
3) Verifying recordings and finding files
• After the call ends, LiveKit will upload the composite MP4 to:
s3://<your-recordings-bucket>/<prefix>/livekit-room-composite-<timestamp>.mp4
• You can also enable webhooks to receive egress events (e.g., egress_started, egress_ended) to confirm when uploads begin and finish.
4) Security recommendations
• Prefer using IAM roles (AssumeRole) instead of embedding long‑lived AWS keys:
◦ If you use AssumeRole, omit access_key and secret in the S3 config and configure the role ARN/external ID on the LiveKit side so LiveKit can assume the role to upload.
• If you must supply keys, use a restricted IAM user with write-only permissions to the specific recordings prefix.
5) Optional: monitor via webhooks
• Configure webhook handling to capture egress_started and egress_ended events so you can log the S3 path and status. Use those events to trigger any post-processing.
If you’d like, I can:
• Provide a sample webhook handler (async Python) to capture egress events.
• Show how to configure the dispatch rule to produce per-participant tracks instead of a composite (if you need separate audio tracks).
• Help convert the example to your exact SDK version or to sync-style code if you’re not using async.
Which of those would be most helpful next?