In my livekit agent I'm tying to generate an agent...
# ask-ai
r
In my livekit agent I'm tying to generate an agent workflow, while propagating the context, like this if propagate_context: propagated_ctx = context.session.history.copy(exclude_instructions=False) # TalkingbitsAgent if hasattr(target_inst, "chat_ctx"): log_lifecycle.info(f"Context to be propagated {pprint.pformat(propagated_ctx)}") target_inst.chat_ctx = propagated_ctx I have an issue with some of the context (specifically the context that is injected as "system") is not propagated. Some of my tool functions inject system context into the call like this chat_ctx.add_message(role="system", content=message) And that data is not being passed to the next agent. What could I do to solve this?
✅ 1
t
Thanks for reaching out, Alejandro. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — you're asking about why
system
messages injected via
chat_ctx.add_message(role="system", ...)
aren't reaching the next agent. I’ll help troubleshoot and give steps to ensure they propagate. Short answer: system messages are included in the session history, but they can be filtered out when copying or handing off context. Make sure you copy and pass the full history (not excluding instructions) and initialize/update the target agent with that copy. What to check and try 1. Ensure you copy the full history with system messages: ◦ Use
propagated_ctx = context.session.history.copy(exclude_instructions=False)
(you already do this — good). 1. Confirm the injected message is actually in the session history before handoff: ◦ After
chat_ctx.add_message(...)
verify it appears in
context.session.history
(log the history or inspect
propagated_ctx.items
). 1. Ensure the next agent receives the context in the way that preserves messages: ◦ Prefer constructing the next agent with
chat_ctx=propagated_ctx
or call the agent’s
update_chat_ctx(propagated_ctx)
/ equivalent API rather than just assigning
target_inst.chat_ctx = propagated_ctx
if that agent expects initialization via constructor or async update. 1. Avoid any intermediate code that filters system/instruction messages: ◦ Check any
copy()
calls with
exclude_instructions=True
, any handoff helpers, or middleware that might strip roles labeled as instructions. 1. If certain system messages are still missing, confirm they aren’t being classified/treated as “agent instructions” by helper functions — those may be intentionally filtered during handoffs. If so, add them as plain
system
messages you want preserved, and confirm the handoff uses
exclude_instructions=False
. If you want, paste: • the exact code where you add the system message, • the log output of
propagated_ctx
right before the handoff, and • how you construct or update
target_inst
(constructor call or update method). I can then point out exactly where the message is being dropped and give a code fix. Sources: Chat context | LiveKit Documentation | The Handoff Pattern for Voice Agents That Replaces IVR Menus | LiveKit | Agents and handoffs | LiveKit Documentation | How to Build a Background Observer for Voice AI Guardrails | LiveKit | How to Build a Background Observer for Voice AI Guardrails | LiveKit, with interactive elements
r
how can I log the
chat_ctx
messages to check if the messages are there before I copy it?
t
Good question — you can log
chat_ctx
contents a few simple ways to inspect messages before copying. Below are concise examples; pick the one that fits your environment. 1) Using pprint for readable output
Copy code
import pprint
pprint.pprint(context.session.history.items)
2) Iterating and logging each message with role/content/timestamp
Copy code
for i, msg in enumerate(context.session.history.items):
    log_lifecycle.info(f"history[{i}] role={msg.role} content={msg.content!r} meta={getattr(msg, 'meta', None)}")
3) Filter only system messages
Copy code
system_msgs = [m for m in context.session.history.items if getattr(m, "role", None) == "system"]
log_lifecycle.info(f"system messages: {pprint.pformat(system_msgs)}")
4) Log after adding a message to confirm it persisted
Copy code
chat_ctx.add_message(role="system", content=message)
log_lifecycle.info("after add_message: " + pprint.pformat([m for m in context.session.history.items][-5:]))
Notes: • Use
.items
on
context.session.history
to access the list of message objects. • Inspect
msg.role
,
msg.content
, and any
msg.meta
or timestamps to see how messages are classified. • If logs show the system message present in history but missing after copy, the drop likely happens during your copy/hand-off step — share that log if you want help pinpointing it., with interactive elements
r
this is how I inject the context into the session, but all the context I inject like this is not appearing on the context.session.history, only the regular chat messages between the user and the agent appear chat_ctx = ChatContext() chat_ctx.add_message(role="system", content=message) if session: agent_obj = getattr(session, "current_agent", None) or getattr(session, "agent", None) if agent_obj and hasattr(agent_obj, "update_chat_ctx"): await agent_obj.update_chat_ctx(chat_ctx) elif hasattr(session, "update_chat_ctx"): await session.update_chat_ctx(chat_ctx) return
t
Thanks — you're injecting a standalone
ChatContext
via
update_chat_ctx
, but those messages aren't appearing in
session.history
. Let's narrow the cause and fix it. Quick checklist and fixes 1) Merge with existing history before updating •
update_chat_ctx
typically replaces or merges depending on implementation. If it replaces, create a copy of current session history, append your system message, then update:
Copy code
ctx_copy = context.session.history.copy(exclude_instructions=False)
ctx_copy.add_message(role="system", content=message)
await session.update_chat_ctx(ctx_copy)
2) If
update_chat_ctx
expects full context on the agent instance • Construct or call the agent's update method with the merged context (not a fresh ChatContext that isn't linked to session history). 3) Verify
update_chat_ctx
behavior • Log the return value or inspect the agent/session after call to ensure the update succeeded:
Copy code
await agent_obj.update_chat_ctx(ctx_copy)
log_lifecycle.info("after update: " + pprint.pformat([m for m in context.session.history.items][-10:]))
4) Confirm messages are persisted in session.history • Immediately after your
add_message
+
update_chat_ctx
, log system messages:
Copy code
system_msgs = [m for m in context.session.history.items if m.role == "system"]
log_lifecycle.info("system messages after update: " + pprint.pformat(system_msgs))
5) If messages still don't appear • It likely means
update_chat_ctx
replaces
session.history
with only the agent-visible chat or filters system/instruction messages. In that case always merge into
context.session.history
directly:
Copy code
# merge manually
context.session.history.add_message(role="system", content=message)
# then call update if needed
await session.update_chat_ctx(context.session.history)
What I need if this doesn't resolve it • Paste the implementation/signature of
update_chat_ctx
(agent or session) or the agent constructor call you use when handing off. I’ll point to the exact line where the injected messages are being dropped., with interactive elements
r
It's very weird because the agent uses the information I'm injecting into the context, for example if I inject the information "User phone number is 777" and later I ask the agent "What's my phone number?" the agent responds adequatly. I don't understand why the agent does know the context that I'm injecting yet it does not appear on the history
t
Thanks — I see why you're puzzled: the agent is using the injected data, but those system messages don't show up in
session.history
. That's consistent with two different behaviors: messages can be present in an agent's internal
chat_ctx
(so the model sees them) without being appended to the shared
session.history
that you inspect. What to check and how to fix it • Confirm where the message is stored ◦ Right after
chat_ctx.add_message(...)
log the agent's chat context to confirm the message exists there:
Copy code
log_lifecycle.info("agent chat_ctx items: " + pprint.pformat([m for m in chat_ctx.items]))
• Also log
context.session.history.items
immediately after
update_chat_ctx
to confirm whether session history was updated:
Copy code
log_lifecycle.info("session.history items: " + pprint.pformat([m for m in context.session.history.items]))
• Likely cause ◦
update_chat_ctx(chat_ctx)
may set the agent's internal context without appending those messages into the shared
session.history
. The agent can use its internal
chat_ctx
to answer but
session.history
stays unchanged or is filtered (system/instruction messages often get filtered). • Two safe fixes 1) Merge into the session history before calling update:
Copy code
ctx_copy = context.session.history.copy(exclude_instructions=False)
     ctx_copy.add_message(role="system", content=message)
     await session.update_chat_ctx(ctx_copy)
2) Add directly to session.history and then update:
Copy code
context.session.history.add_message(role="system", content=message)
     await session.update_chat_ctx(context.session.history)
• Double-check filters ◦ Ensure no
exclude_instructions=True
or other filters run during copy/update that would remove system messages. If you'd like, paste the
update_chat_ctx
implementation or its signature (agent vs session) and the logs from the two quick prints above — I’ll point to the exact step where your messages are not being recorded in
session.history
. , with interactive elements
r
I cannot do that because the handout to the next agent (this code): if propagate_context: propagated_ctx = context.session.history.copy(exclude_instructions=False) # TalkingbitsAgent if hasattr(target_inst, "chat_ctx"): log_lifecycle.info(f"Context to be propagated {pprint.pformat(propagated_ctx)}") target_inst.chat_ctx = propagated_ctx and the context injection (this code): chat_ctx = ChatContext() chat_ctx.add_message(role="system", content=message) if session: agent_obj = getattr(session, "current_agent", None) or getattr(session, "agent", None) if agent_obj and hasattr(agent_obj, "update_chat_ctx"): await agent_obj.update_chat_ctx(chat_ctx) elif hasattr(session, "update_chat_ctx"): await session.update_chat_ctx(chat_ctx) return are done in different moments and sections of the code, it's not a single function.
t
Thanks — you’ve shown the two places run separately (injection via
update_chat_ctx
and later handoff copying
session.history
). That explains the behavior: your injected system messages are ending up in the agent’s internal
chat_ctx
(so the model sees them) but are not being appended to the shared
session.history
that you later copy and hand off. What to do (concise, actionable) 1. Ensure injected messages are merged into session.history when you add them ◦ Preferred pattern (merge then update):
Copy code
# merge into a copy of session history, then update
   ctx_copy = context.session.history.copy(exclude_instructions=False)
   ctx_copy.add_message(role="system", content=message)
   await session.update_chat_ctx(ctx_copy)
• Or add directly to session.history:
Copy code
context.session.history.add_message(role="system", content=message)
   await session.update_chat_ctx(context.session.history)
1. If you cannot change where injection happens, write a small synchronizing step before handoff ◦ Right before you call
propagated_ctx = context.session.history.copy(...)
, ensure any agent-internal
chat_ctx
messages are pushed into `session.history`:
Copy code
# sync agent chat_ctx -> session.history
   agent_ctx = getattr(session, "current_agent", None) or getattr(session, "agent", None)
   if agent_ctx and hasattr(agent_ctx, "chat_ctx"):
       for m in getattr(agent_ctx.chat_ctx, "items", []):
           if m.role == "system" and m not in context.session.history.items:
               context.session.history.add_message(role=m.role, content=m.content, **getattr(m, "meta", {}))
   propagated_ctx = context.session.history.copy(exclude_instructions=False)
1. Add quick checks/logs to confirm persistence
Copy code
# after injection
   log_lifecycle.info("agent chat_ctx: " + pprint.pformat([m for m in agent_ctx.chat_ctx.items]))
   # before handoff
   log_lifecycle.info("session.history: " + pprint.pformat([m for m in context.session.history.items]))
1. Watch for filters ◦ Any call that uses
exclude_instructions=True
or an update implementation that replaces history may remove system messages. Always copy/update with
exclude_instructions=False
when you want to preserve injected system messages. If you want, paste: • the implementation or signature of
update_chat_ctx
(agent and/or session) or • the small logs from the two checks above (agent chat_ctx and session.history) and I’ll point to the exact line to change. , with interactive elements
r
I changed the code on the injection part, but nothing changes, the message injected is still not in the copy of the message history I do later. Is there a way to inject the message directly into the history?
t
You're on the right track — yes, you can inject the message directly into the shared session history so it will appear in your later
copy()
and be propagated. Below are concise, safe patterns to do that plus logging checks. 1) Directly add to session history
Copy code
# add a system message directly to session.history
context.session.history.add_message(role="system", content=message)
# optionally update agent/session so agent internal ctx matches shared history
await session.update_chat_ctx(context.session.history)
2) Merge into a copy of session history, then update
Copy code
ctx_copy = context.session.history.copy(exclude_instructions=False)
ctx_copy.add_message(role="system", content=message)
await session.update_chat_ctx(ctx_copy)
3) Synchronize agent internal chat_ctx into session.history before handoff
Copy code
agent_obj = getattr(session, "current_agent", None) or getattr(session, "agent", None)
if agent_obj and hasattr(agent_obj, "chat_ctx"):
    for m in getattr(agent_obj.chat_ctx, "items", []):
        # skip duplicates; compare by role+content (or message id if available)
        if not any(m.role == h.role and m.content == h.content for h in context.session.history.items):
            context.session.history.add_message(role=m.role, content=m.content)
# now copy including system messages
propagated_ctx = context.session.history.copy(exclude_instructions=False)
4) Logging checks (verify persistence)
Copy code
import pprint
log_lifecycle.info("after inject — session.history items: " + pprint.pformat([
    {"role": m.role, "content": m.content} for m in context.session.history.items
]))
# and agent internal
if agent_obj and hasattr(agent_obj, "chat_ctx"):
    log_lifecycle.info("agent chat_ctx items: " + pprint.pformat([
        {"role": m.role, "content": m.content} for m in agent_obj.chat_ctx.items
    ]))
Notes and pitfalls • Use
exclude_instructions=False
when copying if you want system/instruction messages preserved. • Some
update_chat_ctx
implementations replace history rather than merge. Adding directly to
context.session.history
ensures persistence. • Avoid adding duplicates by comparing role+content or message IDs. If this still doesn't make the system messages appear in
context.session.history
, please paste: • the exact
update_chat_ctx
signature/implementation (agent or session), • a short log output of the two quick prints above (agent chat_ctx and session.history). I’ll point to the exact line that needs changing. , with interactive elements