how do you attach metadata to a room ?
# ask-ai
e
how do you attach metadata to a room ?
t
Thanks for reaching out, Aditya. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about how to attach metadata to a room in LiveKit. You set room metadata from the server using the Room Service API (this must be done server-side). After updating, clients receive the new value through the
room_metadata_changed
event and can read it via
room.metadata
. Examples: • Node.js
Copy code
const { RoomServiceClient } = require('livekit-server-sdk');

const roomService = new RoomServiceClient('<https://your-livekit-url>', 'api-key', 'api-secret');

await roomService.updateRoomMetadata('my-room-name', 'New room metadata');
• Python
Copy code
from livekit.api import LiveKitAPI

lk_api = LiveKitAPI(url='<https://your-livekit-url>', api_key='api-key', api_secret='api-secret')
await lk_api.room.update_room_metadata(room='my-room-name', metadata='New room metadata')
• Go
Copy code
client := livekit.NewRoomServiceClient("<https://your-livekit-url>", "api-key", "api-secret")
_, err := client.UpdateRoomMetadata(context.Background(), &livekit.UpdateRoomMetadataRequest{
    Room:     "my-room-name",
    Metadata: "New room metadata",
})
Important notes • Updates are asynchronous. Clients should listen for
room_metadata_changed
to reliably receive the new value. • Use room metadata for shared state (labels, workflow status, config, etc.). • After calling the server API, do not assume immediate consistency — rely on the event to know when clients have the updated metadata. If you want, tell me: • Which server SDK and language you’re using, and • Whether you want an example for listening to
room_metadata_changed
on the client side — I can provide that next. Sources: Handling asynchronous metadata updates in LiveKit | LiveKit
e
const roomMetadata = { agentId: agent.id, agentName: agent.name, firstMessage: agentSettings.firstMessage, instructions: consolidatedInstructions, voice: { gender: agentSettings.voice.gender as string, voice_name: agentSettings.voice.name, }, }; // Generate participant token const participantIdentity = `fs_user_${Math.floor(Math.random() * 10_000)}`; const roomName = `fs_${Math.floor(Math.random() * 10_000)}`; const apiKey = env.LIVEKIT_API_KEY; const apiSecret = env.LIVEKIT_API_SECRET; const host = env.LIVEKIT_URL; const roomService = new RoomServiceClient(host, apiKey, apiSecret); const room = await roomService.createRoom({ name: roomName, metadata: JSON.stringify(roomMetadata), }); when you attach metadata to a room like this, do you need to explicitly connect in the session like so ? @breezy-insurance-66772.rtc_session() async def frostline_agent(ctx: agents.JobContext): await ctx.connect()
t
Thanks — you’re asking whether creating a room with metadata from the server (as in your example) also requires you to explicitly connect a session (e.g.,
await ctx.connect()
). Short answer: No — you do not need to open a LiveKit client session just to attach metadata when you create or update a room from the server. Creating the room with
metadata
via the Room Service API (or calling
updateRoomMetadata
) stores that string on the room immediately from the server side. Clients will receive the value when they join or when the metadata changes via the
room_metadata_changed
event. A few practical points to keep in mind: • The server-side call (your
roomService.createRoom({... metadata: JSON.stringify(...)})
) is sufficient to set the metadata; no client connection is required to persist it. • Clients already connected will learn about updates by handling the
room_metadata_changed
event. New clients joining after creation will see
room.metadata
when they connect. • If your server-side environment also needs to act as a participant (publish tracks, subscribe, or receive real-time events), then you would call
connect()
and create a participant session. Otherwise, for pure room management (create/update metadata, manage participants), server-side Room Service API calls are the correct approach. • Metadata updates are effectively asynchronous across distributed SFU nodes — rely on the
room_metadata_changed
event on the client rather than assuming immediate propagation. If you want, I can: • Show a short client example that listens for
room_metadata_changed
, or • Show how to update metadata later from the server with
updateRoomMetadata
. Which would you prefer?
e
Copy code
import json
from dotenv import load_dotenv
from livekit import agents
from livekit.agents import AgentServer, AgentSession, Agent, room_io, function_tool, get_job_context
from livekit.plugins import noise_cancellation, silero, google, deepgram, openai
from livekit.plugins.turn_detector.english import EnglishModel
from typing import List

from pydantic import BaseModel, Field

class Voice(BaseModel):
    gender: str
    voice_name: str = Field(alias="voiceName")

class RoomMetadata(BaseModel):
    agent_id: str = Field(alias="agentId")
    name: str
    first_message: str = Field(alias="firstMessage")
    instructions: str
    voice: Voice

load_dotenv(".env")


class FrostlineAssistant(Agent):
    def __init__(self, instructions: str, pid: str) -> None:
        super().__init__(
            instructions=instructions,
        )
        self.pid = pid

    @function_tool
    async def execute_flow(self, flow_id: List[str] | str) -> bool:
        room = get_job_context().room
        await room.local_participant.perform_rpc(
            destination_identity=self.pid,
            method="executeFlow",
            payload=json.dumps({"flow_id": flow_id}),
        )
        return True

    @function_tool
    async def suggest_flow(self, flow_id: List[str] | str) -> bool:
        room = get_job_context().room
        await room.local_participant.perform_rpc(
            destination_identity=self.pid,
            method="suggestFlow",
            payload=json.dumps({"flow_id": flow_id}),
        )
        return True

server = AgentServer()


@FlashCall Server.rtc_session()
async def frostline_agent(ctx: agents.JobContext):

    await ctx.connect()

    participant = await ctx.wait_for_participant()

    room_metadata = RoomMetadata.model_validate_json(ctx.room.metadata or "{}", by_alias=True)

    session = AgentSession(
        stt=deepgram.STT(
            model="nova-3"
        ),
        llm=openai.LLM(
            model="gpt-5.2",
        ),
        tts=google.TTS(
            gender=room_metadata.voice.gender,
            voice_name=room_metadata.voice.voice_name,
        ),
        vad=silero.VAD.load(),
        turn_detection=EnglishModel(),
        max_tool_steps=25,
        user_away_timeout=10,
    )

    await session.start(
        room=ctx.room,
        agent=FrostlineAssistant(
            instructions=room_metadata.instructions,
            pid=participant.identity,
        ),
        room_options=room_io.RoomOptions(
            audio_input=room_io.AudioInputOptions(
                noise_cancellation=noise_cancellation.BVC(),
            ),
        ),
    )

    await session.generate_reply(
        instructions=room_metadata.first_message,
    )


if __name__ == "__main__":
    agents.cli.run_app(server)
Copy code
const room = useRoomContext();

	useEffect(() => {
		room.registerRpcMethod("suggest_flow", async (data) => {
			console.log("suggest_flow", data);
			return "Flow suggested";
		});

		room.registerRpcMethod("execute_flow", async (data) => {
			console.log("execute_flow", data);
			return "Flow executed";
		});

		return () => {
			room.unregisterRpcMethod("suggest_flow");
			room.unregisterRpcMethod("execute_flow");
		};
	}, [room]);
Copy code
m_rpc(\n    ...<3 lines>...\n    )\n  File \"C:\\Users\\adity\\projects\\thundergen\\frostline-lk-agent\\.venv\\Lib\\site-packages\\livekit\\rtc\\participant.py\", line 328, in perform_rpc\n    raise RpcError._from_proto(cb.perform_rpc.error)\nlivekit.rtc.rpc.RpcError: Method not supported at destination", "function": "suggest_flow", "speech_id": "speech_b7162c05c1b8", "timestamp": "2026-02-19T18:39:14.997085+00:00"}
{"message": "exception occurred while executing tool", "level": "ERROR", "name": "livekit.agents", "exc_info": "Traceback (most recent call last):\n  File \"C:\\Users\\adity\\projects\\thundergen\\frostline-lk-agent\\.venv\\Lib\\site-packages\\livekit\\agents\\voice\\generation.py\", line 608, in _traceable_fnc_tool\n    val = await function_callable()\n          ^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"C:\\Users\\adity\\projects\\thundergen\\frostline-lk-agent\\main.py\", line 35, in execute_flow\n    await room.local_participant.perform_rpc(\n    ...<3 lines>...\n    )\n  File \"C:\\Users\\adity\\projects\\thundergen\\frostline-lk-agent\\.venv\\Lib\\site-packages\\livekit\\rtc\\participant.py\", line 328, in perform_rpc\n    raise RpcError._from_proto(cb.perform_rpc.error)\nlivekit.rtc.rpc.RpcError: Method not supported at destination", "function": "execute_flow", "speech_id": "speech_b7162c05c1b8", "timestamp": "2026-02-19T18:39:16.032445+00:00"}
Why am i getting this RPC error, can you help me to debug it.
t
Thanks — I’m responding to your question about why you get the RpcError "Method not supported at destination" when calling
perform_rpc
for
suggestFlow
/
executeFlow
. What that error means • The destination participant you targeted does not have the RPC method registered under the name you called. The call reached the target but the method was not found, so the target returned "Method not supported at destination." Most likely causes and quick checks 1. Destination identity is wrong ◦ You set
destination_identity=self.pid
when calling
perform_rpc
. Confirm
self.pid
equals the participant.identity value from the client that registered the RPC methods. ◦ Log the participant identities on both sides: ▪︎ In your agent: print
participant.identity
when you call
wait_for_participant()
and before you call
perform_rpc
. ▪︎ In the client: print
room.localParticipant.identity
(or equivalent) after connecting. ◦ If identities don’t match, the RPC will go to a different participant (or no one), causing the error. 1. RPC method name mismatch ◦ On the client you register methods as
"suggest_flow"
and
"execute_flow"
. ◦ In Python agent you call method names
executeFlow
and
suggestFlow
(note casing). The RPC system is case-sensitive and the destination expects the name you registered. ◦ Solution: Use the exact same RPC names on both sides. Either: ▪︎ Change server calls to match the client: use
"suggest_flow"
and
"execute_flow"
, or ▪︎ Change client registration to camelCase (
"suggestFlow"
/
"executeFlow"
). ◦ This naming mismatch is the most likely root cause based on your code. 1. Method registered on different participant ◦ Ensure the client that registered the RPC methods is the participant your agent is calling. If multiple participants exist, you may be targeting the wrong one. ◦ Use logs or a participant list to verify which participant registered the RPC methods. 1. Timing / ordering issue ◦ If the agent calls
perform_rpc
before the client has registered its RPC methods, the destination will not recognize the method. ◦ Ensure the client registers RPC handlers immediately after connecting and before you call
perform_rpc
. Add retries or a small delay, or implement a readiness handshake: ▪︎ Client emits a short RPC or a data message to tell the agent “ready”. ▪︎ Agent waits for that readiness signal before calling
perform_rpc
. Actionable debugging steps 1. Log identities and registration: ◦ Agent: log
participant.identity
and the
destination_identity
you pass. ◦ Client: log its
identity
and confirm handlers registered (console log inside registration callback). 1. Match method names exactly (case-sensitive): ◦ Update either side so method names are identical. Example: change agent calls to:
Copy code
await room.local_participant.perform_rpc(
         destination_identity=self.pid,
         method="suggest_flow",
         payload=json.dumps({"flow_id": flow_id}),
     )
• Or change client to register
"suggestFlow"
and
"executeFlow"
. 1. Add a readiness handshake: ◦ Client sends a small RPC or data message to agent when handlers are registered. ◦ Agent waits for that signal before performing RPCs. 1. Retry with error details: ◦ Catch RpcError and log `cb.perform_rpc.error`/exception payload so you can see destination and method in logs. If you’d like, I can: • Provide the exact minimal code changes to make names consistent (Python agent example + client JS example), or • Show a small readiness-handshake pattern to prevent timing issues. Which would you prefer, and can you confirm whether
self.pid
equals the client participant identity and whether you want to change names to snake_case on the agent or camelCase on the client?