red-optician-55467
04/30/2026, 2:11 PMtall-airline-16756
04/30/2026, 2:12 PMtall-airline-16756
04/30/2026, 2:12 PMsystem 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 elementsred-optician-55467
04/30/2026, 2:20 PMchat_ctx messages to check if the messages are there before I copy it?tall-airline-16756
04/30/2026, 2:21 PMchat_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
import pprint
pprint.pprint(context.session.history.items)
2) Iterating and logging each message with role/content/timestamp
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
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
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 elementsred-optician-55467
04/30/2026, 4:28 PMtall-airline-16756
04/30/2026, 4:28 PMChatContext 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:
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:
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:
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:
# 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 elementsred-optician-55467
04/30/2026, 4:31 PMtall-airline-16756
04/30/2026, 4:32 PMsession.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:
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:
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:
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:
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 elementsred-optician-55467
04/30/2026, 4:35 PMtall-airline-16756
04/30/2026, 4:35 PMupdate_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):
# 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:
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`:
# 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
# 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 elementsred-optician-55467
04/30/2026, 4:43 PMtall-airline-16756
04/30/2026, 4:43 PMcopy() and be propagated. Below are concise, safe patterns to do that plus logging checks.
1) Directly add to session history
# 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
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
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)
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