how to implement call recording in my livekit agen...
# ask-ai
k
how to implement call recording in my livekit agent. ?
t
Thanks for reaching out, Saurav. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
To implement call recording in LiveKit, you can use the Egress feature. Here’s a summary of the steps: 1. Enable Auto-Egress for SIP Calls: Configure the
room_config.egress
settings to enable automatic recording for SIP calls. 2. Record Agent Sessions: Start a room composite recorder in your agent's entrypoint. This captures all participants and interactions in the room. Example code snippet:
Copy code
req = api.RoomCompositeEgressRequest(
    room_name="my-room",
    layout="speaker",
    preset=api.EncodingOptionsPreset.H264_720P_30,
    audio_only=False,
    segment_outputs=[api.SegmentedFileOutput(
        filename_prefix="my-output",
        playlist_name="my-playlist.m3u8",
        live_playlist_name="my-live-playlist.m3u8",
        segment_duration=5,
        gcp=api.GCPUpload(
            credentials=file_contents,
            bucket="<my-bucket>",
        ),
    )],
)
3. Cloud Storage Configuration: Set up access to a cloud storage provider (e.g., Google Cloud Storage, Amazon S3) to upload the recordings. 4. Recording Process: Use the
Egress.StartRoomCompositeEgress()
API to start and stop recordings based on participant activity. Let me know if you need further details or assistance with any specific step! Sources: SIP Integration | livekit/livekit_composite | DeepWiki | Speech &amp; Audio Processing | livekit/livekit_composite | DeepWiki | Recording agent sessions | LiveKit Docs | Session recording and transcripts | LiveKit Docs | Custom recording templates | LiveKit Docs
Hey there! If you want to add call recording to your agent, LiveKit's Egress feature is a great way to do it. Here's a simple guide to help you tweak your code: 1. Import Required Modules: First, make sure you import the modules needed for Egress.
from livekit import api
2. Update the Entrypoint: Next, add the Egress setup to your entrypoint function. Here's an example:
Copy code
async def entrypoint(ctx: JobContext):
    # Read credentials from file
    file_contents = ""
    with open("/path/to/credentials.json", "r") as f:
        file_contents = f.read()

    # Configure Egress
    req = api.RoomCompositeEgressRequest(
        room_name="my-room",
        layout="speaker",
        preset=api.EncodingOptionsPreset.H264_720P_30,
        audio_only=False,
        segment_outputs=[api.SegmentedFileOutput(
            filename_prefix="my-output",
            playlist_name="my-playlist.m3u8",
            live_playlist_name="my-live-playlist.m3u8",
            segment_duration=5,
            gcp=api.GCPUpload(
                credentials=file_contents,
                bucket="<my-bucket>",
            ),
        )],
    )

    lkapi = api.LiveKitAPI()
    res = await lkapi.egress.start_room_composite_egress(req)
3. Cloud Storage Configuration: Make sure your cloud storage credentials (like for Google Cloud Storage or Amazon S3) are all set and valid. 4. Optional Audio-Only Recording: If you just want to record audio, simply set
audio_only=True
in your Egress request. If you hit any bumps or want help integrating this into your existing agent code, just let me know—I’m happy to help!
k
so once recording is started, will it automatically stop when call end?
t
Absolutely! LiveKit can automatically stop recording when a call ends, depending on how you set up the egress: • RoomComposite Egress: The recording will stop on its own once everyone leaves the room. • Participant Egress: The recording stops automatically when that specific participant leaves the room. Just a quick tip: make sure the room is properly closed when the call wraps up to keep everything running smoothly. If you have any questions or want me to explain anything further, just let me know—I’m here to help! Sources: Composite and web recordings | LiveKit Docs | Recording Participants | LiveKit Docs
k
refactor this code to save recordings to supabase storage
req = api.RoomCompositeEgressRequest(
room_name="my-room",
layout="speaker",
preset=api.EncodingOptionsPreset.H264_720P_30,
audio_only=False,
segment_outputs=[api.SegmentedFileOutput(
filename_prefix="my-output",
playlist_name="my-playlist.m3u8",
live_playlist_name="my-live-playlist.m3u8",
segment_duration=5,
gcp=api.GCPUpload(
credentials=file_contents,
bucket="<my-bucket>",
),
)],
)
t
To save recordings to Supabase Storage, you can set up the
RoomCompositeEgressRequest
to use the S3-compatible storage option. Here’s a handy refactor of the code for you: Sources: protobufs/livekit_egress.proto | README.md | livekit-protocol/livekit/protocol/egress.py
Hey there! Your configuration for
RoomCompositeEgressRequest
with Supabase S3 looks spot on. Let me quickly walk you through the key parts to make sure everything’s clear: • S3 Configuration: You’ve got all the important fields (
access_key
,
secret
,
region
,
endpoint
, and
bucket
) set up just right. • RoomCompositeEgressRequest: The essentials like
room_name
,
layout
, and
segment_outputs
are all configured properly. • Audio-Only Recording: Using
audio_only=True
is perfect if you want to record just the audio. If you have any questions, want to tweak something, or add optional stuff like metadata or tagging, just give me a shout—I’m here to help! Sources: protobufs/livekit_egress.proto | livekit-protocol/livekit/protocol/egress.py
k
so how file will be saved like with what names?
t
Just to give you a quick rundown on how the file naming works for your recordings: it all comes down to the
filename_prefix
and the way you set up the
SegmentedFileOutput
. Here’s the scoop: • Filename Prefix: The
filename_prefix
you choose (like
recording_file_name_prefix
) becomes the base name for your files. • Segmented Files: Since you’re using segmented outputs, your recordings will be split into multiple files with names in order, such as
recording_file_name_prefix_00001.ts
,
recording_file_name_prefix_00002.ts
, and so forth. • Directory Structure: If your prefix includes a folder path (for example,
recordings/my-room
), the files will be saved right there in that folder inside your S3 bucket. If you’d like to get creative and customize the naming even more—maybe add the room name or timestamps—just give me a shout! I’m happy to help you set that up.
k
and is this enough.? do i need to pass rec_req to somewhere? rec_req = api.RoomCompositeEgressRequest( room_name=agents.get_job_context().room.name, layout="speaker", preset=api.EncodingOptionsPreset.H264_720P_30, audio_only=True, segment_outputs=[ api.SegmentedFileOutput( filename_prefix=recording_file_name_prefix, segment_duration=5, s3=s3_upload_config, ) ], )
t
Once you’ve created the
RoomCompositeEgressRequest
, the next step is to send it over to the LiveKit egress service to kick off the recording. Just having the request object sets things up, but it won’t start recording by itself. Here’s a simple way to get the egress going:
Copy code
lkapi = api.LiveKitAPI()
res = await lkapi.egress.start_room_composite_egress(rec_req)

await lkapi.aclose()
This will begin recording based on what you’ve configured. You’ll get back an
egress_id
in the response, which is handy for keeping track of or managing your recording session. If you have any questions or want a hand with this, just let me know—I’m here to help! Sources: Session recording and transcripts | LiveKit Docs | Universal Egress | protobufs/livekit_egress.proto
k
i want to save the recording url in database. how to do that?
t
Here’s a simple way to save the recording URL in your database right after you start the recording: 1. Start the Recording: Just call the
start_room_composite_egress
method to kick things off. This will give you an
EgressInfo
object. 2. Grab the Recording URL: From that
EgressInfo
, pull out the
egress_id
and build the recording URL using your output settings. For example:
recording_url = f"https://{req.segment_outputs[0].s3.bucket}.s3.{req.segment_outputs[0].s3.region}.<http://amazonaws.com/{req.segment_outputs[0].filename_prefix}|amazonaws.com/{req.segment_outputs[0].filename_prefix}>"
3. Save It in Your Database: Pop the
egress_id
and
recording_url
into your database so you can easily find them later. Like this:
save_recording_info(egress_id, recording_url)
Just a heads-up: make sure to catch any errors along the way and double-check the URL before saving. If you want, I’m here to help with the database part! Sources: Session recording and transcripts | LiveKit Docs | Egress API | LiveKit Docs | protobufs/livekit_egress.proto | cmd/lk/egress.go
k
if i want to save the recording in a file, this needs to be a call recording so ofc the format will not be mp4. so give me code for that.
t
If you want to save your call recordings as audio files in the OGG format, here’s a simple way to set up the `RoomCompositeEgressRequest`:
Copy code
req = api.RoomCompositeEgressRequest(
    room_name="my-room",
    audio_only=True,  # For audio-only recordings
    file_outputs=[api.EncodedFileOutput(
        file_type=api.EncodedFileType.OGG,  # Specify OGG format
        filepath="recordings/{room_name}-{time}.ogg",
        s3=api.S3Upload(
            bucket="your-bucket-name",
            region="your-region",
            access_key="your-access-key",
            secret="your-secret-key",
        ),
    )],
)
Just swap out the placeholders with your own details, and you’ll have your recording saved as an OGG file in your S3 bucket. If you have any questions or want me to walk you through it, just let me know—I’m here to help!
k
i want to save recording to a file
t
If you'd like to save the recording to a local file, you can set up the
RoomCompositeEgressRequest
with a local file path. Here's a quick example to help you out:
Copy code
req = api.RoomCompositeEgressRequest(
    room_name="my-room",
    audio_only=True,  # For audio-only recordings
    file_outputs=[api.EncodedFileOutput(
        file_type=api.EncodedFileType.OGG,  # Specify the file format
        filepath="/path/to/recording.ogg",  # Local file path
    )],
)
Just swap out
/path/to/recording.ogg
with wherever you'd like the file to be saved on your computer. Make sure that folder exists and that your app has permission to write there. If you run into any issues or have questions, feel free to ask—I'm here to help!
k
i am getting this error.
Copy code
2025-08-06 16:50:06.644 | ERROR    | agents.assistant_agent:on_enter:114 - Failed to start call recording egress: TwirpError(code=invalid_argument, message=request has missing or invalid field: output, status=400)
here is the code m using
Copy code
recording_file_name = (
                    f"call-recording-{self.assistant.get('id')}-{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.ogg"
                )
                rec_req = api.RoomCompositeEgressRequest(
                    room_name=agents.get_job_context().room.name,
                    file_outputs=[
                        api.EncodedFileOutput(
                            file_type=api.EncodedFileType.OGG,
                            filepath=recording_file_name,
                        )
                    ],
                    audio_only=True,
                    # segment_outputs=[
                    #     api.SegmentedFileOutput(
                    #         filename_prefix=recording_file_name_prefix,
                    #         segment_duration=5,
                    #         s3=s3_upload_config,
                    #     )
                    # ],
                )
                # Use a short-lived LiveKitAPI client so the underlying aiohttp.ClientSession
                # is properly closed after the request finishes. Otherwise we leak the session
                # and `asyncio` will complain with an "Unclosed client session" warning which
                # we observed in the logs when starting call recording. Using an async context
                # manager automatically invokes `aclose()` on exit and prevents the leak.
                try:
                    async with api.LiveKitAPI() as lkapi:
                        info = await lkapi.egress.start_room_composite_egress(rec_req)
                        <http://logger.info|logger.info>(f"Started egress (id={info.egress_id}) for call recording")
                except Exception as e:
                    logger.error(f"Failed to start call recording egress: {e}")
t
Hey there! The error
request has missing or invalid field: output
pops up because the egress request is missing a properly set output destination. No worries though—here’s how you can fix it: 1. Make Sure Output is Specified: Every
RoomCompositeEgressRequest
needs at least one valid output. Looks like you’re using
file_outputs
, which is exactly right. 2. Double-Check the Output Setup: For
EncodedFileOutput
, just confirm these: ◦
file_type
is set properly (like
api.EncodedFileType.OGG
). ◦
filepath
points to a valid location (for example,
recordings/my-room.ogg
). 1. Use the Right Field Format: You’re already on track using
file_outputs
, which is perfect. Just avoid older fields like
file
. Here’s a tweaked version of your code to guide you:
Copy code
recording_file_name = (
    f"call-recording-{self.assistant.get('id')}-{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.ogg"
)
rec_req = api.RoomCompositeEgressRequest(
    room_name=agents.get_job_context().room.name,
    file_outputs=[
        api.EncodedFileOutput(
            file_type=api.EncodedFileType.OGG,
            filepath=f"recordings/{recording_file_name}",
        )
    ],
    audio_only=True,
)
Just a quick heads-up: make sure the
recordings/
folder exists and you have permission to write to it. If you hit any more snags or want me to walk you through anything else, just give me a shout! Sources: packages/livekit-server-sdk/src/EgressClient.ts | livekit-protocol/src/livekit.rs | livekit-protocol/livekit/protocol/egress.pyi | src/main/kotlin/io/livekit/server/EgressServiceClient.kt | pkg/errors/errors.go
k
i still getting this error
Copy code
2025-08-06 16:59:43.926 | INFO     | __mp_main__:entrypoint:155 - Agent entrypoint finished.
2025-08-06 16:59:43,926 - DEBUG livekit.agents - using audio io: `RoomIO` -> `AgentSession` -> `TranscriptSynchronizer` -> `RoomIO` {"pid": 28825, "job_id": "AJ_MUCk5DEFBgUn"}
2025-08-06 16:59:43,926 - DEBUG livekit.agents - using transcript io: `AgentSession` -> `TranscriptSynchronizer` -> `RoomIO` {"pid": 28825, "job_id": "AJ_MUCk5DEFBgUn"}
2025-08-06 16:59:44.447 | ERROR    | agents.assistant_agent:on_enter:118 - Failed to start call recording egress: TwirpError(code=invalid_argument, message=request has missing or invalid field: output, status=400)
t
It looks like the error is happening because the
EncodedFileOutput
is missing a storage destination. Besides specifying the
file_type
and
filepath
, you’ll need to add at least one storage destination like S3, GCP, Azure, or AliOSS. Here’s a quick example of how to save the file locally:
Copy code
rec_req = api.RoomCompositeEgressRequest(
    room_name=agents.get_job_context().room.name,
    file_outputs=[
        api.EncodedFileOutput(
            file_type=api.EncodedFileType.OGG,
            filepath="/path/to/recordings/call-recording.ogg",
        )
    ],
    audio_only=True,
)
If you’re using cloud storage (like S3), you’ll want to include the storage details like this:
Copy code
rec_req = api.RoomCompositeEgressRequest(
    room_name=agents.get_job_context().room.name,
    file_outputs=[
        api.EncodedFileOutput(
            file_type=api.EncodedFileType.OGG,
            filepath="recordings/call-recording.ogg",
            s3=api.S3Upload(
                bucket="your-bucket-name",
                region="your-region",
                access_key="your-access-key",
                secret="your-secret-key",
            ),
        )
    ],
    audio_only=True,
)
Just double-check that the folder or storage bucket exists and you have permission to write to it. If you’re still running into trouble or want a hand with anything else, feel free to reach out—I’m here to help! Sources: Egress API | LiveKit Docs | livekit/livekit_egress.pb.go
k
not working
Copy code
s3_upload_config = api.S3Upload(
    access_key=settings.SUPABASE_S3_ACCESS_KEY.get_secret_value(),
    secret=settings.SUPABASE_S3_SECRET_KEY.get_secret_value(),
    region=settings.SUPABASE_S3_REGION.get_secret_value(),
    endpoint=settings.SUPABASE_S3_STORAGE_URL.get_secret_value(),
    bucket=settings.SUPABASE_S3_BUCKET_NAME.get_secret_value(),
)

recording_file_name = (
                    f"call-recording-{self.assistant.get('id')}-{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.ogg"
                )
                filepath = os.path.join(os.getcwd(), recording_file_name)
                <http://logger.info|logger.info>(f"Recording file path: {filepath}")
                rec_req = api.RoomCompositeEgressRequest(
                    room_name=agents.get_job_context().room.name,
                    layout="speaker",
                    file_outputs=[
                        api.EncodedFileOutput(
                            file_type=api.EncodedFileType.OGG,
                            filepath=filepath,
                            s3=s3_upload_config,
                        )
                    ],
                    audio_only=True,
                )
                try:
                    async with api.LiveKitAPI() as lkapi:
                        info = await lkapi.egress.start_room_composite_egress(rec_req)
                        <http://logger.info|logger.info>(f"Started egress (id={info.egress_id}) for call recording")
                except Exception as e:
                    logger.error(f"Failed to start call recording egress: {e}")
t
Hey! The issue happens because
EncodedFileOutput
can’t handle both local file paths and S3 settings at the same time. Just so you know, the
filepath
field is actually a filename template for where the file goes in your cloud storage (like S3), not a local file path. Here’s a simple way to fix your configuration:
Copy code
rec_req = api.RoomCompositeEgressRequest(
    room_name=agents.get_job_context().room.name,
    file_outputs=[
        api.EncodedFileOutput(
            file_type=api.EncodedFileType.OGG,
            filepath="recordings/call-recording.ogg",  # Path within S3 bucket
            s3=api.S3Upload(
                bucket=settings.SUPABASE_S3_BUCKET_NAME.get_secret_value(),
                region=settings.SUPABASE_S3_REGION.get_secret_value(),
                access_key=settings.SUPABASE_S3_ACCESS_KEY.get_secret_value(),
                secret=settings.SUPABASE_S3_SECRET_KEY.get_secret_value(),
                endpoint=settings.SUPABASE_S3_STORAGE_URL.get_secret_value(),
            ),
        )
    ],
    audio_only=True,
)
With this setup, your recording will be saved right to your S3 bucket at the path you specified. If you want to get the file locally afterward, you can just download it from S3 once the recording’s done. Just shout if you want a hand with that! Sources: Egress API | LiveKit Docs | livekit/livekit_egress.pb.go | Session recording and transcripts | LiveKit Docs