Skip to content

Parallel tool calls: state_delta list values silently overwritten during merge #5190

@thequantumquirk

Description

@thequantumquirk

Description

When multiple tool calls run in parallel and each writes to the same state_delta key containing a list value, merge_parallel_function_response_events silently drops all but the last value.

Root Cause

deep_merge_dicts in flows/llm_flows/functions.py (line ~822) only recurses into dict values. Lists hit the else branch and get overwritten:

def deep_merge_dicts(d1: dict, d2: dict) -> dict:
    for key, value in d2.items():
        if key in d1 and isinstance(d1[key], dict) and isinstance(value, dict):
            d1[key] = deep_merge_dicts(d1[key], value)
        else:
            d1[key] = value  # <-- lists are overwritten here
    return d1

Reproduction

  1. Create a tool that appends to a list in tool_context.state:
def my_tool(tool_context, item):
    items = tool_context.state.get("items") or []
    items = list(items)
    items.append(item)
    tool_context.state["items"] = items
  1. Have the LLM call this tool multiple times in a single response (parallel execution via asyncio.gather in handle_function_calls_live)
  2. Each parallel call gets its own ToolContext with a separate state_delta
  3. Tool A's delta: {"state_delta": {"items": ["a"]}}
  4. Tool B's delta: {"state_delta": {"items": ["b"]}}
  5. After merge: {"state_delta": {"items": ["b"]}} — item "a" is lost

Impact

Any application that accumulates list state across parallel tool calls loses data. In our case, drafting multiple emails in a single agent turn results in only ~2 out of 10 emails persisting.

Suggested Fix

deep_merge_dicts should concatenate lists instead of overwriting:

def deep_merge_dicts(d1: dict, d2: dict) -> dict:
    for key, value in d2.items():
        if key in d1 and isinstance(d1[key], dict) and isinstance(value, dict):
            d1[key] = deep_merge_dicts(d1[key], value)
        elif key in d1 and isinstance(d1[key], list) and isinstance(value, list):
            d1[key] = d1[key] + value
        else:
            d1[key] = value
    return d1

Environment

  • google-adk 1.18.0
  • Python 3.12
  • Using DatabaseSessionService with LiteLLM + OpenAI models

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions