馃敶 Required Information
Please ensure all items in this section are completed to allow for efficient
triaging. Requests without complete information may be rejected / deprioritized.
If an item is not applicable to you - please mark it as N/A
Describe the Bug:
With Gemini(use_interactions_api=True), when a chat agent delegates to a mode="single_turn" sub-agent, the parent's next request drops the delegation's function_response.
ADK logs:
WARNING google_adk...tools._rearranger: Dropping function responses with no matching function call: ['call_root_1']
The Interactions delta then carries only the sub-agent's quoted final text. The server-side interaction is left with an unanswered function_call, and anything an after_tool_callback attached to the result is lost.
Steps to Reproduce:
import asyncio
from collections.abc import AsyncGenerator
from google.adk.agents import Agent
from google.adk.models.google_llm import Gemini
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.adk.runners import InMemoryRunner
from google.genai import types
from pydantic import PrivateAttr
class Scripted(Gemini):
use_interactions_api: bool = True
_script: list[LlmResponse] = PrivateAttr(default_factory=list)
_requests: list[LlmRequest] = PrivateAttr(default_factory=list)
async def generate_content_async(
self, llm_request: LlmRequest, stream: bool = False
) -> AsyncGenerator[LlmResponse, None]:
self._requests.append(llm_request.model_copy(deep=True))
yield self._script.pop(0)
def turn(iid: str, part: types.Part) -> LlmResponse:
return LlmResponse(content=types.Content(role="model", parts=[part]), interaction_id=iid)
async def main() -> None:
child_model = Scripted(model="gemini-3-flash")
child_model._script = [turn("c1", types.Part(text="child answer"))]
child = Agent(name="child", description="d", mode="single_turn", model=child_model)
root_model = Scripted(model="gemini-3-flash")
root_model._script = [
turn("r1", types.Part(function_call=types.FunctionCall(
id="call_root_1", name="child", args={"request": "do it"}))),
turn("r2", types.Part(text="done")),
]
root = Agent(name="root", model=root_model, sub_agents=[child])
runner = InMemoryRunner(agent=root, app_name="repro")
session = await runner.session_service.create_session(app_name="repro", user_id="u")
async for _ in runner.run_async(
user_id="u", session_id=session.id,
new_message=types.Content(role="user", parts=[types.Part(text="hi")]),
):
pass
# Expected: [..., function_call(call_root_1), function_response(call_root_1)]
# Actual: [user: "For context: ... [child] said: child answer"]
for c in root_model._requests[1].contents:
print(c.role, [p.function_response or p.function_call or p.text for p in c.parts])
asyncio.run(main())
Environment Details:
- ADK Library Version (pip show google-adk): 2.10.0
- Desktop OS: Linux
- Python Version (python -V): 3.14.5
Model Information:
- Are you using LiteLLM: No
- Which model is being used: gemini-3.8-flash
馃煛 Optional Information
Providing this information greatly speeds up the resolution process.
Regression:
N/A
Logs:
See above
Screenshots / Video:
N/A
Additional Context:
Add any other context about the problem here.
Minimal Reproduction Code:
See above
How often has this issue occurred?:
Always (100%)
Potential root cause
_SingleTurnAgentTool.run_async runs the child with tool_context.run_node(..., override_branch="<agent>@<fc_id>") but no override_isolation_scope. So the child's events (including its synthetic author="user" input event) are unscoped and visible to the parent. They land in session.events between the parent's function_call and its function_response.
- On the Interactions path,
_get_current_turn_contents scans backward for the turn start. It only keeps scanning past a function_response whose author is user (_is_submitted_tool_result). The parent's result is authored by the parent agent, so the scan stops at the child's last event.
- The parent's
function_call falls outside the slice, so drop_orphaned_function_responses removes the result.
Task mode doesn't hit this, because _dispatch_task_fc passes override_isolation_scope=fc.id and _synthesize_task_fr_event authors the result as user.
Suggested fix (either)
- In
_get_current_turn_contents, add every event's function_response ids to unmatched_response_ids, not only user-submitted ones. That's what the invariant in the comment above the loop requires ("the slice must reach back far enough to include the call it answers"). Verified locally: the slice becomes [user, function_call, function_response] and the Interactions delta is exactly the function_response.
- Or run
_SingleTurnAgentTool children under override_isolation_scope=fc_id, as task mode does.
馃敶 Required Information
Please ensure all items in this section are completed to allow for efficient
triaging. Requests without complete information may be rejected / deprioritized.
If an item is not applicable to you - please mark it as N/A
Describe the Bug:
With
Gemini(use_interactions_api=True), when a chat agent delegates to amode="single_turn"sub-agent, the parent's next request drops the delegation'sfunction_response.ADK logs:
The Interactions delta then carries only the sub-agent's quoted final text. The server-side interaction is left with an unanswered
function_call, and anything anafter_tool_callbackattached to the result is lost.Steps to Reproduce:
Environment Details:
Model Information:
馃煛 Optional Information
Providing this information greatly speeds up the resolution process.
Regression:
N/A
Logs:
See above
Screenshots / Video:
N/A
Additional Context:
Add any other context about the problem here.
Minimal Reproduction Code:
See above
How often has this issue occurred?:
Always (100%)
Potential root cause
_SingleTurnAgentTool.run_asyncruns the child withtool_context.run_node(..., override_branch="<agent>@<fc_id>")but nooverride_isolation_scope. So the child's events (including its syntheticauthor="user"input event) are unscoped and visible to the parent. They land insession.eventsbetween the parent'sfunction_calland itsfunction_response._get_current_turn_contentsscans backward for the turn start. It only keeps scanning past afunction_responsewhose author isuser(_is_submitted_tool_result). The parent's result is authored by the parent agent, so the scan stops at the child's last event.function_callfalls outside the slice, sodrop_orphaned_function_responsesremoves the result.Task mode doesn't hit this, because
_dispatch_task_fcpassesoverride_isolation_scope=fc.idand_synthesize_task_fr_eventauthors the result asuser.Suggested fix (either)
_get_current_turn_contents, add every event's function_response ids tounmatched_response_ids, not only user-submitted ones. That's what the invariant in the comment above the loop requires ("the slice must reach back far enough to include the call it answers"). Verified locally: the slice becomes[user, function_call, function_response]and the Interactions delta is exactly thefunction_response._SingleTurnAgentToolchildren underoverride_isolation_scope=fc_id, as task mode does.