Skip to content

AgentTask hardcodes exclude_function_call=True when merging chat context back to parent agent, causing loss of tool call history #5284

@tonydialpad

Description

@tonydialpad

Bug Description

When an AgentTask completes, the __await_impl finally block merges the sub-task's chat_ctx back into the parent agent's chat_ctx with a hardcoded exclude_function_call=True:

merged_chat_ctx = old_agent.chat_ctx.merge(
self.chat_ctx, exclude_function_call=True, exclude_instructions=True
)
# set the chat_ctx directly, `session._update_activity` will sync it to the rt_session if needed
old_agent._chat_ctx.items[:] = merged_chat_ctx.items
# await old_agent.update_chat_ctx(merged_chat_ctx)

merged_chat_ctx = old_agent.chat_ctx.merge(
    self.chat_ctx, exclude_function_call=True, exclude_instructions=True
)
# set the chat_ctx directly, [session._update_activity](cci:1://file:///Users/tony/src/python-core/.venv/lib/python3.11/site-packages/livekit/agents/voice/agent_session.py:1107:4-1181:63) will sync it to the rt_session if needed
old_agent._chat_ctx.items[:] = merged_chat_ctx.items

This unconditionally strips all function_call and function_call_output items from the sub-task's context when merging back to the parent. There is no parameter or hook to preserve them.

Impact

  • Tool call history is permanently lost when an AgentTask completes. Any data retrieved via tool calls during the sub-task (e.g., MCP tool results, API responses, database lookups) is removed from the parent agent's context.
  • The parent's LLM session is synced with the stripped context during resume() → _start_session(), before the caller has any opportunity to intervene. For RealtimeModel, this means rt_session.update_chat_ctx() is called with a context missing all function call history.
  • No workaround within the framework: the merge directly mutates old_agent._chat_ctx.items[:], bypassing update_chat_ctx. Callers can manually call update_chat_ctx after await task returns, but by that point the Realtime session has already been synced with the filtered context.

Expected Behavior

The merge behavior should be configurable. Users should be able to preserve function call history from sub-tasks when it's needed for context continuity (e.g., when users can switch between tasks and resume previous ones).

Reproduction Steps

  1. Create an Agent (parent) with tool-calling capabilities
  2. Create an AgentTask (sub-task) that executes tool calls during its lifecycle (e.g., MCP tools, function tools that return data)
  3. Complete the sub-task via self.complete(result)
  4. Observe the parent agent's chat_ctx after the task returns — all function_call and function_call_output items from the sub-task are missing
class MySubTask(AgentTask[str]):
    async def on_enter(self):
        # Tool calls happen during sub-task lifecycle
        # e.g., MCP tools fetch data, function tools execute actions
        ...
        self.complete("done")

# In parent agent's function tool:
result = await MySubTask(instructions="...", chat_ctx=self.chat_ctx.copy(), tools=[...])
# At this point, parent's chat_ctx has lost all function_call/function_call_output
# items from the sub-task

Operating System

macOS 15, all Linux platforms

Models Used

No response

Package Versions

livekit-agents==1.4.3 (Commit: 0ed8e6b9b04971b88d9fcecf099dcdf2f94d11d4)

Session/Room/Call IDs

No response

Proposed Solution

Add a parameter to AgentTask (or complete()) to control the merge behavior:

class AgentTask(Agent, Generic[TaskResult_T]):
    def __init__(self, *, merge_exclude_function_call: bool = True, ...):
        self._merge_exclude_function_call = merge_exclude_function_call
        ...

Then in __await_impl's finally block:

merged_chat_ctx = old_agent.chat_ctx.merge(
    self.chat_ctx,
    exclude_function_call=self._merge_exclude_function_call,
    exclude_instructions=True,
)

This preserves backward compatibility (default True) while allowing users to opt in to preserving function call history.

Additional Context

No response

Screenshots and Recordings

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions