Is this the correct way to update session userdata...
# ask-ai
b
Is this the correct way to update session userdata on a function tool? @function_tool async def *end_call*( _context_: RunContext, ) -> None: """Hang up after the call is coming to an end and you have asked whether they want a callback. Only call this after you have already asked the customer whether they want a callback and they have answered. Do not use this tool as a shortcut to say goodbye before that question is asked and answered. """ logger.info("end_call tool called — saying goodbye") await context.session.say(goodbye_message, _allow_interruptions_=False) logger.info("end_call goodbye finished — signaling shutdown") context.session.userdata["end_call_invoked"] = True context.session.shutdown() get_job_context().shutdown() logger.info("end_call shutdown complete") return None i'm trying to do it but its not appearing when I use a listener on my agent.py entrypoint:
Copy code
async def _wait_for_participant_disconnect(
        self,
        *,
        ctx: JobContext,
        participant_identity: str,
        bound_logger: structlog.BoundLogger,
        session: AgentSession,
    ) -> None:
        """Block until the remote participant leaves, the agent disconnects,
        or end_call is invoked."""
        <http://bound_logger.info|bound_logger.info>(
            "Waiting for disconnect",
            participant_identity=participant_identity,
            room_connected=ctx.room.isconnected(),
            room_name=ctx.room.name,
            remote_participants=list(ctx.room.remote_participants.keys()),
        )
        <http://bound_logger.info|bound_logger.info>(
            "session.userdata",
            userdata=session.userdata,
            ctxsessionuser=ctx.session.userdata,
        )
        while (
            participant_identity in ctx.room.remote_participants
            or not session.userdata.get("end_call_invoked")
        ):
            await asyncio.sleep(0.5)
        <http://bound_logger.info|bound_logger.info>(
            "Disconnected",
            participant_identity=participant_identity,
            room_connected=ctx.room.isconnected(),
            end_call_invoked=session.userdata.get("end_call_invoked", False),
        )
✅ 1
t
Thanks for reaching out, Amir. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding about your question on whether updating
context.session.userdata["end_call_invoked"] = True
inside the
end_call
function tool is correct and why your listener may not see it. Short answer: The assignment is only reliable if
session.userdata
was initialized as a mutable session object when the
AgentSession
was created. If
userdata
is not initialized (e.g.,
None
) or isn’t the same shared object instance, setting a key will fail or not be observed. What to check next (quick checklist): • Ensure userdata is initialized Create the session with a userdata object (dataclass or dict-like) before using it:
Copy code
@dataclass
  class MySessionData:
      end_call_invoked: bool = False

  session = AgentSession[MySessionData](userdata=MySessionData(), ...)
If you use a dataclass, set the flag with dot notation:
context.session.userdata.end_call_invoked = True
. • Confirm the same session instance is shared Verify the
session
passed into your listener is the identical
AgentSession
instance used by the function tool (no copies or re-instantiations). • Use logging immediately after setting the flag Add a log right after the assignment to confirm it ran:
Copy code
context.session.userdata.end_call_invoked = True
  logger.info("end_call flag set", userdata=context.session.userdata)
• Timing / shutdown ordering Make sure the userdata update happens before you call
context.session.shutdown()
(and that the listener checks state before shutdown completes). If shutdown happens immediately, listeners may not observe the change. • Match access style to userdata type If userdata is a dataclass use
session.userdata.end_call_invoked
. If it’s a dict,
session.userdata["end_call_invoked"]
is fine — but only if it was initialized as a dict. If you’d like, share how you construct
AgentSession
(the code where
userdata
is passed in), and I’ll point out exactly what to change. Sources: Agents and handoffs | LiveKit Documentation | Tasks and task groups | LiveKit Documentation | Agent speech and audio | LiveKit Documentation, with interactive elements
b
is this an acceptable way of setting it? session = AgentSession(userdata={"end_call_invoked": False})
t
Yes — that is acceptable if you want
userdata
to be a plain dict. Example:
Copy code
session = AgentSession(userdata={"end_call_invoked": False}, ...)
Notes and best practices: • If you use a dict, access and update with dict syntax:
context.session.userdata["end_call_invoked"] = True
. • Many teams prefer a typed
dataclass
for clearer fields and dot-notation (
session.userdata.end_call_invoked
) and to avoid silent mistakes. Example:
Copy code
from dataclasses import dataclass

@dataclass
class MySessionData:
    end_call_invoked: bool = False

session = AgentSession[MySessionData](userdata=MySessionData(), ...)
Pick whichever fits your codebase; just ensure
userdata
is initialized and the same
AgentSession
instance is shared between the tool and your listener. , with interactive elements
b
how do you then access an up to date session in the entrypoint?
t
Good question — here’s how to ensure your entrypoint (listener) sees the up-to-date
AgentSession.userdata
updated by a function tool. How to guarantee an up-to-date session • Create and pass a single shared
AgentSession
instance when you start the job/entrypoint. Don’t reinstantiate or copy the session.
Copy code
session = AgentSession(userdata={"end_call_invoked": False}, ...)
  start_job(session=session, ...)
• Use that same
session
object in both the function tool (via
context.session
) and your entrypoint/listener (
session
param). If they reference the same object, updates are visible immediately. • Match access style to the userdata type: ◦ Dict:
context.session.userdata["end_call_invoked"] = True
◦ Dataclass:
context.session.userdata.end_call_invoked = True
• Log right after you set the flag to confirm it ran before shutdown:
Copy code
context.session.userdata["end_call_invoked"] = True
  logger.info("flag set", userdata=context.session.userdata)
Synchronization / timing • Ensure the listener checks after the update occurs. Common patterns: ◦ Polling loop that checks the shared
session.userdata
(as you have), but ensure the loop condition uses OR/AND correctly so it exits when the flag is set. ◦ Use an asyncio.Event placed in shared userdata (or a separate shared object) that the function tool sets and the listener awaits. • If you call
context.session.shutdown()
immediately after setting the flag, ensure the listener observes the flag before shutdown completes (log/await or set an event). Quick note about your current loop • Your while condition:
Copy code
while (
      participant_identity in ctx.room.remote_participants
      or not session.userdata.get("end_call_invoked")
  ):
will continue while either the participant is present OR the flag is False — that’s correct if you want to wait until both the participant is gone AND the flag is True. If you intend to stop when either happens, change
or
to
and
. If you want, paste the code where you construct/start the job (where
AgentSession
is created and passed) and I’ll point out any instance-sharing or timing fixes. , with interactive elements