Skip to content

fix(tools): pass the caller's temp: state to an AgentTool's wrapped agent - #7596

Closed
Somay-kousis wants to merge 2 commits into
google:mainfrom
Somay-kousis:fix/agent-tool-temp-state
Closed

Somay-kousis wants to merge 2 commits into
google:mainfrom
Somay-kousis:fix/agent-tool-temp-state

Conversation

@Somay-kousis

Copy link
Copy Markdown
Contributor

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

2. Or, if no issue exists, describe the change:

Problem:

AgentTool.run_async copies the caller's state, temp: keys included, into create_session(state=...) for the nested runner. create_session drops temp: keys because they are never stored, so the wrapped agent can't see them. {temp:key?} in its instruction renders empty, {temp:key} raises KeyError, and its callbacks and tools read None.

Solution:

Split the temp: keys out of the copied state and pass them to the nested runner.run_async as state_delta. That's the public way to set temp: state for one invocation: append_event applies it to the in-memory session, then trims it from the stored event. Other keys still go through create_session as before, and nothing is persisted that wasn't before. Values that aren't JSON-serializable reach the wrapped agent unchanged, since they only live in memory.

Out of scope: temp: writes made inside the wrapped agent going back to the caller. Happy to look at that separately.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

test_agent_tool_reads_caller_temp_state in tests/unittests/tools/test_agent_tool.py, parametrized over each way the wrapped agent reads state: a required {temp:ticket} in its instruction, an optional {temp:ticket?}, its before_agent_callback, a tool it calls, a SequentialAgent wrapping an LlmAgent (the nested runner's legacy path), and an agent with input_schema. The caller sets a string temp: key, a temp: key holding a non-JSON-serializable object (checked by identity) and a plain key.

Before the fix:

message=f'Dynamic node {executed_name} failed',
E         google.adk.workflow._errors.DynamicNodeFailError: Dynamic node root_agent failed
ERROR    google_adk.google.adk.workflow._node_runner:_node_runner.py:163 Node execution failed with exception
ERROR    google_adk.google.adk.runners:runners.py:903 Root node tool_agent failed.
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node tool_agent failed
ERROR    google_adk.google.adk.workflow._node_runner:_node_runner.py:163 Node execution failed with exception
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node tool_agent failed
ERROR    google_adk.google.adk.runners:runners.py:903 Root node root_agent failed.
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node root_agent failed
              message=f'Dynamic node {executed_name} failed',
E         google.adk.workflow._errors.DynamicNodeFailError: Dynamic node root_agent failed
ERROR    google_adk.google.adk.workflow._node_runner:_node_runner.py:163 Node execution failed with exception
ERROR    google_adk.google.adk.runners:runners.py:903 Root node root_agent failed.
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node root_agent failed
              message=f'Dynamic node {executed_name} failed',
E         google.adk.workflow._errors.DynamicNodeFailError: Dynamic node root_agent failed
ERROR    google_adk.google.adk.workflow._node_runner:_node_runner.py:163 Node execution failed with exception
ERROR    google_adk.google.adk.runners:runners.py:903 Root node tool_agent failed.
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node tool_agent failed
ERROR    google_adk.google.adk.workflow._node_runner:_node_runner.py:163 Node execution failed with exception
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node tool_agent failed
ERROR    google_adk.google.adk.runners:runners.py:903 Root node root_agent failed.
google.adk.workflow._errors.DynamicNodeFailError: Dynamic node root_agent failed
FAILED tests/unittests/tools/test_agent_tool.py::test_agent_tool_reads_caller_temp_state[instruction]
FAILED tests/unittests/tools/test_agent_tool.py::test_agent_tool_reads_caller_temp_state[optional_instruction]
FAILED tests/unittests/tools/test_agent_tool.py::test_agent_tool_reads_caller_temp_state[callback]
FAILED tests/unittests/tools/test_agent_tool.py::test_agent_tool_reads_caller_temp_state[tool]
FAILED tests/unittests/tools/test_agent_tool.py::test_agent_tool_reads_caller_temp_state[sequential_agent]
FAILED tests/unittests/tools/test_agent_tool.py::test_agent_tool_reads_caller_temp_state[input_schema]
6 failed, 61 deselected in 10.40s

After the fix:

$ pytest tests/unittests/tools/test_agent_tool.py
67 passed in 6.86s

$ pytest tests/unittests/tools
3136 passed in 141.24s (0:02:21)

pyink --check and isort --check-only are clean on the changed files. mypy src/google/adk/tools/agent_tool.py reports the same one error as on main (AgentTool(agent=agent) in from_config, an existing BaseNode vs BaseAgent mismatch).

Manual End-to-End (E2E) Tests:

The script from the issue runs a coordinator LlmAgent with a tool that sets temp:ticket and an AgentTool specialist, on InMemoryRunner with fake models.

Before:

specialist instruction 'Ticket: {temp:ticket?}'
  coordinator saw: ['Ticket: ', 'Ticket: T-42', 'Ticket: T-42']
  specialist saw:  ['Ticket: ']
specialist instruction 'Ticket: {temp:ticket}'
  coordinator saw: ['Ticket: ', 'Ticket: T-42']
  specialist saw:  []
  run raised:      KeyError: "Context variable not found: `temp:ticket` in agent 'specialist'."

After:

specialist instruction 'Ticket: {temp:ticket?}'
  coordinator saw: ['Ticket: ', 'Ticket: T-42', 'Ticket: T-42']
  specialist saw:  ['Ticket: T-42']
specialist instruction 'Ticket: {temp:ticket}'
  coordinator saw: ['Ticket: ', 'Ticket: T-42', 'Ticket: T-42']
  specialist saw:  ['Ticket: T-42']

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Open PRs touching agent_tool.py: #6020, #7045, #7249 and #7401. None changes how the caller's state reaches the nested session. #7045 re-indents the event loop, so one of the two may need a small rebase.

…gent

AgentTool copied the caller's state into create_session for the nested
runner, and create_session drops temp: keys because they are never stored.
The wrapped agent then saw no temp: state: {temp:key} in its instruction
raised KeyError, {temp:key?} rendered empty, and its callbacks and tools
read None. Pass the temp: keys to the nested run_async as state_delta,
which applies them for that invocation without storing them.
@Somay-kousis
Somay-kousis force-pushed the fix/agent-tool-temp-state branch from 31e19ce to 1f6451a Compare October 19, 2026 09:00
copybara-service Bot pushed a commit that referenced this pull request Oct 24, 2026
Merge #7596

The wrapped agent now sees the caller's temp: keys in its instruction, callbacks and tools; before, they were dropped when the nested session was created. ADK's own temp:_adk keys are still not passed on. The keys travel one way: dict, list and set values are shallow-copied so in-place edits inside the wrapped agent do not reach the caller's state, and temp: writes made inside the wrapped agent are not copied back.

Fixes #7595

PiperOrigin-RevId: 1006234842
@adk-bot

adk-bot commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Thank you @Somay-kousis for your contribution! 🎉

Your changes have been successfully imported and merged via Copybara in commit 12c7bed.

Closing this PR as the changes are now in the main branch.

@adk-bot adk-bot added the merged [Status] This PR is merged label Oct 24, 2026
@adk-bot adk-bot closed this Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged [Status] This PR is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AgentTool's wrapped agent can't read the caller's temp: state

3 participants